Middleware: Cache Control
The CacheControl
middleware in TriFrost helps you set explicit HTTP Cache-Control headers on your responses β making sure clients, proxies, and CDNs know exactly how to handle your content.
Whether you want to lock things down with no-store
or lean into long-term public
caching, this middleware gives you direct, no-magic control.
π¦ Import and Attach
import {CacheControl} from '@trifrost/core';
...
.use(CacheControl(options))
...
You can apply it:
- Globally on the app
- Per router
- Per route
- As a cacheControl option when responding through
ctx.html
,ctx.file
,ctx.text
,ctx.json
βοΈ Usage
App-wide:
app.use(CacheControl(options));
Router-wide:
app.group('/static/*', router => {
router
.use(CacheControl(options))
...
})
Single-Route:
router.route('/assets/*', route => {
route
.use(CacheControl(options))
...
})
As part of a response:
app
.get('/assets/:path', ctx => ctx.file(`/public/${ctx.state.path}`, {cacheControl: {...}}))
βοΈ Available Options
type
:'no-cache' | 'no-store' | 'private' | 'public'
Sets the primary caching directive.maxage
: number
Setsmax-age
in seconds (e.g., 86400 = 1 day).proxyMaxage
: number
Setss-maxage
in seconds (for shared caches like CDNs).immutable
: boolean
Marks the response as immutable (never changes).
default:false
mustRevalidate
: boolean
Signals that once stale, the cache must revalidate with the origin.
default:false
proxyRevalidate
: boolean
Signals that shared caches must revalidate once stale.
default:false
Note:
- Only valid type values are accepted. Invalid strings are ignored.
- maxage and proxyMaxage must be positive integers.
Examples
Public static content, cache 1 day:
app.group('/static/*', router => {
router.use(CacheControl({
type: 'public',
maxage: 86400 /* 1 day */
}));
});
Header sent:
Cache-Control: public, max-age=86400
Private user dashboard, no caching:
app.group('/dashboard/*', router => {
router.use(CacheControl({
type: 'private',
maxage: 0
}));
});
Header sent:
Cache-Control: private, max-age=0
Immutable build assets, cache 1 year:
app.group('/assets/*', router => {
router.use(CacheControl({
type: 'public',
maxage: 31536000, /* 1 year */
immutable: true
}));
});
Header sent:
Cache-Control: public, max-age=31536000, immutable
CDN-specific shared cache control:
app.group('/cdn/*', router => {
router.use(CacheControl({
type: 'public',
maxage: 60, /* browsers: 1 min */
proxyMaxage: 600 /* shared caches: 10 min */
}));
});
Header sent:
Cache-Control: public, max-age=60, s-maxage=600
Force revalidation on stale:
app.group('/reports/*', router => {
router.use(CacheControl({
type: 'private',
mustRevalidate: true
}));
});
Header sent:
Cache-Control: private, must-revalidate
Using with ctx.file:
app.get('/assets/:path', ctx =>
ctx.file(`/public/${ctx.state.path}`, {
cacheControl: {
type: 'public',
maxage: 86400 /* 1 day */
}
})
);
Header sent:
Cache-Control: public, max-age=86400
Using ctx.file with immutable long-term assets:
app.get('/assets/build/:path', ctx =>
ctx.file(`/public/build/${ctx.state.path}`, {
cacheControl: {
type: 'public',
maxage: 31536000, /* 1 year */
immutable: true
}
})
);
Header sent:
Cache-Control: public, max-age=31536000, immutable
Best Practices
- β
Use
type: 'public'
+ longmaxage
for static assets (CSS, JS, images). - β
Use
type: 'private'
ortype: 'no-store'
for sensitive or user-specific content. - β
Add
immutable
when you want caches to treat assets as forever fresh. - β
Use
mustRevalidate
orproxyRevalidate
if you need stricter revalidation control. - β Always be explicit β donβt leave caching behavior up to defaults.