HTTP-välimuistin hallinta 2026: Cache-Control, stale-while-revalidate ja edge-strategiat
Miten hallita HTTP-välimuistia oikein 2026: Cache-Control-direktiivit, stale-while-revalidate ja CDN-invalidointi Cloudflaressa, Fastlyssa ja Vercelissä. Kaavat, koodit ja mittausohjeet.
HTTP-välimuistin hallinta tarkoittaa Cache-Control-otsikon, stale-while-revalidate-direktiivin ja CDN-tason invalidoinnin yhdistämistä siten, että selain ja reunasolmut palvelevat sisältöä ilman turhaa origin-käyntiä. Käytännössä oikea kaava on lähes aina public, max-age=0, s-maxage=31536000, stale-while-revalidate=86400 HTML:lle ja public, max-age=31536000, immutable hashatuille resursseille. Käydään tässä oppaassa läpi vuoden 2026 työkalut (RFC 9111, Cache-Status, Next.js 15 revalidateTag sekä Cloudflare-, Fastly- ja Vercel-erot), jotta TTFB tippuu ilman että käyttäjät näkevät vanhaa sisältöä.
RFC 9111 (2022) on nykyinen HTTP-välimuistin standardi ja korvaa RFC 7234:n. stale-while-revalidate tulee RFC 5861:sta ja on tuettu kaikissa isoissa CDN:issä vuonna 2026.
Käytä max-age selaimelle ja s-maxage jaetulle CDN-välimuistille. Nämä eivät ole sama asia, ja niiden erottaminen mahdollistaa "instant HTML + immutable assets" -kaavan.
stale-while-revalidate palvelee vanhaa sisältöä välittömästi ja päivittää taustalla, jolloin TTFB pysyy alle 50 ms myös cold cache -tilanteissa.
Reunavälimuistin invalidointi tag- tai surrogate-key-pohjaisesti (Cloudflare Cache Tags, Fastly Surrogate-Key, Vercel revalidateTag) on ainoa skaalautuva tapa hallita monimutkaista sisältöä.
Mittaa Cache-Status-otsikolla (RFC 9211) hit ratio, älä oleta. 90 %+ edge-hit-suhde on realistinen tavoite content-sivuille.
bfcache (selaimen back/forward-välimuisti) antaa lähes nollan LCP:n takaisin-navigoinnissa, mutta rikkoutuu no-store-otsikosta ja epäasianmukaisista unload-kuuntelijoista.
Mikä on HTTP-välimuisti ja miksi sillä on väliä 2026
HTTP-välimuisti on kerros palvelimesi ja käyttäjän välissä. Se tallentaa vastauksia ja palauttaa ne uudelleen ilman origin-pyyntöä. Kerros voi olla selain, service worker, yrityksen proxy, reunavälimuisti (CDN) tai kääntävä välipalvelin kuten Varnish. Kun kerrokset toimivat yhdessä, käyttäjän kokema latenssi tippuu satojen millisekuntien luokasta muutamiin, ja origin-palvelin selviää kymmenkertaisella liikenteellä.
Vuonna 2026 pelisäännöt tulevat RFC 9111:sta, joka päivitti HTTP/1.1-välimuistin määrittelyt vuonna 2022 ja on nyt kaikkien selainten ja CDN:ien lähtökohta. Käytännön muutokset RFC 7234:sta ovat pieniä mutta merkittäviä: Age-laskenta on tarkennettu, heuristic freshness on selkeämmin määritelty ja must-understand-direktiivi on lisätty. Kentältä havaittuna suurin muutos on kuitenkin Cache-Status-otsikko (RFC 9211), jota Cloudflare, Fastly ja Akamai lähettävät nyt oletuksena. Mittaaminen on vihdoin yksinkertaista.
Yhteys perf-metriikoihin on suora. Hyvin viritetty välimuisti kutistaa TTFB:n selaimen näkökulmasta lähelle nollaa (edge-hit) ja stabiloi LCP:tä, koska pääasiakirja saapuu välittömästi. Jos sinulla on vielä TTFB-ongelmia ennen välimuistiin siirtymistä, kannattaa lukea myös TTFB-optimoinnin käytännön opas. Välimuisti ei korjaa hidasta originia, se vain piilottaa sen.
Cache-Control-direktiivien pikakäsikirja
Cache-Control on ainoa otsikko, jonka tarvitset 95 % tapauksista. Expires, Pragma ja Last-Modified ovat legacy-kalustoa. RFC 9111 tukee niitä taaksepäinyhteensopivuuden vuoksi, mutta uutta koodia ei niille kirjoiteta. Alla ovat direktiivit, jotka pitää osata ulkoa:
max-age=N: kuinka monta sekuntia vastaus on tuore selaimen yksityisessä välimuistissa.
s-maxage=N: sama jaetuille välimuisteille (CDN, proxy). Ohittaa max-age-arvon jaetuissa cacheissa.
public / private: saako jaettu välimuisti tallentaa. Cookielliset ja henkilökohtaiset vastaukset merkitään private.
no-cache: saa tallentaa, mutta pitää validoida (ETag/Last-Modified) ennen käyttöä. Yleisin väärinymmärrys koko HTTP:ssä, koska tämä ei estä tallennusta.
no-store: ei saa tallentaa mihinkään. Käytä vain aidosti arkaluontoiselle datalle. Hajottaa myös bfcachen.
immutable: sisältö ei koskaan muutu tämän URLin takana. Selain ohittaa refresh-validoinnit F5:ää painettaessa. Käytä hashatuille asseteille.
stale-while-revalidate=N: palvelee vanhentunutta vastausta N sekuntia ja hakee tuoreen taustalla.
stale-if-error=N: sama, mutta vain kun origin epäonnistuu (5xx). Halpavakuutus outageille.
must-revalidate: vanhentunutta ei koskaan saa palvella. Käytä tapahtumasidonnaiselle datalle (varastosaldot, hinnat).
Huomaa max-age=0, s-maxage=3600-kombo HTML:lle. Selain hakee tuoreen HTML:n joka navigoinnilla, mutta CDN palvelee samaa vastausta tunnin. Käyttäjä saa muutokset heti kun purge tehdään, ja origin näkee vain 1/N pyynnöistä.
stale-while-revalidate: nopea sivu ilman vanhentumista
stale-while-revalidate (SWR) on määritelty RFC 5861:ssa, ja se on vuoteen 2026 mennessä laajimmin tuettu perf-työkalu jaettujen välimuistien joukossa. Idea on yksinkertainen. Kun objekti vanhentuu (max-age ylittyy), CDN palauttaa vanhentuneen version heti ja lähettää samalla taustalla revalidointipyynnön origiiniin. Käyttäjän TTFB pysyy edge-tasoisena, ja seuraava vierailija saa jo tuoreen version.
Kaadoin itse tämän kaavan viime keväänä uutispalveluun, jossa etusivun HTML oli aiemmin miss-vetoinen joka minuutti. SWR-siirron jälkeen origin näki alle 1 % liikenteestä. Käytännön esimerkki blogialustalla, jossa artikkelien HTML voi elää tunnin ennen refreshiä mutta tuoreus 24 h sisällä on ok:
0–3600 s: CDN palauttaa cached-vastauksen välittömästi. TTFB noin 15–30 ms.
3600–90 000 s: CDN palauttaa vanhentuneen vastauksen (~15 ms) ja lähettää revalidoinnin origiiniin taustalla. Käyttäjä ei koskaan odota.
Yli 90 000 s: CDN pakotetaan hakemaan tuore vastaus. Kylmä TTFB, mutta harvoin.
SWR yhdessä stale-if-error:n kanssa on paras yksittäinen investointi outage-kestävyyteen. Kun origin kaatuu, CDN palvelee viimeisintä versiota stale-if-error-ikkunan ajan. Sivustosi pysyy pystyssä vaikka backend on nurin.
ETag ja ehdolliset pyynnöt: 304 säästää kaistaa
Kun Cache-Control vanhentuu, selain ei aina lataa koko vastausta uudelleen. Jos vastauksessa oli ETag (tai Last-Modified), selain lähettää revalidointipyynnön If-None-Match-otsikolla, ja palvelin vastaa 304 Not Modified ilman bodya, jos sisältö ei ole muuttunut. Tämä säästää sekä kaistaa että LCP-aikaa, koska selain käyttää olemassa olevaa vastausta.
# Ensimmäinen pyyntö
GET /artikkeli/http-valimuisti HTTP/1.1
Host: webperfclinic.com
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "v42-a1b2c3d4"
Content-Length: 24817
...html body...
# Vanhentumisen jälkeen selain lähettää:
GET /artikkeli/http-valimuisti HTTP/1.1
Host: webperfclinic.com
If-None-Match: "v42-a1b2c3d4"
# Jos ei muuttunut:
HTTP/1.1 304 Not Modified
ETag: "v42-a1b2c3d4"
Cache-Control: public, max-age=3600
# ei bodya, selain käyttää välimuistin versiota
Generoi ETag sisällöstä (esim. SHA1(body).slice(0, 16)) tai sisällön versiosta (v${article.version}-${article.updatedAt}). Älä käytä satunnaista arvoa, se pilaa koko mekaniikan. Muista myös Vary-otsikko, jos vastaus riippuu Accept-Encoding:sta tai Accept-Language:sta. Muuten CDN palvelee brotli-vastauksen asiakkaalle, joka pyysi gzipiä (osunut tähän itse tuotannossa, ei suositella).
Reunavälimuisti ja monitasoinen cache Cloudflaressa, Fastlyssa ja Vercelissä
Yksitasoinen välimuisti on vuonna 2026 harvinaisuus. Nykyaikainen stack näyttää tältä: selain → service worker → CDN edge PoP → CDN regional tier → origin shield → origin. Jokainen kerros noudattaa samoja Cache-Control-sääntöjä, mutta niiden konfigurointi ja invalidointi eroaa oleellisesti.
Ominaisuus
Cloudflare
Fastly
Vercel
Invalidointimalli
Cache Tags + URL purge
Surrogate-Key + soft/hard purge
revalidateTag / revalidatePath
Tag-purge-latenssi
< 1 s globaalisti
< 150 ms globaalisti
< 300 ms edge-verkossa
SWR-tuki
Kyllä (natiivi)
Kyllä (VCL stale-while-revalidate)
Kyllä (ISR + fetch-tags)
Tiered cache
Argo / Tiered Cache
Shielding
Regional edge cache
Cache-Status-otsikko
Oletuksena päällä
Oletuksena päällä
x-vercel-cache
Hinta 1 TB / kk
~$0 (Free/Pro)
~$120
Sisältyy pro-planiin
Origin shield tarkoittaa yhtä valittua PoPia, joka toimii origin-palvelimen etunaamana. Kaikki muut PoPit menevät sen läpi. Vaikutus on iso. Jos sivustosi liikenne jakautuu 250 PoPin kesken, ilman shieldiä jokainen niistä voi lähettää cold-miss-pyynnön originiin. Shieldillä origin näkee korkeintaan yhden pyynnön tuoreelle sisällölle per objekti.
Kolmannen osapuolen skriptien osalta CDN-strategia on eri, koska et hallitse niitä. Ne kannattaa proxytata oman domainisi kautta. Käsittelin tätä syvemmin kolmannen osapuolen skriptien optimointi -oppaassa.
Cache-tunnisteet ja purge: hallittu invalidointi
URL-pohjainen purge (PURGE /artikkeli/x) on toiminut 15 vuotta ja toimii yhä, mutta se ei skaalaudu monimutkaisiin riippuvuuksiin. Kuvitellaan tilanne: päivität kirjoittajan nimen, ja jokainen artikkelisivu, kategoriasivu ja RSS-feed pitää invalidoida. URL-purge vaatisi tuhansia kutsuja. Tag-purge yhden.
Kaava on yksinkertainen. Palvelin liittää vastaukseen yhden tai useamman tagin, ja invalidointi viittaa tagiin. Cloudflaressa (Enterprise):
# Palvelin lähettää artikkelin
HTTP/1.1 200 OK
Cache-Control: public, s-maxage=86400
Cache-Tag: article-42, author-mateo, category-perf
# Invalidointi kun Mateo päivittää bion
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data '{"tags":["author-mateo"]}'
Fastlyssa sama Surrogate-Key-otsikolla ja fastly purge --surrogate-key. Vercelissä revalidateTag('author-mateo'), joka käyttää Vercelin oman fetchin liittämiä tageja.
Suunnitteluperiaate: liitä jokaiseen cache-tavaraan tagit, jotka kuvaavat sen riippuvuuksia. Yksi kutsu tageilla on halvempi ja luotettavampi kuin sadan URL-purgen batch-ajo.
Next.js 15 ISR ja revalidateTag käytännössä
Next.js 15 (marraskuu 2024, edelleen aktiivinen 2026) rakentaa ISR:n (Incremental Static Regeneration) suoraan HTTP-välimuistin päälle. Fetch-kutsuun liitetään tagit, ja server action tai webhook kutsuu revalidateTag:iä muutosten yhteydessä. Alla toimiva minimi:
Vercel-alustalla tämä toimii natiivisti globaalissa edge-cachessa. Itsehostatussa Next.js:ssä tarvitset @vercel/next-yhteensopivan runtimen tai Vercelin ISR-dokumentaation mukaisen mukautetun cache handlerin (Redis, Cloudflare KV, S3+CloudFront). Oma suositukseni itsehostatuille: Redis plus Cloudflare-etunaama. Silloin saat globaalin edge-cachen ilman Vercel-lukkiutumista.
bfcache ja Back/Forward-navigointi
bfcache on selaimen sisäinen välimuisti koko sivun tilalle (DOM, JS-heap, scroll-positio) navigoinnin yhteydessä. Kun käyttäjä painaa "takaisin", sivu palautuu välittömästi (LCP on käytännössä 0 ms). Chrome 96+ ja Safari 15+ tukevat sitä kaikilla laitteilla, ja Chrome UX Reportin 2026 mediaani back-cache-hit-rate on 60–70 %.
unload-tapahtumakuuntelija: deprecoitu, käytä pagehide-eventtiä sen tilalla.
Avoin IndexedDB-transaktio navigoinnin hetkellä.
Cache-Control: no-cache yksin ei estä bfcachea, mutta yhdistelmä no-cache, no-store estää.
Auditoi Chrome DevToolsissa: Application → Back/forward cache → Test. Chrome kertoo tarkasti, mikä syy esti tallennuksen. Vinkki myös INP:n kannalta: bfcache-hit ei laukaise LCP-mittausta uudelleen, joten Speculation Rules API ja bfcache yhdessä muodostavat "zero-latency-navigation"-parin.
Mittaaminen: Cache-Status-otsikko ja hit-suhteen seuranta
RFC 9211 määrittelee Cache-Status-otsikon, jonka jokainen kerros lisää itseään kuvaavan merkinnän. Esimerkkivastaus, joka on kulkenut edge- ja regional-cachen läpi:
Kolme näistä on välttämättömiä lukemaan: hit/miss/fwd=stale, ttl (aikaa tuoreena) ja Age (kuinka kauan objekti on ollut cachessa). RUM-työkalu ei näe näitä oletuksena, joten pitää tallentaa PerformanceResourceTiming-metriikoiden yhteyteen. Jos käytät web-vitals-kirjastoa kenttädatan keräämiseen, laajenna beacon-payloadia mukaan Cache-Status-arvolla parseroituna hit-suhteen segmentointia varten.
Staattiset assetit: edge-hit > 98 %, cold miss vain deploylla.
API: hit > 60 %, stale-served < 10 %.
Yleisimmät virheet ja miten välttää ne
Kentältä yleisimmät kolme, ja olen törmännyt jokaiseen henkilökohtaisesti tuotannossa:
Cache-Control: no-cache ymmärretään "älä tallenna" -direktiiviksi. Oikeasti se tarkoittaa "tallenna mutta validoi ennen käyttöä". Jos haluat estää tallennuksen, käytä no-store.
Cookielliset vastaukset merkitään public. Cloudflare oletuksena kunnioittaa cookieita eikä cachea, mutta Fastly ja mukautetut Varnish-konfiguraatiot voivat välttää tarkistuksen. Merkitse aina henkilökohtaiset vastaukset private.
Sama URL palvelee eri sisältöä ilman Vary-otsikkoa. Klassinen: mobiili- ja desktop-versiot samasta osoitteesta. Tarvitset Vary: User-Agent (varo cache-fragmentaatiota), tai parempi ratkaisu on erilliset URLit.
Neljäs yleinen virhe on yrittää välimuistittaa personoituja sivuja. Oikea kaava on edge-side compose: cachettu shell yhdistettynä edge-koottuun personoituun fragmenttiin. Cloudflare Workers, Fastly Compute@Edge ja Vercel Edge Middleware tarjoavat tähän valmiit mallit. Jos yrität cachea kokonaista personoitua sivua, joko tuhlaat välimuistin tilaa (per-käyttäjä-objektit) tai vuodat toisen käyttäjän dataa (hit väärälle sessiolle). Kumpikaan ei ole hauska.
Usein kysytyt kysymykset
Mikä on ero max-age ja s-maxage välillä?
max-age koskee yksityisiä välimuisteja (yksittäisen selaimen cache). s-maxage koskee jaettuja välimuisteja (CDN, proxy) ja ohittaa max-age-arvon jaetussa cachessa. Yleisin kaava on max-age=0, s-maxage=3600: selain hakee joka kerta, CDN palvelee tunnin.
Mitä stale-while-revalidate tekee käytännössä?
Kun cachettu vastaus vanhenee, CDN palauttaa vanhentuneen version välittömästi (~15 ms TTFB) ja käynnistää revalidointipyynnön origiiniin taustalla. Käyttäjä ei odota, ja seuraava vierailija saa tuoreen version. Se on paras yksittäinen työkalu TTFB-optimoinnissa vuonna 2026.
Onko immutable-direktiivi turvallista käyttää?
Kyllä, mutta vain URL-osoitteille, jotka sisältävät sisältöhashin (esim. app.a1b2c3.js). Jos sisältö muuttuu, deploy generoi uuden hashin ja siten uuden URL:n. Älä käytä immutable:a hashaamattomille URLeille, muuten käyttäjät jäävät jumiin vanhaan versioon kunnes he tyhjentävät välimuistin manuaalisesti.
Miten CDN-välimuisti tyhjennetään turvallisesti?
Käytä tag- tai surrogate-key-pohjaista invalidointia URL-purgen sijaan. Liitä jokaiseen vastaukseen tagit, jotka kuvaavat sen riippuvuuksia (esim. article-42, author-mateo), ja invalidoi tageilla. Vältä globaaleja tageja kuten all, joka aiheuttaisi thundering herd -tilanteen originiisi.
Miksi Cache-Control ei näytä toimivan Chromessa?
Yleisin syy on DevToolsin "Disable cache" -asetus päällä. Toinen: Vary-otsikko sisältää dynaamisen kentän kuten Vary: * tai Cookie. Kolmas: vastauksessa on Set-Cookie-otsikko, joka Chromen mukaan merkitsee "personoitua". Auditoi DevTools → Network → oikeaklikkaus → "Show cache related headers".
Kuinka pitkän max-age-arvon saan asettaa?
Hashatuille asseteille yksi vuosi (max-age=31536000) on standardi ja täysin turvallista immutable-flagin kanssa. HTML:lle pidä max-age=0 selaimessa ja käytä s-maxage+SWR-yhdistelmää CDN-kerroksessa. Erittäin harvoin muuttuva sisältö kuten kuvat voi käyttää viikkoja tai kuukausia.
Käytännön opas responsiivisiin kuviin vuonna 2026: srcset ja sizes oikein, AVIF/WebP-fallback picture-elementillä, lazy loading ja fetchpriority LCP:n parantamiseksi. Sisältää valmiit koodiesimerkit, kuvaputken ohjeet ja Lighthouse-tarkistuslistan.
Käytännön opas kolmannen osapuolen skriptien optimointiin 2026: mittaus LoAF-API:lla, facade-kuvio raskaille upotuksille, Partytown web workerissa ja bundle-budjetit CI:ssä. Sisältää valmiit koodinäytteet ja verkkokaupasta poimitut mittaustulokset.
Käytännön opas web-vitals-kirjaston käyttöön tuotannossa 2026: INP-, LCP-, CLS-, TTFB- ja FCP-mittaus, attribution-data, sendBeacon-lähetys ja oman p75-arvon vertailu CrUX-dataan.