Cache-Control — co to jest, dyrektywy i zarządzanie buforowaniem

Autor: IT Sectr Opublikowano: 2026-03-09 Czas czytania: 9 min

Cache-Control — nagłówek HTTP, który określa zasady buforowania zasobów po stronie klienta, serwerów proxy i CDN za pomocą zestawu dyrektyw. W przeciwieństwie do przestarzałego nagłówka Expires, Cache-Control obsługuje dziesiątki kombinacji: max-age określa czas życia w sekundach, private i public zarządzają dostępnością pamięci podręcznej, no-cache i no-store — wymuszonym sprawdzaniem. Według Google Web Dev (2025), prawidłowa konfiguracja Cache-Control może skrócić czas ładowania stron o 50-80% przy ponownych odwiedzinach. To czyni nagłówek krytycznym dla wydajności aplikacji internetowych i mobilnych.

Najważniejsze

  • Cache-Control — nagłówek HTTP z dyrektywami zarządzającymi buforowaniem na kliencie, proxy i CDN
  • max-age — kluczowa dyrektywa określająca czas życia zasobu w sekundach bez ponownego sprawdzania
  • private vs public — private zezwala na pamięć podręczną tylko na kliencie, public — także na proxy i CDN
  • no-cache vs no-store — no-cache wymaga sprawdzenia przed użyciem, no-store całkowicie zabrania buforowania
  • s-maxage — nadpisuje max-age dla współdzielonych (shared) pamięci podręcznych, nie wpływając na przeglądarki

Czym jest Cache-Control?

Cache-Control — to nagłówek HTTP, ustandaryzowany w HTTP/1.1 (RFC 7234), który pozwala serwerowi określić, jak i przez jaki czas klienci, proxy i CDN mogą buforować odpowiedź. W przeciwieństwie do Expires (HTTP/1.0), Cache-Control używa dyrektyw — poleceń tekstowych, które łączy się przecinkami: Cache-Control: public, max-age=3600, must-revalidate. Nagłówek daje precyzyjną kontrolę nad każdym ogniwem łańcucha buforowania.

Buforowanie to jeden z fundamentalnych mechanizmów wydajności sieci i aplikacji mobilnych. Bez niego każde żądanie użytkownika szłoby bezpośrednio do serwera, powodując nadmierne obciążenie i opóźnienia. Cache-Control określa trzy poziomy buforowania: przeglądarka/aplikacja (private cache), serwery proxy (shared cache) i CDN (distributed cache). Każdy poziom interpretuje dyrektywy na swój sposób.

Nieprawidłowa konfiguracja Cache-Control to jedna z najczęstszych przyczyn problemów z wydajnością. Zbyt agresywne buforowanie powoduje, że użytkownicy widzą nieaktualne dane. Zbyt słabe — prowadzi do nadmiernych żądań do serwera i wolnego ładowania. Według Akamai (2025), optymalizacja Cache-Control dla treści statycznych zmniejsza obciążenie serwera o 70-90% i poprawia czas ładowania o 40-60% dla użytkowników mobilnych.

Historia nagłówka

Cache-Control pojawił się w HTTP/1.1 (RFC 2616, 1999) jako zamiennik Expires. Expires miał fundamentalny problem: używał absolutnej daty, która zależała od strefy czasowej serwera i klienta. Cache-Control rozwiązał ten problem, przechodząc na czas względny (max-age w sekundach od momentu otrzymania odpowiedzi). Później w RFC 7234 (2014) dodano nowe dyrektywy: immutable dla statyków, stale-while-revalidate i stale-if-error dla odroczonego sprawdzania.

Dyrektywy Cache-Control

Cache-Control zawiera ponad 10 dyrektyw podzielonych na trzy grupy: dyrektywy żądania (klient → serwer), dyrektywy odpowiedzi (serwer → klient) i rozszerzenia. W praktyce w programowaniu mobilnym używa się 6-7 podstawowych dyrektyw odpowiedzi, które pokrywają 95% scenariuszy buforowania. Omówmy każdą z przykładami i zaleceniami.

