TriFrost

TriFrost Docs

Learn and build with confidence, from a first project with workers to global scale on bare metal.

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
    Sets max-age in seconds (e.g., 86400 = 1 day).
  • proxyMaxage: number
    Sets s-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' + long maxage for static assets (CSS, JS, images).
  • βœ… Use type: 'private' or type: 'no-store' for sensitive or user-specific content.
  • βœ… Add immutable when you want caches to treat assets as forever fresh.
  • βœ… Use mustRevalidate or proxyRevalidate if you need stricter revalidation control.
  • βœ… Always be explicit β€” don’t leave caching behavior up to defaults.

Resources

Loved the read? Share it with others