Skip to content

fix(dev): restore no-store for dev HTML responses to prevent bfcache hydration failures#94107

Closed
sleitor wants to merge 1 commit into
vercel:canaryfrom
sleitor:fix-94036
Closed

fix(dev): restore no-store for dev HTML responses to prevent bfcache hydration failures#94107
sleitor wants to merge 1 commit into
vercel:canaryfrom
sleitor:fix-94036

Conversation

@sleitor
Copy link
Copy Markdown
Contributor

@sleitor sleitor commented May 25, 2026

What?

Restores no-store, must-revalidate as the dev server Cache-Control header value for HTML document responses, reverting the behavioral change introduced in #91503 (which hard-coded no-cache, must-revalidate).

Fixes #94036

Why?

Chrome's bfcache (back/forward cache) and disk cache can serve no-cache responses without revalidating on back/forward navigation. This is a known browser behavior: back/forward navigations are treated differently from regular fetches, and no-cache does not reliably force revalidation in that path.

In practice this means:

  1. User visits a Next.js dev page → server sends HTML with no-cache, must-revalidate
  2. Chrome stores the response in its disk/bfcache
  3. HMR updates JS chunks on the dev server
  4. User clicks Back/Forward → Chrome restores the old HTML from bfcache without revalidating
  5. The stale HTML's __next_f.push() chunks reference build IDs/URLs from a previous build
  6. Turbopack hydration silently fails — the page looks rendered but is un-hydrated

How?

Change 'no-cache, must-revalidate''no-store, must-revalidate' in the three dev HTML render paths:

  • packages/next/src/server/base-server.ts (main render path)
  • packages/next/src/build/templates/app-page.ts (app router)
  • packages/next/src/server/route-modules/pages/pages-handler.ts (pages router)

no-store prevents the browser from storing the HTML in any cache (including bfcache), so back/forward navigation always results in a fresh request to the dev server. This eliminates the stale-chunk hydration failure.

Note: The static asset path (/_next/static/) is unaffected — it correctly uses public, max-age=31536000, immutable in prod and no-cache, must-revalidate in dev, since JS chunks are fingerprinted by content hash and stale-chunk issues don't apply there.

Update all tests that previously expected no-cache, must-revalidate in dev mode.

…hydration failures

Fixes vercel#94036

Chrome's bfcache (back/forward cache) and disk cache can serve
`no-cache` responses without revalidating on back/forward navigation.
When the dev server has updated JS chunks (via HMR), this results in
stale HTML with mismatched `__next_f.push()` chunks being restored,
causing silent Turbopack hydration failures.

PR vercel#91503 changed the dev HTML Cache-Control from `no-store` to
`no-cache` to enable conditional revalidation (304 Not Modified).
While correct for production, `no-cache` in dev allows bfcache to
bypass revalidation entirely on back/forward, leading to stale page
renders and broken hydration.

Restore `no-store, must-revalidate` across all three dev HTML render
paths (base-server, app-page template, pages-handler) so the browser
never stores dev HTML in any cache, eliminating the stale-chunk issue.

Update tests to expect `no-store, must-revalidate` accordingly.
@sleitor sleitor closed this Jun 1, 2026
@sleitor sleitor deleted the fix-94036 branch June 1, 2026 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

next dev 16.1+: back/forward navigation in Chrome leaves page un-hydrated due to Cache-Control change to no-cache

1 participant