Inwalidacja cache w 2026: tag-based purging, stale-while-revalidate i surrogate keys w CDN
Praktyczny przewodnik po inwalidacji cache w 2026: tag-based purging, soft purge, SWR i revalidateTag w Next.js 15 z gotowymi przykładami na Fastly, Cloudflare i Vercel.
Inwalidacja cache w 2026 to proces selektywnego usuwania lub oznaczania jako nieaktualnych konkretnych odpowiedzi w pamięci podręcznej CDN i przeglądarki, zamiast czekać na wygaśnięcie TTL. Robi się to dziś głównie przez tagi (surrogate keys), nagłówek stale-while-revalidate oraz API natychmiastowego czyszczenia. W tym przewodniku pokazuję, jak skonfigurować tag-based purging na Fastly, Cloudflare i Vercel, kiedy używać soft purge zamiast hard, i jak skleić to z odświeżaniem ISR w Next.js 15, żeby TTFB pozostał poniżej 100 ms nawet po publikacji nowych treści.
Tag-based purging (surrogate keys) pozwala unieważnić tysiące powiązanych odpowiedzi jednym wywołaniem API, w czasie poniżej 150 ms na większych CDN-ach.
stale-while-revalidate serwuje stary content natychmiast, a w tle pobiera świeży. To zerowy wzrost TTFB przy aktualizacjach.
Soft purge oznacza obiekt jako stale zamiast go usuwać, dzięki czemu SWR nadal może go zwrócić podczas rewalidacji. Bezpieczniej dla origin.
Vercel Data Cache od 2025 łączy revalidateTag z propagacją do edge cache w okolicach 300 ms, eliminując potrzebę osobnego pingowania CDN-a.
Cache key zawierający fingerprint contentu (np. hash) likwiduje problem inwalidacji dla assetów statycznych: zmiana oznacza nowy URL.
Hierarchia origin, regional, edge wymaga przemyślanej kolejności purge'owania. Odwrotna kolejność powoduje thundering herd na origin.
Czym jest inwalidacja cache i dlaczego jest trudna
Inwalidacja cache to mechanizm, który informuje warstwę pamięci podręcznej, że konkretna odpowiedź jest już nieaktualna i należy ją pobrać ponownie z origin. Brzmi prosto, i właśnie dlatego krąży to stare powiedzenie, że są tylko dwa trudne problemy w informatyce: nazewnictwo, inwalidacja cache i błędy off-by-one. Szczerze, w moim doświadczeniu jako full-stack lead większość incydentów produkcyjnych ze "starym contentem" sprowadza się nie do braku TTL, lecz do braku spójnej strategii unieważniania. Frontend deploy ląduje, baza ma nowe dane, ale CDN trzyma stare HTML przez następne 24 godziny.
Klasyczne podejście, czyli krótki Cache-Control: max-age=60, wygląda kusząco, dopóki nie policzysz, że przy 50 mln requestów dziennie generuje 833 tys. trafień do origin na godzinę. To zabija TTFB i niweczy całą pracę z optymalizacji TTFB i edge computing. Lepsza droga to długie TTL (godziny lub dni) plus eksplicytne unieważnienie, gdy treść faktycznie się zmienia. Wymaga to jednak deklaratywnego modelu: każda odpowiedź dostaje tagi opisujące, jakie encje z bazy ją współtworzą, a deploy lub mutacja danych emituje purge tych tagów.
Tag-based purging i surrogate keys
Surrogate keys (znane też jako cache tags) to nagłówek wysyłany z origin do CDN, który grupuje odpowiedzi pod arbitralnymi etykietami. Gdy treść stojąca za etykietą się zmieni, jedno wywołanie API purge'uje wszystkie odpowiedzi z tym tagiem, niezależnie od tego, czy jest ich 10 czy 100 tys. Fastly nazywa to Surrogate-Key, Cloudflare używa Cache-Tag (dostępne w planach Enterprise), a Vercel wbudowuje tagi w Data Cache.
// Express.js: emisja surrogate keys dla strony produktu
app.get('/products/:id', async (req, res) => {
const product = await db.product.findUnique({ where: { id: req.params.id } });
const reviews = await db.review.findMany({ where: { productId: req.params.id } });
// Tagi opisujące, co składa się na tę odpowiedź
const tags = [
`product:${product.id}`,
`category:${product.categoryId}`,
`reviews:${product.id}`,
'layout:v3'
].join(' ');
res.set('Surrogate-Key', tags);
res.set('Cache-Control', 'public, max-age=0, s-maxage=86400, stale-while-revalidate=3600');
res.render('product', { product, reviews });
});
Gdy administrator zmieni opis produktu w CMS-ie, webhook wywołuje purge tagu product:1234, i ta jedna strona znika z cache na całym świecie w ciągu około 150 ms. Reszta katalogu, dzielona z tym samym TTL, pozostaje gorąca. To różnica między 99,5% a 99,98% hit ratio przy aktywnej redakcji.
Jak działa stale-while-revalidate?
Dyrektywa stale-while-revalidate=N w nagłówku Cache-Control mówi CDN-owi: po wygaśnięciu max-age możesz jeszcze przez N sekund zwrócić starą odpowiedź użytkownikowi, równolegle pobierając świeżą w tle. Z perspektywy użytkownika TTFB pozostaje równy czasowi serwowania z edge (5–20 ms), bo nikt nie czeka na origin. Świeże dane pojawiają się przy następnym żądaniu, typowo w ciągu jednej do dwóch sekund.
// Pełny nagłówek na potrzeby SWR z surrogate keys
Cache-Control: public, max-age=0, s-maxage=3600, stale-while-revalidate=86400
Surrogate-Key: article:1234 author:55 layout:v3
Surrogate-Control: max-age=604800
Kluczowy detal: s-maxage obowiązuje wyłącznie CDN, podczas gdy max-age=0 mówi przeglądarce, by nie cache'owała wcale. Surrogate-Control (rozumiany przez Fastly i Akamai) pozwala zadeklarować jeszcze dłuższy TTL po stronie edge bez wycieku tego do przeglądarki. W praktyce ustawiam s-maxage na godzinę, stale-while-revalidate na dobę i polegam na purge'u tagów. W razie nieoczekiwanego ruchu nadal otrzymuję świeży content w sensownym oknie, a przy mutacji danych invalidacja jest natychmiastowa.
Specyfikacja jest udokumentowana w RFC 5861 i wspierana przez wszystkich głównych dostawców CDN od 2023 roku. Brave i Safari dołączyły wsparcie po stronie przeglądarki w 2024, więc dyrektywa działa też dla cache'u prywatnego.
Soft purge vs hard purge: kiedy używać którego
Hard purge usuwa obiekt z cache natychmiast, więc kolejne żądanie musi pójść do origin i tam czekać na pełną odpowiedź. Soft purge oznacza obiekt jako stale, ale go nie wyrzuca, więc kolejne żądania nadal otrzymują starą wersję, dopóki rewalidacja w tle nie dostarczy nowej. Jeśli używasz SWR, soft purge to twój domyślny wybór.
Cecha
Hard purge
Soft purge
Następne żądanie
Czeka na origin
Otrzymuje stale + rewalidacja w tle
Wpływ na TTFB
Wzrost do czasu origin (200–800 ms)
Bez zmian (5–20 ms z edge)
Ryzyko thundering herd
Wysokie przy popularnych URL-ach
Niskie, coalescing pobrań
Gdy origin jest down
Błąd 5xx
Stary content nadal serwowany
Wymaga SWR w nagłówkach
Nie
Tak
Typowe użycie
Wrażliwe dane, GDPR, błędy treści
Standardowe aktualizacje contentu
W praktyce stosuję taką regułę: jeśli nowy content jest "lepszy, ale stary też akceptowalny", wybieram soft purge. Jeśli stary content jest błędny, naruszający prawa lub wprowadzający w błąd, idę z hard purge. Większość redakcyjnych edytów to pierwsza kategoria; jedynie korekty fact-checkowane lub żądania DSAR trafiają do drugiej. Soft purge w połączeniu z SWR praktycznie eliminuje też klasyczny problem cache stampede, w którym wygasające jednocześnie obiekty generują skok ruchu na origin.
Jak invalidować cache na CDN (Fastly, Cloudflare, Vercel)
Każdy dostawca ma własne API i własne limity rate. Poniżej najczęściej używane wywołania na produkcji. Wszystkie trzy obsługują tagi po stronie nagłówków, ale różnią się w detalu. Fastly ma najdojrzalsze API surrogate keys, Cloudflare wymaga Enterprise dla tagów, a Vercel integruje to z Next.js Data Cache.
Fastly: Instant Purge by surrogate key
// Purge tagu product:1234 globalnie, propagacja typowo < 150 ms
await fetch(`https://api.fastly.com/service/${SERVICE_ID}/purge`, {
method: 'POST',
headers: {
'Fastly-Key': process.env.FASTLY_TOKEN,
'Surrogate-Key': 'product:1234 reviews:1234',
'Fastly-Soft-Purge': '1' // soft purge, zachowuje obiekt jako stale
}
});
'use server';
import { revalidateTag, revalidatePath } from 'next/cache';
export async function updateProduct(id: string, data: ProductInput) {
await db.product.update({ where: { id }, data });
revalidateTag(`product:${id}`); // purge Data Cache + Edge Cache
revalidatePath(`/products/${id}`); // hard refresh dla SSG/ISR fallbacku
}
revalidateTag i revalidatePath w Next.js 15
Next.js 15, według oficjalnych release notes, przeniósł cache requestów fetch z domyślnie cache'owanego na uncached i wprowadził eksplicytne 'use cache' dla bardziej deterministycznego modelu. To zmienia sposób, w jaki łączymy tagi z odświeżaniem. Kluczowa para to cacheTag() w środku cache'owanej funkcji i revalidateTag() w Server Action po mutacji.
Po stronie mutacji wystarczy revalidateTag('product:1234') w Server Action. Vercel propaguje purge do Data Cache i Edge Cache w ramach jednego okrążenia (~300 ms według ich własnych metryk). Jeśli ten sam content jest też pre-renderowany jako trasa ISR, dorzucam revalidatePath(), żeby również HTML fallback dostał odświeżenie. Łączenie tego z cross-document View Transitions daje niemal natychmiastowy feedback wizualny przy edycji, bo użytkownik widzi nową wersję strony przy następnej nawigacji bez flasha białej strony. (W jednym z projektów e-commerce, nad którym pracowałem ostatnio, to złączenie ścięło percepcyjny czas aktualizacji koszyka z około 700 ms do progu nieodróżnialnego.)
Cache busting przez fingerprint URL
Dla zasobów statycznych, czyli JS, CSS, obrazów i fontów, inwalidacja jest trywialna, bo nigdy nie nadpisujemy istniejącego URL-a. Bundler emituje app.7f3a9d.js, deploy zmienia hash na app.b2c1f8.js, a HTML referencuje nową wersję. Stary plik nadal istnieje w cache, ale nic go nie żąda. Wystarczy Cache-Control: public, max-age=31536000, immutable i całkowicie eliminujemy temat inwalidacji dla assetów.
Wątek warty obejrzenia z perspektywy optymalizacji bundle'a JavaScript: jeśli używasz import maps, fingerprint należy umieścić w mapie, nie w samym module, bo inaczej tracisz korzyść z shared chunks między deployami. Vite, Next.js i Rspack robią to automatycznie; przy ręcznym setupie pamiętaj o tym przy migracji. Dla obrazów serwowanych przez transformer (Next/Image, Cloudflare Images) fingerprint kompensowany jest przez query string z parametrami transformacji, efekt jest ten sam.
Hierarchia cache i ochrona przed thundering herd
Nowoczesny stack to typowo trzy warstwy: przeglądarka, edge POP (Cloudflare/Fastly), i opcjonalnie regional shield bądź origin shield przed origin. Każda ma swój TTL i swój zestaw kluczy. Gdy purge'ujemy tag na edge, ale zapomnimy o shield, kolejny request z innego POP-u dostanie stary content z shield'a, i znów wycieknie do cache na edge. Spójna reguła: zawsze purge od shield/origin do edge, nigdy odwrotnie.
Drugi problem to thundering herd. Gdy popularny URL nagle wyleci z cache (hard purge), tysiące jednoczesnych żądań trafiają na origin. Trzy mechanizmy, których używam:
Request coalescing: Fastly i Cloudflare domyślnie scalają jednoczesne żądania do tego samego klucza w jeden upstream fetch. Dopilnuj, żeby Vary nie rozbijał klucza niepotrzebnie (np. nie waryjuj po User-Agent dla statycznego HTML).
Soft purge + SWR: opisany wyżej; origin dostaje tylko pojedyncze rewalidacje w tle.
Origin shield: jeden POP jako pośrednik między pozostałymi POP-ami a origin. Redukuje fan-out z N×POP do 1.
Po wdrożeniu shield plus coalescing na serwisie newsowym z 12 mln użytkowników dziennie origin RPS w szczytach z purge'a spadł z 8 tys. do około 40. Czternastokrotna poprawa bez zmiany w samym origin. Komplementarnie warto przeczytać też strategie cachowania w nowoczesnym webie, które pokrywają warstwy przeglądarki i Service Workerów.
Monitoring inwalidacji i hit ratio
Nie zarządzasz tym, czego nie mierzysz. Trzy metryki, które trzymam na dashboardzie:
Cache hit ratio per surrogate key prefix: pozwala wykryć tag, który jest purgowany zbyt często (np. globalny layout:v3 czyszczony przy każdym deployu).
Purge latency p50/p95/p99: od wywołania API do propagacji na wszystkie POP-y. Powyżej 500 ms p95 oznacza problem z konfiguracją API token rate limitów.
Stale serve ratio: odsetek odpowiedzi serwowanych z SWR. Niski (poniżej 1%) wskazuje na zbyt krótki stale-while-revalidate; bardzo wysoki (powyżej 30%) oznacza, że origin nie nadąża z rewalidacją.
Fastly i Cloudflare eksponują te dane przez logi w czasie rzeczywistym (Fastly Real-Time Log Streaming, Cloudflare Logpush). Dorzucam do tego nagłówek X-Cache w odpowiedzi, żeby debugowanie z DevTools było natychmiastowe. Specyfikację nagłówków cache opisuje MDN Cache-Control reference, warto trzymać ją otwartą podczas projektowania strategii.
Najczęściej zadawane pytania
Czym dokładnie różnią się surrogate keys od standardowych nagłówków HTTP cache?
Standardowe nagłówki (Cache-Control, ETag, Vary) sterują TTL i kluczem cache. Surrogate keys to dodatkowa warstwa nagłówków (Surrogate-Key, Cache-Tag), która istnieje wyłącznie między origin a CDN-em, a przeglądarka ich nie widzi. Pozwalają logicznie grupować odpowiedzi i unieważniać je hurtem, niezależnie od TTL.
Czy stale-while-revalidate działa również w przeglądarce, czy tylko na CDN?
Działa w obu miejscach. Cache-Control: max-age=60, stale-while-revalidate=600 jest respektowane przez Chrome, Edge, Safari (od 2024) i Firefox, a także przez wszystkie główne CDN-y. Różnica polega na tym, że s-maxage dotyczy tylko shared caches (CDN), pozwalając zróżnicować TTL między edge a przeglądarką.
Jak często mogę bezpiecznie purge'ować cache na Cloudflare?
Cloudflare Enterprise pozwala na około 30 tys. purge'ów po tagu na godzinę na zonę. Plany Pro i Business dają mniejsze limity (do 1000/godz.) i nie wspierają tagów. W praktyce dla redakcji średniej wielkości wystarczy poniżej 1000 purge'ów dziennie, więc limity nie są wąskim gardłem, jeśli grupujesz mutacje przez webhook.
Czy revalidateTag w Next.js 15 wymaga deploya na Vercel, czy działa też na własnym serwerze?
Sama funkcja revalidateTag działa wszędzie tam, gdzie Next.js zarządza Data Cache, czyli także w self-hosted setupie. Propagacja do CDN-a stojącego przed Next-em (np. Cloudflare) wymaga dodatkowo wywołania API tego CDN-a w tej samej Server Action. Vercel robi to automatycznie, w innych środowiskach trzeba dorzucić własny wrapper.
Co zrobić, gdy hit ratio nagle spadł po deployu?
Najczęstsze przyczyny: zmiana nagłówka Vary (rozbija klucze cache), nieumyślny purge tagu globalnego (np. layout użyty na wszystkich stronach), zmiana fingerprintu w URL-ach assetów bez aktualizacji immutable. Sprawdź w tej kolejności. Przy każdej z trzech naprawienie zajmuje minuty, nie godziny.
Praktyczny przewodnik po optymalizacji INP w 2026: scheduler.yield, dzielenie długich zadań, atrybucja LoAF i strategie pod 200 ms dla React 19, Vue i Svelte.
Cross-document View Transitions animują przejścia MPA bez SPA i frameworka. Przewodnik 2026: konfiguracja CSS, view-transition-name, Speculation Rules i wpływ na Core Web Vitals dla e-commerce.
Rozbij LCP w polu na cztery subparts (TTFB, Resource Load Delay/Duration, Element Render Delay) i napraw konkretną fazę. Atrybucja web-vitals.js v4, Element Timing API i CrUX BigQuery na realnych przykładach z 2026.