DyrektywaZnaczeniePrzykład
max-ageCzas życia w sekundach od momentu odpowiedzimax-age=3600 — 1 godzina
s-maxagemax-age dla shared cache (proxy, CDN)s-maxage=86400 — 1 dzień dla CDN
publicZezwala na buforowanie wszystkim (w tym proxy)public, max-age=3600
privateZezwala na pamięć podręczną tylko przeglądarce/aplikacjiprivate, max-age=600
no-cacheNie używaj bez sprawdzenia (304 obowiązkowy)no-cache
no-storeCałkowity zakaz buforowaniano-store
must-revalidatePo max-age obowiązkowo sprawdź u originmax-age=3600, must-revalidate
immutableZasób nie zmieni się (dla wersjonowanych statyków)max-age=31536000, immutable

max-age — najważniejsza dyrektywa. Zabrania klientowi wysyłania żądania do serwera przez określony czas. Dla statyków (CSS, JS, obrazy) max-age ustawia się zwykle od 1 dnia do 1 roku. Dla odpowiedzi API — od 0 sekund (dane zawsze świeże) do 5-10 minut (dane referencyjne). s-maxage pozwala ustawić różny czas życia dla CDN i przeglądarki: CDN przechowuje kopię 1 dzień, przeglądarka — 1 godzinę.

no-cache vs no-store

Te dwie dyrektywy są często mylone. no-cache nie zabrania buforowania — wymaga sprawdzania buforowanej kopii przy każdym użyciu przez żądanie warunkowe (If-Modified-Since lub If-None-Match). Jeśli serwer odpowiada 304 — klient używa pamięci podręcznej. Jeśli 200 — aktualizuje. no-store natomiast całkowicie zabrania zapisywania odpowiedzi w jakiejkolwiek pamięci podręcznej, w tym dyskowej i operacyjnej. Używaj no-store tylko dla danych wrażliwych — tokeny, dane płatnicze, dokumenty osobiste.

Cache-Control vs Expires

Nagłówek Expires (HTTP/1.0) również określa czas życia zasobu, ale używa absolutnej daty: Expires: Thu, 03 Jul 2026 12:00:00 GMT. Cache-Control max-age — czas względny od momentu odpowiedzi. Różnica jest krytyczna dla systemów rozproszonych: jeśli serwer i klient znajdują się w różnych strefach czasowych, Expires może być interpretowany nieprawidłowo. Cache-Control nie ma tego problemu — 3600 sekund to zawsze 3600 sekund.

Gdy oba nagłówki są obecne, Cache-Control ma priorytet nad Expires. Jest to określone w RFC 7234: „Jeśli odpowiedź zawiera Cache-Control z dyrektywą max-age, odbiorca MUSI zignorować Expires”. W praktyce zaleca się nie zwracać Expires dla nowoczesnych klientów, ponieważ Cache-Control pokrywa wszystkie scenariusze Expires. Jednak dla zachowania wstecznej kompatybilności ze starymi proxy i przeglądarkami można zwracać oba nagłówki.

Expires zachował się głównie dla treści statycznych na Nginx i Apache — te serwery automatycznie dodają oba nagłówki. Jeśli w twoim projekcie występuje Expires bez Cache-Control, zastąp go Cache-Control z max-age: precyzja zarządzania pamięcią podręczną wzrasta, a zależność od strefy czasowej zostaje wyeliminowana. Do migracji wystarczy skonfigurować serwer tak, aby dodawał Cache-Control zamiast Expires.

nginx
# Nginx: Cache-Control dla plików statycznych
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 30d;
    add_header Cache-Control "public, immutable, max-age=2592000";
}

# Różne polityki dla różnych typów treści
location /api/config {
    expires -1;
    add_header Cache-Control "no-cache, must-revalidate";
}

location /api/static-data {
    expires 5m;
    add_header Cache-Control "public, max-age=300";
}

