deploy/reference/caching.md
Deno Deploy includes a built-in HTTP caching layer that automatically caches eligible responses at the edge, reducing latency and origin load. This document covers how caching works, how to control cache behavior, and how to invalidate cached content.
When a request arrives at Deno Deploy:
The cache follows RFC 9110 and RFC 9111 semantics, implementing standard HTTP caching behavior.
Deno Deploy respects the standard Cache-Control header with the following
directives:
| Directive | Description |
|---|---|
public | Response can be cached by shared caches |
private | Response is user-specific and cannot be cached (bypasses CDN) |
no-store | Response must not be cached |
no-cache | Response must be revalidated before use |
max-age=N | Response is fresh for N seconds |
s-maxage=N | Like max-age, but only for shared caches (takes precedence) |
stale-while-revalidate=N | Serve stale content while revalidating in background |
stale-if-error=N | Serve stale content if origin returns an error |
must-revalidate | Stale responses must not be used without revalidation |
Deno.serve(() => {
return new Response("Hello, World!", {
headers: {
"Cache-Control": "public, s-maxage=3600",
},
});
});
Deno.serve(() => {
return new Response(JSON.stringify({ data: "..." }), {
headers: {
"Content-Type": "application/json",
// Cache for 60s, serve stale for up to 5 minutes while revalidating
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
},
});
});
Deno Deploy supports additional headers for fine-grained cache control:
Deno-CDN-Cache-ControlA CDN-specific cache control header that takes precedence over both
CDN-Cache-Control and Cache-Control. Use this when you want different
caching behavior for Deno Deploy's CDN versus browsers or other caches.
Header priority (highest to lowest):
Deno-CDN-Cache-ControlCDN-Cache-ControlCache-ControlDeno.serve(() => {
return new Response("Hello!", {
headers: {
// Browser caches for 60s
"Cache-Control": "public, max-age=60",
// Deno Deploy CDN caches for 1 hour
"Deno-CDN-Cache-Control": "public, s-maxage=3600",
},
});
});
Deno-Cache-Tag / Cache-TagAssociate responses with cache tags for targeted invalidation. Tags allow you to invalidate groups of cached responses without knowing their exact URLs.
Deno.serve((req) => {
const url = new URL(req.url);
const productId = url.pathname.split("/")[2];
return new Response(JSON.stringify({ id: productId, name: "Widget" }), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, s-maxage=3600",
// Tag this response for later invalidation
"Deno-Cache-Tag": `product-${productId},products,catalog`,
},
});
});
Tag format:
Deno-Cache-IdA special header that serves two purposes:
Opt out of automatic invalidation: Responses with Deno-Cache-Id use a
shared cache namespace that persists across deployments. Without this header,
cached responses are automatically invalidated when you deploy a new version.
Acts as a cache tag: The value can be used to invalidate the cached response.
Deno.serve(() => {
return new Response("Static content that rarely changes", {
headers: {
"Cache-Control": "public, s-maxage=86400",
// This response survives redeployments
"Deno-Cache-Id": "static-content-v1",
},
});
});
Use cases for Deno-Cache-Id:
Deno-VaryExtend cache variation beyond standard HTTP Vary semantics. (Coming soon)
Deno Deploy supports on-demand cache invalidation via cache tags. This allows you to purge specific cached content without redeploying.
Send a POST request to http://cache.localhost/invalidate/http from within your
Deno Deploy application:
Deno.serve(async (req) => {
const url = new URL(req.url);
if (req.method === "POST" && url.pathname === "/admin/purge") {
// Invalidate all responses tagged with "products"
const res = await fetch("http://cache.localhost/invalidate/http", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
tags: ["products"],
}),
});
if (res.ok) {
return new Response("Cache purged successfully");
}
return new Response("Purge failed", { status: 500 });
}
// ... handle other requests
});
{
"tags": ["tag1", "tag2", "tag3"]
}
tags: Array of cache tags to invalidate (required)Use "*" to invalidate all cached responses for your deployment:
await fetch("http://cache.localhost/invalidate/http", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
tags: ["*"],
}),
});
Cache invalidation is automatically synchronized across all Deno Deploy regions. When you invalidate a tag, the purge propagates globally within seconds.
Deno.serve(async (req) => {
const url = new URL(req.url);
// Webhook endpoint for CMS updates
if (req.method === "POST" && url.pathname === "/webhook/cms") {
const payload = await req.json();
// Invalidate based on content type
const tags: string[] = [];
if (payload.type === "product") {
tags.push(`product-${payload.id}`, "products");
} else if (payload.type === "category") {
tags.push(`category-${payload.id}`, "categories", "navigation");
}
if (tags.length > 0) {
await fetch("http://cache.localhost/invalidate/http", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tags }),
});
}
return new Response("OK");
}
// ... serve content
});
Deno Deploy adds a Cache-Status response header to indicate the cache result:
| Value | Description |
|---|---|
deno; hit | Response served from cache |
deno; fwd=uri-miss; stored | Cache miss, response stored for future requests |
deno; fwd=miss; stored | Vary miss, response stored with new vary key |
deno; fwd=stale | Stale response, forwarding to origin |
deno; fwd=method | Non-cacheable method (POST, PUT, etc.) |
deno; fwd=bypass | Response explicitly bypassed cache |
deno; fwd=request | Request directives prevented caching |
When a response bypasses the cache, the detail field indicates why:
detail=not-cacheable - Response doesn't meet caching criteriadetail=no-cache-or-private - no-cache or private directive presentdetail=set-cookie - Response contains Set-Cookie headerdetail=pragma-no-cache - Legacy Pragma: no-cache header presentdetail=too-large - Response body exceeds maximum cacheable sizedetail=zero-ttl - Calculated TTL is zerodetail=vary-star - Vary: * header prevents cachingdetail=header-overflow - Too many response headersA response is cacheable when:
GET or HEADCache-Control, Expires, etc.)no-store, private, or no-cache directivesSet-Cookie headerThe cache respects the standard Vary header to store different versions of a
response based on request headers:
Deno.serve((req) => {
const acceptLanguage = req.headers.get("Accept-Language") || "en";
const language = acceptLanguage.startsWith("es") ? "es" : "en";
return new Response(`Hello in ${language}!`, {
headers: {
"Cache-Control": "public, s-maxage=3600",
"Vary": "Accept-Language",
"Content-Language": language,
},
});
});
Note: Vary: * prevents caching entirely.
HEAD requests can be served from cached GET responses. The cache automatically strips the response body while preserving headers.
The cache supports HTTP range requests (Range header) and can serve partial
content from cached full responses.
s-maxage for CDN caching// Let browsers cache for 1 minute, CDN for 1 hour
headers: {
"Cache-Control": "public, max-age=60, s-maxage=3600"
}
// Tag by content type, ID, and category for flexible purging
headers: {
"Deno-Cache-Tag": `article-${id},articles,category-${category}`
}
stale-while-revalidate for better UX// Serve stale content while refreshing in background
headers: {
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=600"
}
Deno-Cache-Id for stable assets// Content-addressed assets that shouldn't be invalidated on deploy
const hash = computeHash(content);
headers: {
"Cache-Control": "public, s-maxage=31536000, immutable",
"Deno-Cache-Id": `asset-${hash}`
}
max-age=31536000 (1 year)s-maxage=60 to s-maxage=300 with stale-while-revalidateVary appropriatelyBy default, all cached responses (without Deno-Cache-Id) are automatically
invalidated when you deploy a new version of your application. This ensures
users always see fresh content after a deployment.
To opt out of automatic invalidation, use the Deno-Cache-Id header.