Handle a purged browser cache on back navigation#94317
Merged
Merged
Conversation
Contributor
Tests PassedCommit: 2762a4e |
Contributor
Stats from current PR🔴 1 regression
📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📝 Changed Files (4 files)Files with changes:
View diffsapp-page-exp..ntime.dev.jsfailed to diffapp-page-tur..ntime.dev.jsfailed to diffapp-page-tur..ntime.dev.jsfailed to diffapp-page.runtime.dev.jsfailed to diff📎 Tarball URLCommit: 2762a4e |
a43a425 to
a568bca
Compare
efcc5dd to
532801d
Compare
When the browser's HTTP cache entry for a back-navigation target has been evicted between forward visit and back-press — for example with a long-lived tab, storage pressure, or a manual cache clear — the browser re-fetches the document fresh from the server. Previously we relied on `type === 'back_forward'` alone to decide that the document came from cache, which meant we treated those re-fetches as cache restores too. The persisted-chunk lookup would miss on the fresh response, and we'd recover with an unnecessary `location.reload()`. The same edge case is exposed by the Playwright/WebKit combination in the upstack PR: bfcache isn't utilized there, so back-navigations always come over the network, and the old single-signal check classified those re-fetches as served-from-cache and triggered the same unnecessary reload. With this change we split the cache-restore detection into two phases. At script-execution time we look at `deliveryType` (Chrome ≥109, Safari ≥17) and the navigation entry's `transferSize`/`encodedBodySize` to decide cache-restore vs fresh-response when those fields are populated. When the body bytes haven't been measured yet — the common case for streaming Next.js dev RSC responses on every browser, and also WebKit leaving both size fields at zero at exec time — we suspend the readable until `pageshow` and re-check there. On a fresh re-fetch we route through the live WebSocket-backed channel, which already has the debug data for the new response, and skip the reload entirely. The new `bfcache-regression` test exercises this edge case on Chromium by clearing the browser cache via CDP between the forward and back navigations, using a new `clearBrowserCache()` helper on the test browser wrapper. The same exec-time code path is naturally hit by Safari whenever its navigation entry's size fields are still zero at script-execution time, but the harness can't force the eviction deterministically there.
532801d to
2762a4e
Compare
lubieowoce
approved these changes
Jun 2, 2026
timneutkens
approved these changes
Jun 2, 2026
abbfa7e
into
hl/fix-eslint-default-case
292 of 298 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When the browser's HTTP cache entry for a back-navigation target has been evicted between forward visit and back-press — for example with a long-lived tab, storage pressure, or a manual cache clear — the browser re-fetches the document fresh from the server. Previously we relied on
type === 'back_forward'alone to decide that the document came from cache, which meant we treated those re-fetches as cache restores too. The persisted-chunk lookup would miss on the fresh response, and we'd recover with an unnecessarylocation.reload().The same edge case is exposed by the Playwright/WebKit combination in the upstack PR: bfcache isn't utilized there, so back-navigations always come over the network, and the old single-signal check classified those re-fetches as served-from-cache and triggered the same unnecessary reload.
With this change we split the cache-restore detection into two phases. At script-execution time we look at
deliveryType(Chrome ≥109, Safari ≥17) and the navigation entry'stransferSize/encodedBodySizeto decide cache-restore vs fresh-response when those fields are populated. When the body bytes haven't been measured yet — the common case for streaming Next.js dev RSC responses on every browser, and also WebKit leaving both size fields at zero at exec time — we suspend the readable untilpageshowand re-check there. On a fresh re-fetch we route through the live WebSocket-backed channel, which already has the debug data for the new response, and skip the reload entirely.The new
bfcache-regressiontest exercises this edge case on Chromium by clearing the browser cache via CDP between the forward and back navigations, using a newclearBrowserCache()helper on the test browser wrapper. The same exec-time code path is naturally hit by Safari whenever its navigation entry's size fields are still zero at script-execution time, but the harness can't force the eviction deterministically there.