- Vrealmatic
- Cloudflare
- Workers Cache
Cloudflare Workers Cache
Edge cache před vaším Workerem - zapnutí, pravidla cache, ochrana proti otrávení a purge
Praktický návod pro Next.js na OpenNext i pro jakýkoli jiný Worker: jak funguje cache před Workerem, kolik stojí, jaké hlavičky posílat, jak udržet HTML a RSC od sebe a jak mazat cache bez deploye. Každý příkaz a soubor zkopírujete jedním kliknutím.
Obsah návodu
00 · Přehled
Workers Cache v kostce
Workers Cache je cache, která stojí před vaším Cloudflare Workerem. Při zásahu (HIT) Cloudflare vrátí uloženou odpověď a váš kód se vůbec nespustí: žádný čas CPU, žádný studený start, žádná čtení z databáze. Zapíná se jedním řádkem ve wrangler.jsonc a co se uloží a na jak dlouho, rozhodují hlavičky odpovědi, které Worker posílá.
Celé nastavení ve zkratce
- Zapnutí -
"cache": { "enabled": true }a"workers_dev": falsevewrangler.jsonc - Pravidla v hlavičkách - malý
worker-entry.mjsoznačí vyrenderované stránky jako cachovatelné a vše ostatní jakono-store - Ochrana klíče cache - uložit jen odpověď, jakou by pod danou URL čekal běžný návštěvník (nikdy RSC payload pod adresou stránky)
- Purge a ověření - endpoint
POST /__cache-purgechráněný tokenem a pár kontrol hlavičkyCf-Cache-Statuspřescurl
Co potřebujete vyřešit?
| Otázka | Kde najdete odpověď |
|---|---|
| Vyplatí se Workers Cache pro můj projekt? Kolik stojí? | Jak to funguje → |
| Chci ji zapnout pro Worker s Next.js (OpenNext). | Zapnutí → |
| Jak dlouho mají stránky zůstat na edge a v prohlížeči? | Pravidla cache → |
| Návštěvníci místo stránky dostali surová RSC data. | Klíč cache a otrávení → |
| Změnil jsem obsah a web pořád ukazuje starou verzi. | Purge → |
| Co se stane s cache po deployi nebo rollbacku? | Deploy a rollback → |
| Jak ověřím, že to funguje? | Ověření → |
| Dnes cachujeme přes caches.default ve Workeru. | Přechod z Cache API → |
Workers Cache, jak ji Cloudflare spustil v červenci 2026, Wrangler 4.69 nebo novější, stav k říjnu 2026. Příklady jsou pro Next.js na OpenNext, protože tam jsou největší pasti (RSC payloady, s-maxage). U jiného Workeru část o RSC přeskočte, zbytek platí beze změny. Nastavení běží v produkci na našich vlastních webech.
01 · Základy
Jak Workers Cache funguje a kolik stojí
Bez Workers Cache spustí každý request váš Worker. I vlastní cache přes Cache API (caches.default) pomůže až poté, co se Worker spustí. Workers Cache odpovídá dřív než Worker, z cache sdílené celou sítí Cloudflare.
Dvě vrstvy a slučování požadavků
- Nižší vrstva v datacentru (colo) blízko návštěvníka, vyšší vrstva sdílená celou sítí. První render kdekoli na světě naplní vyšší vrstvu, takže ostatní datacentra dostanou HIT bez dalšího renderu.
- Slučování požadavků. Když na stejnou necachovanou URL přijde naráz mnoho požadavků, Cloudflare spustí Worker jednou za datacentrum a výsledek vrátí všem.
- stale-while-revalidate. Po vypršení dostane další návštěvník uloženou kopii okamžitě a Worker na pozadí vyrenderuje novou. Nikdo nečeká na render.
- Naměřeno na produkčním webu s Next.js: HIT kolem 0,05 s, MISS kolem 0,4 s.
Za co se platí
| Požadavek | Účtuje se | Poznámka |
|---|---|---|
HIT | jen request | Worker neběží, žádný čas CPU |
MISS, BYPASS | request + CPU | stejně jako bez cache |
| Statické soubory (JS, CSS, obrázky) | request | jinak zdarma; se zapnutou cache se účtuje každý request na Worker, včetně /_next/static |
| Volání mezi Workery | request | účtují se i service bindings a ctx.exports |
| Purge, úložiště | nic navíc | Workers Cache nemá samostatnou cenu |
Stránka, která ze stejného Workeru načte 20 souborů, znamená 21 účtovaných requestů (na placeném plánu $0,30 za milion nad limit plánu, na Free plánu se počítají do denního limitu). Úspora CPU za ušetřené rendery to obvykle mnohonásobně vyváží, ale první týdny kontrolujte billing. Velká média a soubory ke stažení je lepší servírovat z veřejného R2 bucketu nebo ze samostatného Workeru bez cache.
Přínosy a nevýhody
Přínosy
- Vyšší hit rate po celém světě: jeden render naplní cache pro všechna datacentra.
- HIT nespouští žádný kód: žádné CPU, žádný studený start, méně čtení z D1 a R2.
- stale-while-revalidate a stale-if-error: po vypršení se nečeká a při chybě renderu se vrátí uložená kopie.
- Purge podle tagu nebo cesty přímo z Workeru, bez deploye.
- Méně vlastního kódu než ruční vrstva nad Cache API.
Nevýhody
- Requesty statických souborů se účtují.
- Klíč cache neobsahuje host, cookies ani hlavičky požadavku. Ochranu si musíte udělat sami.
- Při HIT nevznikají logy Workeru. Stav cache ukazuje jen hlavička Cf-Cache-Status a Workers Observability.
- Nejde předehřát: každý deploy začíná s prázdnou cache.
- OpenNext zatím nemá oficiální integraci, hlavičky nastavuje malý wrapper.
Kdy se nehodí
- Stránky se liší podle přihlášeného uživatele a nejde je oddělit cestou (cookies nejsou součástí klíče).
- Většinu provozu tvoří statické soubory ze stejného Workeru a blížíte se limitu requestů na Free plánu.
- Obsah se musí změnit během sekund a po každé změně nemáte jak zavolat purge.
02 · Zapnutí
Zapnutí Workers Cache krok za krokem
Pět kroků pro existující aplikaci Next.js na OpenNext. Wrapper worker-entry.mjs stojí mezi Cloudflare a Workerem OpenNext. Mění jen hlavičky odpovědi, takže na tom, jak aplikace renderuje, nic nemění.
Krok 1: Aktualizujte Wrangler
Workers Cache vyžaduje Wrangler 4.69.0 nebo novější (4.107.0 pro nastavení po entrypointech a cross_version_cache).
npm install -D wrangler@latest
npx wrangler --version # 4.69.0 or newerKrok 2: Zapněte cache ve wrangler.jsonc
Nasměrujte main na wrapper a zapněte cache. Zbytek konfigurace (bindings, D1, R2) nechte, jak je.
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-app",
// instead of ".open-next/worker.js": the wrapper sets the cache headers
"main": "./worker-entry.mjs",
"compatibility_date": "2026-07-01",
"compatibility_flags": ["nodejs_compat"],
"assets": { "directory": ".open-next/assets", "binding": "ASSETS" },
// Workers Cache: Cloudflare answers from the cache before the worker runs
"cache": { "enabled": true },
// The cache key has no host: *.workers.dev would share (and fill) the same cache
"workers_dev": false
}workers_dev: false je důležité: klíč cache neobsahuje host, takže adresa *.workers.dev by servírovala a plnila stejnou cache a přesměrování uvnitř Workeru by se při HIT nikdy nespustilo.
Krok 3: Přidejte worker-entry.mjs
Vytvořte soubor v kořeni projektu a do SITE_HOST dejte produkční host. Jednotlivé části vysvětlují kapitoly Pravidla cache a Klíč cache.
// worker-entry.mjs - cache policy for Workers Cache in front of an OpenNext (Next.js) worker.
// Workers Cache answers BEFORE this code runs. This file only decides, through response
// headers, what the edge may store. Anything not explicitly allowed gets no-store.
import openNextWorker from "./.open-next/worker.js";
// export { DOQueueHandler } from "./.open-next/worker.js"; // only if you use the DO queue
const SITE_HOST = "www.example.com"; // the only host whose pages may be stored
const NEVER_CACHE = ["/api/"]; // path prefixes that are never stored
// Edge: 7 days fresh, then 1 more day stale-while-revalidate / stale-if-error.
// Use max-age, not s-maxage: s-maxage and must-revalidate switch stale-while-revalidate off.
const EDGE_PAGE_CC = "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400";
// Browser: 10 minutes. The edge copy can be purged at any time, the browser copy cannot.
const CLIENT_PAGE_CC = "public, max-age=600";
const NO_STORE = "no-store";
// Edge-only header: highest precedence, stripped before the response reaches the browser.
const EDGE_CC = "Cloudflare-CDN-Cache-Control";
// Headers the Next.js router sends with RSC requests (client-side navigation, prefetch).
const ROUTER_HEADERS = ["rsc", "next-router-state-tree", "next-router-prefetch", "next-router-segment-prefetch", "next-url"];
/* ---------- canonical variant guard ----------
* The cache key is path + query only. If an RSC payload is ever stored under a page URL,
* every visitor gets raw RSC instead of HTML until the entry expires. So only the response
* a normal visitor expects under that URL may be stored:
* HTML: no _rsc parameter and no router headers,
* RSC: "RSC: 1" and _rsc equal to the hash Next.js derives from the router headers
* (next/dist/shared/lib/router/utils/cache-busting-search-param.js).
* If Next.js ever changes the formula, RSC simply stops being cached (fail-safe).
*/
function djb2(str) {
let hash = 5381;
for (let i = 0; i < str.length; i++) hash = ((hash << 5) + hash + str.charCodeAt(i)) & 0xffffffff;
return hash >>> 0;
}
function expectedRscParam(req) {
const h = req.headers;
const p = h.get("next-router-prefetch");
const prefetch = p === "1" || p === "2" ? p : undefined;
const segment = h.get("next-router-segment-prefetch") || undefined;
const tree = h.get("next-router-state-tree") ?? undefined;
const nextUrl = h.get("next-url") ?? undefined;
if ([prefetch, segment, tree, nextUrl].every((v) => v === undefined)) return "";
return djb2([prefetch || "0", segment || "0", tree || "0", nextUrl || "0"].join(",")).toString(36).slice(0, 5);
}
function isCanonicalPage(req, url, type) {
const rsc = url.searchParams.get("_rsc"); // null = parameter missing
if (type.startsWith("text/html")) {
return rsc === null && !ROUTER_HEADERS.some((name) => req.headers.has(name));
}
if (type.startsWith("text/x-component")) {
return req.headers.get("rsc") === "1" && rsc !== null && rsc === expectedRscParam(req);
}
return false;
}
/* ---------- request passed to Next.js on a MISS ----------
* The cache key ignores cookies and Authorization, so always render the anonymous variant.
* Conditional headers are dropped so the origin returns a full 200 for the edge to store
* (the edge answers If-None-Match with 304 by itself). Router headers stay: Next.js needs
* them to build the right RSC payload.
*/
function upstreamRequest(request, url) {
if (request.method !== "GET" || NEVER_CACHE.some((p) => url.pathname.startsWith(p))) return request;
const h = new Headers(request.headers);
for (const name of ["Cookie", "Authorization", "If-None-Match", "If-Modified-Since"]) h.delete(name);
return new Request(request, { headers: h });
}
/* ---------- response headers = cache policy ---------- */
function noStore(h) {
h.set(EDGE_CC, NO_STORE);
h.set("Cache-Control", NO_STORE); // also stops other shared caches from keeping Next's s-maxage
}
// Next.js on Workers returns uncompressed bodies and Cloudflare compresses at the edge,
// so Vary: Accept-Encoding would only split the cache into identical variants.
function dropAcceptEncodingVary(h) {
if (h.has("Content-Encoding")) return;
const vary = (h.get("Vary") || "")
.split(",")
.map((v) => v.trim())
.filter((v) => v && v.toLowerCase() !== "accept-encoding");
if (vary.length) h.set("Vary", vary.join(", "));
else h.delete("Vary");
}
function applyCachePolicy(request, url, resp) {
const out = new Response(resp.body, resp); // mutable copy
const h = out.headers;
const type = (h.get("Content-Type") || "").toLowerCase();
const isPage = type.startsWith("text/html") || type.startsWith("text/x-component");
const cacheableRequest =
request.method === "GET" &&
url.hostname === SITE_HOST &&
!NEVER_CACHE.some((p) => url.pathname.startsWith(p));
if (isPage) {
const originCc = (h.get("Cache-Control") || "").toLowerCase();
const store =
cacheableRequest &&
out.status === 200 &&
!h.has("Set-Cookie") &&
!originCc.includes("private") &&
!originCc.includes("no-store") &&
isCanonicalPage(request, url, type);
if (!store) {
noStore(h);
return out;
}
h.set(EDGE_CC, EDGE_PAGE_CC);
h.set("Cache-Control", CLIENT_PAGE_CC);
h.set("Cache-Tag", type.startsWith("text/html") ? "html" : "rsc"); // for targeted purge, not sent to the browser
dropAcceptEncodingVary(h);
return out;
}
// Everything else (API, sitemaps, redirects, errors). Without an explicit Cache-Control,
// Workers Cache applies heuristic TTLs (200 for 2 h, 301 for 20 min, 404 for 3 min).
if (!cacheableRequest || out.status >= 400 || !h.has("Cache-Control")) noStore(h);
return out;
}
/* ---------- POST /__cache-purge (token in CACHE_PURGE_TOKEN) ---------- */
async function sha256(text) {
return new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)));
}
async function tokenMatches(request, expected) {
const auth = request.headers.get("Authorization") || "";
const given = auth.startsWith("Bearer ") ? auth.slice(7) : "";
// Hash both sides first: equal lengths, so the comparison runs in constant time.
const [a, b] = await Promise.all([sha256(given), sha256(expected)]);
return crypto.subtle.timingSafeEqual(a, b);
}
async function handlePurge(request, env, ctx) {
const headers = { "Cache-Control": NO_STORE, [EDGE_CC]: NO_STORE };
if (!env.CACHE_PURGE_TOKEN || request.method !== "POST") return new Response("Not Found", { status: 404, headers });
if (!(await tokenMatches(request, env.CACHE_PURGE_TOKEN))) return new Response("Unauthorized", { status: 401, headers });
const body = await request.json().catch(() => ({}));
const scope = {};
if (Array.isArray(body.tags) && body.tags.length) scope.tags = body.tags;
if (Array.isArray(body.pathPrefixes) && body.pathPrefixes.length) scope.pathPrefixes = body.pathPrefixes;
const result = await ctx.cache.purge(Object.keys(scope).length ? scope : { purgeEverything: true });
return Response.json(result, { status: result.success ? 200 : 500, headers });
}
/* ---------- entry ---------- */
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (url.pathname === "/__cache-purge") return handlePurge(request, env, ctx);
const resp = await openNextWorker.fetch(upstreamRequest(request, url), env, ctx);
return applyCachePolicy(request, url, resp);
},
};Používáte cookies pro přihlášené uživatele? Pak pro tyto cesty Cookie neodstraňujte: přidejte je do NEVER_CACHE.
Krok 4: Vytvořte token pro purge
Bez secretu CACHE_PURGE_TOKEN endpoint pro purge neexistuje (vrací 404). Spusťte ve složce projektu (Linux, macOS nebo WSL):
# generate a random token into .env (.env must be in .gitignore)
echo "CACHE_PURGE_TOKEN=$(openssl rand -hex 32)" >> .env
# store the same value as a worker secret
grep '^CACHE_PURGE_TOKEN=' .env | cut -d= -f2 | npx wrangler secret put CACHE_PURGE_TOKENKrok 5: Build a deploy
npx opennextjs-cloudflare build
npx opennextjs-cloudflare deployPak projděte kontroly v kapitole Ověření. První request na stránku je MISS, druhý HIT.
Přesměrování napsané ve Workeru (třeba example.com → www.example.com) se při HIT přeskočí, protože host není součástí klíče. Použijte místo něj Redirect Rule zóny v dashboardu Cloudflare. Ta běží před Workers Cache.
03 · Pravidla cache
Co cachovat a jak dlouho
Cache Rules, Page Rules ani Cache Reserve z nastavení zóny se na Workers Cache neuplatňují. Co se uloží a na jak dlouho, rozhodují výhradně hlavičky odpovědi, kterou Worker vrátí.
Doporučená pravidla
| Cesta / odpověď | Edge (Cloudflare) | Prohlížeč |
|---|---|---|
| HTML stránka, kanonický request | 7 dní + 1 den SWR + 1 den stale-if-error | 10 min |
| RSC payload od routeru, kanonický request | 7 dní + 1 den SWR + 1 den stale-if-error | 10 min |
| HTML nebo RSC, které nejsou kanonické | neukládá se | no-store |
| 404 a jiné ne-200 stránky | neukládá se | no-store |
/api/* | neukládá se | no-store |
| Ostatní odpovědi (sitemapy, přesměrování) | podle vlastního Cache-Control, bez něj se neukládá | podle Next.js |
| Jiný host než produkční | neukládá se | - |
| Jiná metoda než GET | neukládá se | - |
Dvě hlavičky: edge a prohlížeč
Cloudflare čte pravidla cache ze tří hlaviček v tomto pořadí priority: Cloudflare-CDN-Cache-Control, CDN-Cache-Control a Cache-Control. První z nich se před odesláním k návštěvníkovi odstraní. Díky tomu může edge držet stránku 7 dní a prohlížeč jen 10 minut.
Cloudflare-CDN-Cache-Control: max-age=604800, stale-while-revalidate=86400, stale-if-error=86400
Cache-Control: public, max-age=600
Cache-Tag: htmlCloudflare-CDN-Cache-Control- jen pro edge, nejvyšší priorita, návštěvník ji nevidí.Cache-Control- jde k návštěvníkovi.Cache-Tag- štítky pro cílený purge (html,rsc), návštěvník je také nevidí.
Život jedné stránky na edge
S ukázkovými hodnotami prochází každá stránka na edge třemi fázemi:
- 0-7 dní - čerstvá. Servíruje se z cache, Worker neběží.
- 7-8 dní - stará, ale použitelná. SWR: první návštěvník dostane starou kopii okamžitě a na pozadí se stránka vyrenderuje znovu. stale-if-error: když render selže (databáze neodpovídá, 5xx), vrátí se uložená kopie.
- Po 8 dnech - zahozená. Další návštěva čeká na render.
Proč jen 10 minut v prohlížeči
Kopii na edge jde kdykoli zahodit (deploy, rollback, purge), kopii v prohlížeči ne. Když se ven dostane rozbitá stránka, u návštěvníka vydrží nejvýš 10 minut.
Při HIT posílá Cloudflare i hlavičku Age (jak dlouho je kopie v cache) a prohlížeč si ji odečte od max-age. Kopie starší než 10 minut se proto při další návštěvě ověří a odpoví se 304 Not Modified přímo z edge (kolem 50 ms, bez Workeru). Je to levné a v pořádku.
Pasti v hlavičkách
| Past | Co se stane | Co dělat |
|---|---|---|
Chybí Cache-Control | Uloží se i tak podle heuristiky: 200 na 2 hodiny, 301 na 20 minut, 404 na 3 minuty. | Vše, co se cachovat nesmí, musí mít explicitně no-store. |
s-maxage, must-revalidate, proxy-revalidate | Vypnou stale-while-revalidate. Vypršená stránka čeká na render. | Next.js posílá s-maxage u statických stránek. Pravidlo pro edge nastavte přes max-age v Cloudflare-CDN-Cache-Control. |
Set-Cookie v odpovědi | Automatický BYPASS, Worker běží pokaždé. | Na veřejných stránkách cookies nenastavujte (lišta souhlasu, A/B testy řešte na klientovi). |
Authorization v požadavku | BYPASS, pokud odpověď není public. Pak se uloží a sdílí. | Před renderem Authorization odstraňte (ukázka to dělá), uložená kopie je pak vždy anonymní. |
Vary: Accept-Encoding | Rozdělí cache na stejné varianty podle prohlížeče. | Next.js na Workers vrací nekomprimované tělo a komprimuje až edge, proto wrapper odstraní accept-encoding z Vary. |
- Na edge dlouho, v prohlížeči krátce: edge jde vyčistit, prohlížeč ne.
- Pravidlo pro edge v
Cloudflare-CDN-Cache-Controlsmax-age, nikdys-maxage. - Bez hlavičky neznamená bez cache. Dynamické odpovědi označte
no-store.
04 · Klíč cache
Klíč cache a otrávení cache
Z pohledu bezpečnosti nejdůležitější kapitola. Klíč cache je mnohem jednodušší než u klasické CDN. Když dvě různé odpovědi sdílejí jednu URL, cache je může zaměnit a k rozbití stránky pro všechny stačí jeden obyčejný request.
Co je v klíči
| V klíči je | V klíči není |
|---|---|
| cesta + query string (záleží na pořadí parametrů) | host |
verze Workeru: každý deploy, rollback i wrangler secret put začíná s prázdnou cache | cookies |
| cílový entrypoint Workeru | Authorization ani jiné hlavičky požadavku |
metoda: GET a HEAD sdílí jednu položku |
Vary se nespoléhejteDokumentace uvádí, že Workers Cache respektuje všechny názvy v hlavičce Vary. V praxi se RSC payload pod adresu stránky uložil, přestože Next.js posílá Vary: rsc, next-router-state-tree, …. Variantu musí určovat samotná URL.
Co se stalo: RSC pod adresou stránky
Při ladění šel na produkci request s hlavičkou RSC: 1 na holou adresu stránky, bez ?_rsc=. Next.js správně vrátil RSC payload (text/x-component). Workers Cache ho uložil pod adresu stránky a návštěvníci po celou dobu TTL dostávali místo stránky surový text payloadu.
Stačil k tomu jeden obyčejný request s jednou hlavičkou. Cache obě odpovědi nerozliší, protože mají stejnou URL, a tedy i stejný klíč.
Ochrana: ukládat jen kanonickou odpověď
Odpověď uložte jen tehdy, když je přesně taková, jakou by pod danou URL čekal běžný návštěvník (isCanonicalPage v ukázce):
- HTML jen pro request bez
_rsca bez jakékoli hlavičky routeru (rsc,next-router-state-tree,next-router-prefetch,next-router-segment-prefetch,next-url). - RSC jen s
RSC: 1a s hodnotou_rsc, která přesně odpovídá hashi hlaviček routeru. Next.js ho počítá jako djb2 zprefetch,segmentPrefetch,stateTree,nextUrl, v base36, prvních 5 znaků. Next.js tuhle kontrolu sám má (experimental.validateRSCRequestHeaders), ale pod OpenNext neběží, proto ji dělá wrapper. - A navíc jen: produkční host,
GET, status 200, bezSet-Cookie, bezprivatečino-storeod originu.
Všechno ostatní se návštěvníkovi obslouží normálně, jen s no-store v obou hlavičkách. Druhá z nich (Cache-Control) brání tomu, aby s-maxage z Next.js podržela jiná sdílená cache po cestě.
Výsledek: request z incidentu se obslouží jako BYPASS a HTML pod stejnou URL zůstane nedotčené. Podvržený hash _rsc se také neuloží.
Testy, které by mohly cache otrávit, pouštějte na URL s unikátním parametrem (?cachetest=<náhodné číslo>). Ta má vlastní klíč a skutečné stránky neovlivní.
05 · Purge
Purge bez deploye
Purge v dashboardu Cloudflare (ani přes API zóny či Terraform) na Workers Cache nedosáhne. Mazat jde jen z Workeru přes ctx.cache.purge(), a to výhradně cache tohoto Workeru. Ukázka ho zpřístupňuje jako endpoint chráněný tokenem.
Endpoint POST /__cache-purge
export CACHE_PURGE_TOKEN=$(grep '^CACHE_PURGE_TOKEN=' .env | cut -d= -f2)
# everything
curl -X POST https://www.example.com/__cache-purge \
-H "Authorization: Bearer $CACHE_PURGE_TOKEN"
# by path prefix (the page and everything below it)
curl -X POST https://www.example.com/__cache-purge \
-H "Authorization: Bearer $CACHE_PURGE_TOKEN" \
-d '{"pathPrefixes":["/blog"]}'
# by tag: only RSC payloads (html = documents)
curl -X POST https://www.example.com/__cache-purge \
-H "Authorization: Bearer $CACHE_PURGE_TOKEN" \
-d '{"tags":["rsc"]}'- Bez těla smaže vše,
pathPrefixessmaže stránku a vše pod ní,tagsmaže podleCache-Tag. - Bez tokenu nebo se špatným tokenem:
401. NaGET:404. Bez secretu endpoint neexistuje (404). - Token se porovnává v konstantním čase (obě strany přes SHA-256, pak
timingSafeEqual). - Purge má limit počtu volání podle Free plánu purge zóny, a to na každém plánu. Volejte ho jednou po dávce změn, ne po každé změně.
Změny obsahu
Změny v databázi (nové články, úpravy, pořadí) se samy projeví až po vypršení kopie na edge, tedy do 7 dní (v krajním případě 8). Po dávce změn zavolejte purge: celý, nebo jen dotčené pathPrefixes. Nejlepší místo je akce uložení ve vašem CMS nebo administraci.
Další způsoby, jak cache vyprázdnit
- Deploy nebo rollback: nová verze znamená nový klíč.
wrangler secret puttaké vytvoří novou verzi, cache tedy začne prázdná.
Jen vyprázdnit cache. Nečte data a nemění obsah. Opakované mazání by cache drželo prázdnou: vyšší CPU, vyšší účet, pomalejší web, případně vyčerpaný limit purge. Řešením je token vyměnit. Nejdřív z .env smažte starý řádek, pak znovu spusťte krok 4 z kapitoly Zapnutí.
06 · Deploy
Deploy, rollback a staré chunky
Nastavení cache je součástí verze Workeru. Deploy platí okamžitě a nikdy nevrátí HTML, které odkazuje na staré chunky, hit rate ale pokaždé začíná od nuly.
Změny wrapperu nepotřebují rebuild
Build OpenNext vyrábí jen .open-next/, worker-entry.mjs sbalí až Wrangler při deployi. Změny ve wrapperu nebo ve wrangler.jsonc proto stačí nasadit. Rebuild je potřeba jen po změnách v src/, next.config.ts a podobně.
# only worker-entry.mjs or wrangler.jsonc changed: no rebuild needed
npx opennextjs-cloudflare deployRollback
npx wrangler rollback # previous version, including its cache settings- Rollback vrátí i nastavení
cachedané verze. "enabled": falsecache nevypne zpětně: uložené položky nesmaže a použije je znovu, pokud se cache zase zapne.
cross_version_cache nechte vypnuté
cross_version_cache: true drží zahřátou cache přes deploye. Stará HTML by ale odkazovala na soubory v _next/static, které nový build už nemusí mít, a stránka spadne s chybou ChunkLoadError. Pokud ho přesto zapnete, po každém deployi, který mění obsah, zavolejte purge.
Staré buildy v .open-next/assets/_next/static držte v každém případě. Prohlížeče mají HTML až 10 minut a otevřené záložky donačítají chunky ze své verze. Viz slučování starých chunků _next/static.
07 · Ověření
Jak ověřit, že to funguje
Při HIT nevznikají logy Workeru, hlavním nástrojem je proto hlavička odpovědi Cf-Cache-Status. Hit ratio a počty HIT / MISS / BYPASS ukazuje i Workers Observability.
Kontroly po deployi
# GET twice (not curl -I: only GET responses are stored)
curl -s -o /dev/null -D - https://www.example.com/ | grep -i -E "cf-cache-status|^cache-control"
curl -s -o /dev/null -D - https://www.example.com/ | grep -i -E "cf-cache-status|^cache-control"# a unique URL of its own, so a failed test cannot poison a real page
T="https://www.example.com/?cachetest=$RANDOM"
curl -s -o /dev/null -D - -H "RSC: 1" "$T" | grep -i -E "cf-cache-status|content-type" # BYPASS, text/x-component
curl -s -o /dev/null -D - "$T" | grep -i -E "cf-cache-status|content-type" # text/html| Kontrola | Jak | Očekávání |
|---|---|---|
| Stránka se cachuje | dvakrát GET na stejnou URL | MISS, pak HIT; Cache-Control: public, max-age=600 |
| Ochrana proti otrávení | RSC: 1 na holou URL, pak stejná URL bez hlavičky | nejdřív BYPASS + text/x-component, pak text/html |
| API se necachuje | libovolná URL /api/… | BYPASS, Cache-Control: no-store |
| 404 se necachuje | neexistující URL | BYPASS, no-store |
| Revalidace | If-None-Match: "<etag>" na cachovanou stránku | 304, HIT |
| Interní hlavičky neunikají | hlavičky libovolné stránky | bez Cloudflare-CDN-Cache-Control a Cache-Tag |
| Klientská navigace | proklikat web s otevřenými DevTools | bez chyb v konzoli |
Hodnoty Cf-Cache-Status
| Hodnota | Význam |
|---|---|
HIT | Vráceno z cache, Worker neběžel. |
MISS | V cache nebylo: Worker běžel a odpověď se uložila (pokud to pravidla dovolí). |
EXPIRED | Kopie vypršela a vyrenderovala se znovu. |
REVALIDATED | Vypršená kopie byla potvrzena jako stále platná. |
UPDATING | stale-while-revalidate: vrátila se stará kopie a nová se renderuje na pozadí. |
STALE | Vrátila se stará kopie, protože render selhal (stale-if-error). |
BYPASS | Neukládá se: no-store, Set-Cookie, Authorization nebo jiná metoda než GET. |
08 · Přechod
Z vlastní vrstvy nad Cache API na Workers Cache
Před Workers Cache se vyrenderované stránky běžně cachovaly wrapperem, který HTML a RSC ukládal do caches.default (Cache API). Pokud takové řešení dnes provozujete, přechod znamená hlavně mazání kódu.
Cache API (caches.default ve wrapperu) | Workers Cache | |
|---|---|---|
| Kde cache je | uvnitř Workeru, který se spouští při každém requestu | před Workerem, při HIT Worker neběží |
| Dosah | jedno datacentrum, každé se plní zvlášť | nižší vrstva + vyšší vrstva sdílená celou sítí |
| Klíč | vlastní (hash z _rsc, hlaviček routeru, id deploye) | cesta + query + verze Workeru |
| Po vypršení | první návštěvník čeká na render | SWR: stará kopie hned, obnova na pozadí |
| Purge | napříč datacentry nejde, jen čekat na TTL | ctx.cache.purge() |
| Po deployi | studená cache (id deploye v klíči) | studená cache (verze v klíči) |
| Platba při zásahu | request + CPU | jen request |
Přechod ve čtyřech krocích
- Z
worker-entry.mjsodstraňte dotazy docaches.default, vlastní klíče, hashování a přepisováníVary. - Zapněte cache ve
wrangler.jsonc(krok 2). - Pravidla cache nastavte v hlavičkách odpovědi, včetně ochrany variant (krok 3).
- Ověřujte přes
Cf-Cache-Statusmísto vlastní hlavičky typuX-Edge-Cache(Ověření).
Pro cachování dat uvnitř Workeru, například lokální vrstvu v datacentru před R2 bucketem, zůstává Cache API správným nástrojem. Viz R2 storage a cache (anglicky).
Zdroje
Oficiální dokumentace a další čtení
Návod vychází z dokumentace Cloudflare a z našeho produkčního nastavení:
- Your Worker can now have its own cache in front of itOznámení Cloudflare (červenec 2026, anglicky).
- Workers CachePřehled, vrstvy, slučování požadavků a cena.
- Konfiguracewrangler.jsonc, priorita hlaviček, heuristické TTL, pravidla bypassu.
- Klíče cacheCo součástí klíče je a co ne.
- Purgectx.cache.purge(), tagy, prefixy cest a limity.
Související
- Next.js na CloudflareStatický build, Worker OpenNext, D1, R2 a incremental cache (anglicky).
- Cloudflare wikiAPI tokeny, deploy přes token, R2 z příkazové řádky (anglicky).
Jak citovat tuto publikaci (ČSN ISO 690:2022)NEPOR, Vladimír. Cloudflare Workers Cache: edge cache pro Next.js na Workers. Online. Vrealmatic, 2026. Dostupné z: https://vrealmatic.com/cs/cloudflare/workers-cache. [citováno ].
Chcete rychlejší web na Cloudflare?
Nastavíme Workers Cache, OpenNext i celý stack Cloudflare pro váš projekt.