Skip to content

fix(router): share Pages Router context across chunks - #2010

Merged
james-elicx merged 1 commit into
mainfrom
codex/fix-pages-router-context-identity
Jun 14, 2026
Merged

fix(router): share Pages Router context across chunks#2010
james-elicx merged 1 commit into
mainfrom
codex/fix-pages-router-context-identity

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • share the Pages Router RouterContext across duplicated client-entry/page-chunk module instances
  • restore reactive router.isReady updates for Pages Router pages rendered through the App Router hooks fixture
  • add a focused duplicate-module context identity regression test

CI regression

This fixes a newly failing deploy-suite delta between:

The affected upstream test is test/e2e/app-dir/hooks/hooks.test.ts, specifically the /adapter-hooks/static and /adapter-hooks/1/account cases timing out while waiting for #router-ready.

Endpoint independence

This failure is unrelated to next-data-api-endpoint.vercel.app:

  • the complete pinned fixture directory test/e2e/app-dir/hooks contains no next-data-api-endpoint.vercel.app, vercel.app, or HTTP URL references
  • the deployment succeeds and the test reaches local browser assertions after hydration
  • both failures are local router.isReady assertion timeouts, not fetch, deployment, or shared-server cascades

I excluded the middleware method-forwarding and rewrite-body candidates because their fixtures directly call the Vercel endpoint. I also excluded the segment-cache candidate because it failed locally at the declared good SHA.

Root cause

4baf85ad made the Pages Router wrapper component types and readiness state shared across duplicated next/router bundles, but RouterContext stayed module-local. An HTML-fallback page chunk could therefore read a different context from the shared provider and fall back to the non-reactive singleton. Deferred-ready routes stayed at isReady=false even after the shared readiness bit flipped.

This patch gives the Pages Router context the same symbol-keyed shared identity strategy already used by the App Router contexts. It does not change readiness semantics.

Next.js parity

Verified against pinned Next.js v16.2.6 / ee6e79b1792a4d401ddf2480f40a83549fe8e722:

Local reproduction

Fresh disposable Next.js checkout, Node 24, Next.js pinned to ee6e79b1792a4d401ddf2480f40a83549fe8e722:

env -u VINEXT_BUILD \
  NEXTJS_PREPARE=0 \
  NEXT_TEST_CONCURRENCY=1 \
  vp env exec --node 24 -- \
  ./scripts/run-nextjs-deploy-suite.sh /tmp/nextjs-v16.2.6-20260614-153154 \
  --retries 0 -c 1 --debug \
  test/e2e/app-dir/hooks/hooks.test.ts

Results:

  • good SHA 5ace0db7: vinext built, deployment succeeded, 26/26 passed
  • bad SHA de9d03a8: vinext built, deployment succeeded, 2 failed / 24 passed with matching #router-ready timeouts
  • boundary: d1a80cd6 passes; 4baf85ad fails the same two assertions
  • fixed branch: vinext built, deployment succeeded, 26/26 passed

Validation

vp check packages/vinext/src/shims/internal/router-context.ts tests/shims.test.ts
vp test run tests/shims.test.ts
  • scoped format/lint/type checks pass
  • 1,086/1,086 shim tests pass
  • exact deploy-suite probe passes 26/26

@pkg-pr-new

pkg-pr-new Bot commented Jun 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2010
npm i https://pkg.pr.new/vinext@2010

commit: 49d9fc8

@github-actions

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

LuuOW

This comment was marked as spam.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@james-elicx
james-elicx marked this pull request as ready for review June 14, 2026 15:14

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: share Pages Router context across chunks

Reviewed both changed files, the consumers of RouterContext, and the App Router pattern this mirrors. The fix is correct and well-scoped. Scoped vp check and the new regression test both pass locally (1086 shim tests, 1 run / 1085 skipped).

Correctness

The change applies the same Symbol.for()-keyed globalThis identity strategy already used in app-router-context.ts:35-61. This is the right fix for the duplicate-module problem: when Vite evaluates next/router in both the client entry and a page chunk, the module-local createContext(null) produced two distinct context objects. PagesRouterProvider populated one (router.ts:2431) while a fallback page chunk read the other and fell through to the non-reactive Router singleton (router.ts:2353), so deferred isReady never propagated. Sharing the context object via globalThis[ROUTER_CONTEXT_KEY] resolves this.

All consumers benefit and none regress, since they all reference the same shared object:

  • router.ts:2339,2432 — provider + useRouter
  • compat-router.ts:23next/compat/router
  • error.tsx:245useContext(RouterContext) !== null Pages-vs-App detection (this was previously a latent split-chunk bug too: a separate module-local context would have always returned null here)

No regression vs. the App Router pattern

app-router-context.ts adds a typeof React.createContext !== "function" guard and can return null. This PR omits that guard, which is correct: the original RouterContext was createContext(null) with no guard and was always treated as a valid, non-null context. The App Router guard exists because those contexts are imported into the RSC environment where createContext may be unavailable; the Pages Router context is client/SSR-only, so the guard isn't needed and adding nullability would force needless null checks on every consumer. Preserving the always-non-null type is the right call.

Test

tests/shims.test.ts:20934 correctly validates the fix: vi.resetModules() clears the registry to force a genuinely fresh re-import, and the toBe identity assertion only holds because of the globalThis symbol storage. It fails against the old createContext(null) implementation. Placed appropriately in the existing "Pages Router runtime state sharing" describe block.

Verification performed

  • vp check packages/vinext/src/shims/internal/router-context.ts tests/shims.test.ts — pass (format/lint/types)
  • vp test run tests/shims.test.ts -t "shares RouterContext" — pass

No issues found. LGTM.

@ask-bonk

ask-bonk Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review posted on PR #2010.

Summary

I reviewed PR #2010 (fix(router): share Pages Router context across chunks) — a focused 22/-2 change across two files.

Verdict: LGTM, no issues found.

The fix is correct and well-scoped:

  • Root cause is real: router-context.ts previously used a module-local createContext(null). When Vite duplicates the next/router module across the client entry and page chunks, two distinct context objects exist — the provider populates one (router.ts:2431) while a fallback page chunk reads the other and falls through to the non-reactive Router singleton (router.ts:2353), so deferred isReady never propagated.

  • The fix is consistent with the established Symbol.for()-keyed globalThis identity strategy already used in app-router-context.ts:35-61.

  • All consumers benefit, none regressuseRouter, next/compat/router, and the error.tsx:245 Pages-vs-App detection all reference the same shared object now.

  • Omitting the App Router's createContext null-guard is correct — the original RouterContext was always a non-null context, and the guard only exists for RSC-environment imports, which don't apply to this client/SSR-only context.

  • The regression test is valid: vi.resetModules() forces a fresh re-import, and the toBe identity assertion only holds because of the global symbol storage.

I verified locally that vp check passes (format/lint/types) and the new test passes.

github run

@james-elicx
james-elicx merged commit e1958ae into main Jun 14, 2026
55 checks passed
@james-elicx
james-elicx deleted the codex/fix-pages-router-context-identity branch June 14, 2026 15:15
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.

2 participants