Skip to content

Performance · Next.js (App Router)

How to set Cache-Control on static assets in Next.js (App Router)

With no Cache-Control, ETag or Last-Modified, browsers apply a heuristic and CDNs usually decline to cache at all, so the asset is fetched from your origin far more often than it needs to be. One header turns a repeat request into a local read.

The fix for Next.js (App Router)

13x detects your framework from the response and hands you this version rather than the generic one — below 50% confidence it hedges and gives you the generic one instead.

Next already serves /_next/static/* with max-age=31536000, immutable — those files are content-hashed, so nothing needs doing there.

What needs headers is everything you put in public/, which Next serves with no caching policy at all:

ts code
// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        // Fonts and images in public/ — rename the file to bust the cache.
        source: "/:all*(svg|jpg|jpeg|png|webp|avif|woff2)",
        headers: [
          { key: "Cache-Control", value: "public, max-age=31536000, immutable" },
        ],
      },
    ];
  },
};

export default nextConfig;

The tradeoff to understand: with immutable, replacing public/logo.png in place will not reach anyone who has already loaded it. Change the filename instead — that is the same discipline the hashed bundles already follow.

On a different stack? The general version of this fix explains what 13x checks and why it matters, without assuming a framework.

Check your Next.js (App Router) site

113 deterministic checks against your live URL, in about 30 seconds. Framework detected from the response, so every fix comes back in the form your stack actually uses.

No signup. Results in 30 seconds.

The same fix, other stacks