W konfiguracji Nginx dla plików statycznych (CSS, JS, obrazy) ustawiany jest Cache-Control na 30 dni z atrybutem immutable — ten atrybut informuje przeglądarkę, że zasób nigdy nie zmienia się pod tym adresem URL (wersjonowanie przez hash w nazwie pliku). Punkty końcowe API używają no-cache dla danych dynamicznych i public z krótkim max-age dla referencyjnych — często żądanych i rzadko zmieniających się list.

Buforowanie w aplikacjach mobilnych

W aplikacjach mobilnych Cache-Control odgrywa szczególną rolę ze względu na ograniczenia sieci mobilnych: wysokie opóźnienie, niestabilne połączenie, limity transferu. Prawidłowe buforowanie pozwala użytkownikowi widzieć dane natychmiast, nawet offline, i aktualizować je w tle. OkHttp na Androidzie i URLSession na iOS mają wbudowane systemy buforowania uwzględniające Cache-Control.

OkHttp używa CacheInterceptor, który odczytuje Cache-Control z odpowiedzi i automatycznie zarządza buforowaniem. Jeśli serwer zwrócił Cache-Control: max-age=3600, OkHttp nie będzie wysyłać żądania do serwera przez godzinę. Po wygaśnięciu max-age OkHttp wysyła żądanie warunkowe z If-Modified-Since i If-None-Match. Konfiguracja pamięci podręcznej w OkHttp: OkHttpClient.Builder().cache(Cache(directory, maxSize)).

kotlin
fun createCachedClient(cacheDir: File): OkHttpClient {
    return OkHttpClient.Builder()
        .cache(Cache(cacheDir, 10L * 1024 * 1024))
        .addNetworkInterceptor { chain ->
            val response = chain.proceed(chain.request())
            response.newBuilder()
                .header("Cache-Control",
                    "public, max-age=300")
                .removeHeader("Pragma")
                .build()
        }
        .build()
}

Kod tworzy OkHttpClient z pamięcią podręczną 10 MB i nadpisuje Cache-Control przez NetworkInterceptor. Jeśli serwer nie zwraca Cache-Control lub używa Expires, intercept dodaje public, max-age=300 (5 minut). Interceptor usuwa przestarzały nagłówek Pragma (HTTP/1.0) dla zgodności. Według analogicznego schematu działa buforowanie na iOS przez URLCache.shared z konfiguracją memoryCapacity i diskCapacity.

Tryb offline i stale-while-revalidate

Dyrektywa stale-while-revalidate pozwala użytkownikowi wyświetlać nieaktualną pamięć podręczną (stale), podczas gdy aplikacja w tle ładuje świeże dane. Daje to efekt natychmiastowej odpowiedzi: użytkownik widzi treść od razu, a po sekundzie jest ona aktualizowana. Obsługiwane przez OkHttp od wersji 3.10 i URLCache na iOS 14+. Przykład: Cache-Control: max-age=3600, stale-while-revalidate=300 — 1 godzina aktualnej pamięci podręcznej, potem 5 minut wyświetlania nieaktualnej z aktualizacją w tle.

Przykłady konfiguracji Cache-Control

Różne typy zasobów wymagają różnych strategii buforowania. Omówmy optymalne konfiguracje dla typowych scenariuszy w programowaniu mobilnym. Dla treści statycznych z hashem w nazwie pliku (bundle.abc123.js) można ustawić max-age do 1 roku z immutable. Dla list API, które aktualizują się rzadko (katalogi, kategorie), — max-age od 5 minut do 1 godziny z stale-while-revalidate.

Typ zasobuCache-ControlWyjaśnienie
Wersjonowane statykipublic, max-age=31536000, immutable1 rok, pliki się nie zmieniają (hash w URL)
Niewersjonowane statykipublic, max-age=86400, must-revalidate1 dzień z obowiązkowym sprawdzeniem po
API: dane referencyjnepublic, max-age=600, stale-while-revalidate=6010 minut pamięci podręcznej + 1 minuta stale
API: dane użytkownikaprivate, max-age=601 minuta, tylko dla konkretnego użytkownika
API: dane wrażliweno-storeCałkowity zakaz buforowania
Strony HTMLno-cache, must-revalidateSprawdzanie przy każdym żądaniu, 304 przy braku zmian

