Link to the code that reproduces this issue
https://github.com/cipriancaba/next-runtime-deployment-id-prerender-skew
To Reproduce
npm install
NEXT_DEPLOYMENT_ID=dpl_aaaaaaaaaaaaaaaa npm run build
NEXT_DEPLOYMENT_ID=dpl_bbbbbbbbbbbbbbbb npm run start (same build artifact, different deployment id — simulating a .next built under deployment A being served under deployment B, e.g. a Turborepo remote-cache restore across Vercel deployments)
- Open http://localhost:3000 (a fully prerendered page) and click the
<Link> to /dynamic (a dynamic page)
- Observe in the Network tab: a full document navigation instead of a client-side RSC navigation. Go back to
/ and click again — it hard-reloads every time and never recovers. With a router.push in a form (login flows) this becomes an infinite hard-reload loop.
- Control: rerun step 3 with the same id as the build (
dpl_aaaaaaaaaaaaaaaa) — the same click is a normal client-side navigation.
Browserless verification (server from step 3 running):
$ curl -s http://localhost:3000/ | grep -o 'dpl_[ab]*' | sort | uniq -c
23 dpl_aaaaaaaaaaaaaaaa # BUILD-time id baked into the served flight payload
$ curl -s -L -D - -o /dev/null -H 'RSC: 1' http://localhost:3000/dynamic | grep -i x-nextjs-deployment-id
x-nextjs-deployment-id: dpl_bbbbbbbbbbbbbbbb # RUNTIME id on the RSC navigation response
Headless-browser check (check.mjs in the repro, Playwright counting full document navigations after clicking the <Link>):
# mismatched ids
round 1: navigation to /dynamic caused 1 full document load(s) -> MPA fallback (full page reload)
round 2: navigation to /dynamic caused 1 full document load(s) -> MPA fallback (full page reload)
# control, same id
navigation to /dynamic caused 0 full document load(s) -> client-side navigation
Current vs. Expected behavior
Current: experimental.runtimeServerDeploymentId (#86865, auto-enabled on Vercel when skew protection is on) resolves the deployment id at runtime for data-dpl-id and dynamic responses — but the id embedded in prerendered flight payloads (the b field consumed by setNavigationBuildId) is still baked at build time. When a .next built under deployment A is served under deployment B, the initial HTML of a prerendered route initializes the client's navigation build id with A, every dynamic RSC/nav response carries x-nextjs-deployment-id: B, and the client skew check (fetch-server-response.js / server-action-reducer.js: responseBuildId !== getNavigationBuildId()) fires on every client navigation → MPA full-page reload → the reload serves the prerendered HTML again with A's id → infinite hard-reload loop.
We hit this in production (Next 16.3.3) via a Turborepo remote-cache hit on Vercel builders after excluding VERCEL_DEPLOYMENT_ID from the task hash — the anti-pattern from vercel/turborepo#10478, since reverted on our side. Our login form (router.push before a server action) became unusable; wire traffic between a fresh and a restored deployment was byte-identical except each client sending its own deployment id.
Expected: the navigation build id in prerendered flight payloads resolved at serve time (like data-dpl-id already is), making build artifacts deployment-portable — or explicit documentation that .next artifacts must never be reused across deployments while skew detection is active.
Since data-dpl-id is already injected at serve time, serve-time stamping the prerendered payload's navigation build id would also unlock the Turborepo-cache-on-Vercel case that vercel/turborepo#10478 closes as a platform limitation (the "we need to let deployment platforms influence the hash" takeaway there — this is the other half of that story).
Aware of #94734 and #95397 as adjacent runtimeServerDeploymentId issues; this one is specifically about the prerendered flight payload id. Supersedes #98077, which was closed by the bot for a missing reproduction link — this issue adds the runnable reproduction.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.6.0: Fri Jul 31 19:19:08 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6050
Available memory (MB): 49152
Available CPU cores: 18
Binaries:
Node: 24.14.1
npm: 11.18.0
Yarn: N/A
pnpm: 10.33.0
Relevant Packages:
next: 16.3.3 // Latest available version is detected (16.3.3).
eslint-config-next: N/A
react: 19.2.8
react-dom: 19.2.8
typescript: 5.9.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Navigation, Runtime
Which stage(s) are affected? (Select all that apply)
next build (local), next start (local), Vercel (Deployed)
Additional context
Verified on next@16.3.3 (latest stable) and next@16.4.0-canary.12 — identical behavior on both: served prerendered HTML carries the build-time id, the RSC navigation response header carries the runtime id, and Playwright observes the MPA full-page reload on every <Link> click.
Link to the code that reproduces this issue
https://github.com/cipriancaba/next-runtime-deployment-id-prerender-skew
To Reproduce
npm installNEXT_DEPLOYMENT_ID=dpl_aaaaaaaaaaaaaaaa npm run buildNEXT_DEPLOYMENT_ID=dpl_bbbbbbbbbbbbbbbb npm run start(same build artifact, different deployment id — simulating a.nextbuilt under deployment A being served under deployment B, e.g. a Turborepo remote-cache restore across Vercel deployments)<Link>to/dynamic(a dynamic page)/and click again — it hard-reloads every time and never recovers. With arouter.pushin a form (login flows) this becomes an infinite hard-reload loop.dpl_aaaaaaaaaaaaaaaa) — the same click is a normal client-side navigation.Browserless verification (server from step 3 running):
Headless-browser check (
check.mjsin the repro, Playwright counting full document navigations after clicking the<Link>):Current vs. Expected behavior
Current:
experimental.runtimeServerDeploymentId(#86865, auto-enabled on Vercel when skew protection is on) resolves the deployment id at runtime fordata-dpl-idand dynamic responses — but the id embedded in prerendered flight payloads (thebfield consumed bysetNavigationBuildId) is still baked at build time. When a.nextbuilt under deployment A is served under deployment B, the initial HTML of a prerendered route initializes the client's navigation build id with A, every dynamic RSC/nav response carriesx-nextjs-deployment-id: B, and the client skew check (fetch-server-response.js/server-action-reducer.js:responseBuildId !== getNavigationBuildId()) fires on every client navigation → MPA full-page reload → the reload serves the prerendered HTML again with A's id → infinite hard-reload loop.We hit this in production (Next 16.3.3) via a Turborepo remote-cache hit on Vercel builders after excluding
VERCEL_DEPLOYMENT_IDfrom the task hash — the anti-pattern from vercel/turborepo#10478, since reverted on our side. Our login form (router.pushbefore a server action) became unusable; wire traffic between a fresh and a restored deployment was byte-identical except each client sending its own deployment id.Expected: the navigation build id in prerendered flight payloads resolved at serve time (like
data-dpl-idalready is), making build artifacts deployment-portable — or explicit documentation that.nextartifacts must never be reused across deployments while skew detection is active.Since
data-dpl-idis already injected at serve time, serve-time stamping the prerendered payload's navigation build id would also unlock the Turborepo-cache-on-Vercel case that vercel/turborepo#10478 closes as a platform limitation (the "we need to let deployment platforms influence the hash" takeaway there — this is the other half of that story).Aware of #94734 and #95397 as adjacent
runtimeServerDeploymentIdissues; this one is specifically about the prerendered flight payload id. Supersedes #98077, which was closed by the bot for a missing reproduction link — this issue adds the runnable reproduction.Provide environment information
Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Version 25.6.0: Fri Jul 31 19:19:08 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6050 Available memory (MB): 49152 Available CPU cores: 18 Binaries: Node: 24.14.1 npm: 11.18.0 Yarn: N/A pnpm: 10.33.0 Relevant Packages: next: 16.3.3 // Latest available version is detected (16.3.3). eslint-config-next: N/A react: 19.2.8 react-dom: 19.2.8 typescript: 5.9.3 Next.js Config: output: N/AWhich area(s) are affected? (Select all that apply)
Navigation, Runtime
Which stage(s) are affected? (Select all that apply)
next build (local), next start (local), Vercel (Deployed)
Additional context
Verified on
next@16.3.3(latest stable) andnext@16.4.0-canary.12— identical behavior on both: served prerendered HTML carries the build-time id, the RSC navigation response header carries the runtime id, and Playwright observes the MPA full-page reload on every<Link>click.