Cache Control
The Cache-Control middleware sets HTTP caching directives on your responses, controlling how browsers, CDNs, and proxies cache your content. This is essential for optimizing performance and managing stale data.
Quick Start
import { MageApp } from "@mage/app";
import { cacheControl } from "@mage/app/cache-control";
const app = new MageApp();
// Cache responses for 1 hour in browsers and CDNs
app.use(
cacheControl({
maxAge: 3600,
sMaxAge: 3600,
public: true,
}),
);
app.get("/api/products", (c) => {
return c.json({ products: [] });
});
Deno.serve(app.handler);
How It Works
Sets Cache-Control HTTP header to control how browsers and CDNs cache
responses. See
MDN: HTTP Caching
for directive details.
Options
| Option | Type | Default | Description |
|---|---|---|---|
maxAge |
number |
- | Seconds the response can be cached in browsers |
sMaxAge |
number |
- | Seconds the response can be cached in shared caches (CDN) |
noCache |
boolean |
- | Revalidate with origin before using cached response |
noStore |
boolean |
- | Don't cache the response at all |
noTransform |
boolean |
- | Don't modify the response when caching |
mustRevalidate |
boolean |
- | Discard stale cache; must fetch fresh |
proxyRevalidate |
boolean |
- | Like mustRevalidate but for shared caches only |
mustUnderstand |
boolean |
- | Only cache if the cache understands these directives |
private |
boolean |
- | Only browser caches can store it |
public |
boolean |
- | Shared caches (CDN, proxies) can store it |
immutable |
boolean |
- | Response won't change while fresh |
staleWhileRevalidate |
number |
- | Seconds to use stale cache while fetching fresh |
staleIfError |
number |
- | Seconds to use stale cache if origin is unavailable |
Examples
Static Assets (Images, CSS, JavaScript)
Static assets that don't change should be cached aggressively with the
immutable directive:
app.get(
"/assets/:path",
cacheControl({
maxAge: 31536000, // 1 year
sMaxAge: 31536000,
public: true,
immutable: true,
}),
(c) => {
// Serve static asset
return c.file(`./assets/${c.req.params.path}`);
},
);
With immutable, browsers trust that the file never changes while fresh,
eliminating revalidation checks.
API Responses (with Revalidation)
API responses that change frequently should revalidate with the origin:
app.get(
"/api/posts",
cacheControl({
maxAge: 300, // 5 minutes in browser
sMaxAge: 600, // 10 minutes in CDN
public: true,
staleWhileRevalidate: 86400, // Use up to 1 day old while refreshing
}),
(c) => {
return c.json({ posts: fetchPosts() });
},
);
This allows the CDN to serve stale content while fetching fresh, improving perceived performance.
User-Specific Content (Private Cache)
Content specific to a user should only be cached in their browser:
app.get(
"/account/profile",
cacheControl({
maxAge: 300, // 5 minutes
private: true, // Only browser cache
noTransform: true, // Prevent compression or modification
}),
async (c) => {
const user = await getCurrentUser(c);
return c.json(user);
},
);
Dynamic Content (No Cache)
Highly dynamic content that updates frequently shouldn't be cached:
app.get(
"/api/live-updates",
cacheControl({
noStore: true, // Don't cache at all
}),
(c) => {
return c.json({ timestamp: Date.now() });
},
);
Sensitive Data (No Storage)
Sensitive data like login pages should not be cached:
app.post(
"/login",
cacheControl({
noStore: true,
private: true,
}),
async (c) => {
// Handle login
return c.json({ success: true });
},
);
Security Considerations
Never cache sensitive data in shared caches. Use noStore or private with
short maxAge for sensitive endpoints.
Notes
- Validates conflicting directives (e.g., can't use both
publicandprivate) - Apply globally with
app.use()or per-route - CDN behavior varies—check your CDN's documentation
Related
- Request and Response - Working with HTTP requests and responses
- Middleware System - How middleware works in Mage
- MDN: HTTP Caching - Deep dive on HTTP caching
- RFC 9111: HTTP Caching - Official HTTP caching specification