Ważne jest, aby pamiętać o bezpieczeństwie: dla odpowiedzi zawierających dane osobowe użytkownika zawsze ustawiaj private. Bez tej dyrektywy publiczne proxy (np. korporacyjne) może buforować odpowiedź i przekazać ją innemu użytkownikowi. Dla tokenów uwierzytelniania i informacji płatniczych używaj no-store — nawet private cache nie powinien zapisywać tych danych na dysk.

Debugowanie buforowania

Aby sprawdzić poprawność Cache-Control, użyj nagłówka Age (ile sekund pamięć podręczna jest przechowywana) i X-Cache (hit/miss na CDN). W przeglądarce — zakładka Network, kolumna Size pokazuje „from disk cache” lub „304 Not Modified”. Jeśli zasób powinien być buforowany, ale ładuje się za każdym razem — sprawdź, czy serwer nie dodaje Cache-Control: no-cache lub Pragma: no-cache razem z twoimi dyrektywami.

Często zadawane pytania

Jaka jest różnica między max-age a s-maxage?

max-age działa dla wszystkich pamięci podręcznych (w tym przeglądarek), s-maxage — tylko dla shared cache (proxy, CDN). Jeśli podano s-maxage, CDN ignoruje max-age i używa s-maxage. Pozwala to ustawić różny czas życia dla przeglądarki i CDN.

Czy można anulować buforowanie po wysłaniu Cache-Control?

Nie, po wysłaniu odpowiedzi z max-age klient nie będzie wysyłać żądania do wygaśnięcia timera. Do natychmiastowej unieważnienia pamięci podręcznej należy zmienić URL zasobu (dodać wersję/hash) i rozesłać powiadomienia push lub wiadomości WebSocket w celu wymuszonego resetu.

Czym jest dyrektywa immutable?

Dyrektywa immutable (RFC 8246) informuje przeglądarkę, że zasób nigdy się nie zmieni pod danym adresem URL. Przeglądarka nawet nie próbuje wysłać żądania warunkowego przy odświeżaniu strony — używa pamięci podręcznej do wygaśnięcia max-age. Działa tylko z wersjonowanymi plikami.

Jak Cache-Control wpływa na SEO?

Googlebot uwzględnia Cache-Control: długie buforowanie przyspiesza ponowne skanowanie. noindex z szybką pamięcią podręczną — ok. no-store może spowolnić indeksowanie, ponieważ Googlebot będzie ładować stronę za każdym razem od zera. Zbyt krótki max-age zwiększa obciążenie serwera podczas skanowania.

Jak skonfigurować Cache-Control w Express.js?

Przez helmet lub middleware: res.set('Cache-Control', 'public, max-age=3600'). Dla statyków użyj express.static z parametrem maxAge: express.static('public', {maxAge: '1y'}). Dla dynamicznych tras — indywidualnie w każdym handlerze.

Podsumowanie

  • Cache-Control — główny nagłówek HTTP do zarządzania buforowaniem z elastycznym systemem dyrektyw
  • max-age — czas życia w sekundach od momentu odpowiedzi; kluczowa dyrektywa dla wszystkich scenariuszy buforowania
  • private vs public — private tylko dla klienta, public dla proxy i CDN; wpływa na bezpieczeństwo danych
  • no-cache wymaga sprawdzenia, no-store całkowicie zabrania buforowania; różne przeznaczenie, nie mylić
  • s-maxage — nadpisuje max-age dla shared cache, przydatny do rozdzielenia polityk przeglądarka/CDN
  • stale-while-revalidate — wyświetlanie nieaktualnej pamięci podręcznej z aktualizacją w tle dla natychmiastowego UX
  • Zalecenie — skonfiguruj Cache-Control dla każdego typu zasobów na serwerze i w kliencie HTTP aplikacji mobilnej

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również