H Heygents Docs Open App

Updated July 31, 2026 · Performance · Medium severity

HTML pages sent with cache-control: no-store

why does every page on my site have cache-control no-store Usually because session-refresh middleware or a locale cookie runs on every request path, including anonymous ones, which forces the framework into full server-side rendering and defeats its own static or ISR caching. Check with curl -sI on a few routes, then scope the middleware matcher so it skips public pages, and separate anonymous responses from authenticated ones.

Seen in 20 of 450 scanned projects (4%). Based on 450 Deep Scan runs across production projects.

Common stacks: Next.js Nginx CDN Vercel

How to tell you have it

  • curl -sI on the homepage and every other route shows cache-control: no-store
  • Pages that should be statically generated render on every request instead
  • Time to first byte for anonymous visitors is high even though the page content rarely changes
  • Middleware logs show session or locale checks running on marketing and static pages
  • Immutable JS and CSS assets are served without a content hash, so browsers never cache them across deploys

Why it matters

cache-control: no-store on every HTML response tells browsers, CDNs, and any intermediate cache to never store the page, not even for a second. That single header undoes every optimization your framework's static rendering or incremental static regeneration was built to provide, and it does it silently, since nothing in the app looks broken, it is just slower than it needs to be for everyone.

The usual cause is middleware that runs globally instead of being scoped to the routes that actually need it. A session-refresh check or a locale-detection cookie write is a legitimate thing to do for a logged-in dashboard, but if the matcher pattern also catches the marketing homepage, the pricing page, and the blog, those pages lose their caching even though nothing about them is personalized.

Anonymous visitors are the group hurt most by this. A visitor who has never logged in and has no session should be served the cheapest, most cacheable version of the page, ideally straight from a CDN edge node with no origin round trip at all. Instead they get full server-side rendering on every request because the middleware cannot tell the difference between them and a logged-in user.

A related but separate issue is immutable assets served without a content hash in the filename. Even with correct HTML cache headers, if your JS and CSS bundles are not fingerprinted, you either cannot set a long max-age safely, because a new deploy would serve stale assets under the old cache, or you are forced to bust the cache on every deploy for everyone, throwing away a year of possible cache lifetime.

How Heygents detects it

Deep Scan runs curl -sI against several representative routes on the live site to read the cache-control header on both HTML and static asset responses, and reads the project's middleware configuration and matcher patterns to see which routes trigger session or locale logic.

How to fix it

  1. Confirm the problem with curl on a few routes Check both a static-looking page and a known dynamic one to see whether the no-store header is applied everywhere or just where it belongs.
  2. Scope the middleware matcher to only the routes that need it Restrict session-refresh or locale-cookie middleware to authenticated or locale-sensitive paths, excluding static assets, marketing pages, and public API routes.
  3. Separate anonymous responses from authenticated ones explicitly When a route genuinely serves both anonymous and authenticated users, branch on session presence before deciding the cache policy, rather than defaulting the whole route to no-store.
  4. Fingerprint immutable assets and cache them for a year Confirm the build pipeline outputs content-hashed filenames for JS and CSS, then set a long immutable max-age on those specific asset paths at the CDN or reverse proxy.
  5. Re-run curl checks after the fix to confirm cacheable responses Verify the previously no-store routes now return a sane cache-control value for anonymous visitors, and that authenticated routes still correctly say private or no-store.

Confirm the problem with curl on a few routes

curl -sI https://example.com/ | grep -i cache-control
curl -sI https://example.com/blog/some-post | grep -i cache-control
curl -sI https://example.com/dashboard | grep -i cache-control

Scope the middleware matcher to only the routes that need it

export const config = {
  matcher: ['/dashboard/:path*', '/account/:path*', '/api/private/:path*'],
};

Separate anonymous responses from authenticated ones explicitly

export function GET(req) {
  const session = getSession(req);
  const headers = session
    ? { 'Cache-Control': 'private, no-store' }
    : { 'Cache-Control': 'public, max-age=60, stale-while-revalidate=300' };
  return new Response(html, { headers });
}

Fingerprint immutable assets and cache them for a year

location ~* \.[0-9a-f]{8,}\.(js|css)$ {
  add_header Cache-Control "public, max-age=31536000, immutable";
}

Find this in your own projects, automatically

Heygents runs a read-only Deep Scan across every project you own, finds issues like this one, and hands you a ready-to-run fix an AI agent can execute and verify. A solo developer gets the audit, the backlog and the fix loop in one place.

Open Heygents →

Frequently asked questions

Is cache-control no-store ever the correct choice for HTML?

Yes, for authenticated pages containing user-specific data, no-store or private is exactly right, since you do not want a shared cache or CDN storing one user's dashboard and serving it to another. The problem is only when it leaks onto pages with no personalized content.

Why does a locale cookie force no-store on every page?

If middleware reads or writes a cookie on every request to detect locale, many frameworks treat that as a signal the response is dynamic and per-user, which disables static generation and caching for the whole route even though the actual HTML might be identical for most visitors.

What is stale-while-revalidate and should I use it?

It lets a cache serve a slightly stale response instantly while fetching a fresh one in the background, which is a good default for content that changes occasionally, like blog posts or marketing pages, because visitors never wait on a cache miss.

Does fixing this affect SEO?

Indirectly yes, since faster time to first byte and consistent caching improve Core Web Vitals, which is a ranking factor. It also reduces server load, which keeps response times stable during traffic spikes from a sudden increase in crawler or visitor activity.