Skip to content

Changelog

New updates and improvements at Cloudflare.

Back to all posts

Workers Cache — mark cached responses stale with invalidate()

View as MarkdownAgent setup

Workers Cache now supports invalidate(), the soft counterpart of purge(). purge() deletes matching cached responses, so the next request is a cache miss. invalidate() keeps them but marks them stale, so the cache revalidates them with your Worker instead.

To revalidate a response, the cache sends your Worker a conditional request built from the validators stored with it — for example, If-None-Match carrying the cached ETag. If your Worker answers 304 Not Modified, the cache keeps the stored body. If your Worker answers with a full 200 response, that response replaces the cached one.

invalidate() accepts the same options as purge(): tags, pathPrefixes, or purgeEverything. It follows the same per-entrypoint scoping and resolves to the same result object. Call it as ctx.cache.invalidate(), or import cache from cloudflare:workers and call cache.invalidate().

Use invalidate() when one call covers many cached responses but only some of them changed. Your Worker needs to emit ETag or Last-Modified and answer matching conditional requests with 304. Each unchanged response then costs a validator check instead of a full regeneration:

src/index.jsjs
export default {
	async fetch(request, env, ctx) {
		if (request.method === "POST") {
			// Write the updated catalog, then mark every cached product page stale.
			await syncCatalog(env, await request.json());
			await ctx.cache.invalidate({ tags: ["products"] });
			return new Response("Synced");
		}

		const product = await getProduct(env, request);
		const etag = `"${product.revision}"`;
		const headers = {
			"Cache-Control": "public, max-age=86400",
			"Cache-Tag": "products",
			ETag: etag,
		};

		// The product has not changed since it was cached. Answer 304, and the
		// cache keeps the body it already has.
		if (request.headers.get("If-None-Match") === etag) {
			return new Response(null, { status: 304, headers });
		}

		return new Response(renderProductPage(product), { headers });
	},
};
src/index.tsts
export default {
	async fetch(request, env, ctx): Promise<Response> {
		if (request.method === "POST") {
			// Write the updated catalog, then mark every cached product page stale.
			await syncCatalog(env, await request.json());
			await ctx.cache.invalidate({ tags: ["products"] });
			return new Response("Synced");
		}

		const product = await getProduct(env, request);
		const etag = `"${product.revision}"`;
		const headers = {
			"Cache-Control": "public, max-age=86400",
			"Cache-Tag": "products",
			ETag: etag,
		};

		// The product has not changed since it was cached. Answer 304, and the
		// cache keeps the body it already has.
		if (request.headers.get("If-None-Match") === etag) {
			return new Response(null, { status: 304, headers });
		}

		return new Response(renderProductPage(product), { headers });
	},
} satisfies ExportedHandler<Env>;

For more information, refer to Invalidate cached responses.