Skip to content

fix(pages): pass AppTree to page initial props#2562

Open
james-elicx wants to merge 6 commits into
mainfrom
codex/fix-pages-apptree-28938793088
Open

fix(pages): pass AppTree to page initial props#2562
james-elicx wants to merge 6 commits into
mainfrom
codex/fix-pages-apptree-28938793088

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • pass the Pages AppTree renderer through the nested NextPageContext supplied to _app.getInitialProps
  • keep development and production Pages rendering behavior aligned
  • add focused regression coverage for _app forwarding ctx to Page.getInitialProps

Root cause

Next.js includes AppTree both on the outer AppContext and the nested ctx passed to _app.getInitialProps. Vinext only populated the outer field. Custom apps that call Component.getInitialProps(ctx) therefore forwarded a context without AppTree, causing page-level rendering through AppTree to throw.

Next.js parity

Fixes run 28938793088:

  • test/e2e/app-tree/app-tree.test.tsshould pass AppTree to NextPageContext

Oracle: Next.js v16.3.0-canary.80 at 9dd6d677b63b249584c8ff0bccffcc6a65288683.

Validation

  • vp test run tests/pages-page-data.test.ts tests/pages-get-initial-props.test.ts — 63 passed
  • vp check packages/vinext/src/server/pages-page-data.ts packages/vinext/src/server/pages-get-initial-props.ts tests/pages-page-data.test.ts tests/pages-get-initial-props.test.ts — clean
  • vp run vinext#build — passed
  • NEXTJS_DIR=/Users/jamesanderson/Developer/vinext/.nextjs-ref-v16.3.0-canary.80 VINEXT_BUILD=0 REPO=$PWD ./scripts/run-targeted-nextjs-e2e.sh test/e2e/app-tree/app-tree.test.ts — 3 passed

Separate backlog

test/e2e/no-page-props/no-page-props.test.ts remains queued separately. Its custom _app 404 visibility failures are not demonstrably caused by the missing nested AppTree, so they are intentionally excluded from this PR.

Bonk has not been requested yet.

@pkg-pr-new

pkg-pr-new Bot commented Jul 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2562
npm i https://pkg.pr.new/create-vinext-app@2562
npm i https://pkg.pr.new/vinext@2562

commit: f472357

@github-actions

github-actions Bot commented Jul 8, 2026

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

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared f472357 against base aa1b666 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 126.6 KB 126.6 KB ⚫ +0.0%
Client entry size (gzip) vinext 120.6 KB 120.6 KB ⚫ +0.0%
Dev server cold start vinext 2.17 s 2.17 s ⚫ +0.0%
Production build time vinext 2.46 s 2.49 s ⚫ +1.5%
RSC entry closure size (gzip) vinext 98.5 KB 98.5 KB ⚫ +0.0%
Server bundle size (gzip) vinext 165.1 KB 165.1 KB ⚫ +0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@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: fix(pages): pass AppTree to page initial props

Reviewed against Next.js v16.3.0-canary.80. The change is correct and well-scoped. Verified behavior against the Next.js source and ran the relevant test files locally — all green.

Correctness verification

Nested ctx.AppTree matches Next.js. Next.js sets ctx.AppTree = AppTree in Router.getInitialProps (shared/lib/router/router.ts:2630) and duplicates it into the nested ctx in client/index.tsx:411. The PR mirrors this in every path: shims/router.ts (ctx: { ...ctx, AppTree } and PageComponent.getInitialProps({ ...ctx, AppTree })), pages-get-initial-props.ts (ctx.AppTree), and pages-page-data.ts. Good parity.

committedComponent correctly uses the previous page during navigation. In loadComponentOnlyProps, the client AppTree closure binds committedComponent (the last committed page), not the incoming PageComponent. This matches Next.js exactly: _wrapApp uses the module-level CachedComponent (client/index.tsx:343), which is only updated in render() after getInitialProps(routeInfo.Component, ctx) runs. So during a navigation's data phase, AppTree renders the cached/old component while getInitialProps still receives the new Component separately — the PR replicates this precisely. The precedence committedComponent ?? window.__VINEXT_PAGE_COMPONENT__ ?? PageComponent also correctly handles the first-navigation-after-hydration case, since __VINEXT_PAGE_COMPONENT__ is set at hydration in both the client entry and the dev hydrate template.

Dev error page now runs App.getInitialProps. Previously renderErrorPage only ran ErrorComponent.getInitialProps and never invoked the custom _app's data function. The new branch runs App.getInitialProps (which by convention calls Component.getInitialProps(ctx)) when present, matching Next.js client/index.tsx:411 (loadGetInitialProps(App, appCtx)). statusCode is still force-merged afterward, so the default _error statusCode contract is preserved. Prod already handled this via resolvePagesPageData, so dev/prod parity is now aligned.

Prod path parity holds. pages-page-handler.ts wires createAppTree to createPageElement(PageComponent, AppComponent, ...), and pages-server-entry.ts:createPageElement wraps with the App component when present — so createAppTree builds the full App tree, not a bare page. The ?? options.createPageElement fallback in pages-page-data.ts is therefore safe (prod always supplies createAppTree; the fallback still produces a valid element).

Test coverage

Coverage is appropriately focused:

  • shims.test.ts: two new tests exercising the committed-component AppTree through both custom-_app and page-only getInitialProps during no-data navigation, including a second navigation to confirm the snapshot advances.
  • pages-router.test.ts: route-aware AppTree for default-_app page GIP, plus dev error rendering running custom _app.getInitialProps with nested AppTree.
  • The ISR-regen appTreeWrapCount change 1 → 2 is expected (the new fixture's Page.getInitialProps now invokes AppTree, adding one wrap on top of the final render wrap).

Local validation

  • vitest run tests/pages-page-data.test.ts tests/pages-get-initial-props.test.ts — 64 passed
  • vitest run tests/shims.test.ts -t AppTree — 2 passed
  • vitest run tests/pages-router.test.ts -t "AppTree|error|ISR|regeneration" — all passed
  • tsc --noEmit -p packages/vinext/tsconfig.json — clean

Nits / non-blocking

  • Minor duplication. The AppTree-closure construction pattern (App? wrap-with-App : bare-component, then wrapWithRouterContext) is now hand-rolled in four places: shims/router.ts (loadComponentOnlyProps), dev-server.ts (createAppTree and createErrorAppTree), and pages-page-handler.ts. These are close but not identical (error path passes errorRouter, client path passes committedSnapshot). Not worth blocking on, but a shared helper could reduce drift risk in future changes, consistent with the AGENTS.md guidance to keep runtime logic in typed server/* helpers.
  • The excluded no-page-props backlog item is reasonably justified in the PR description — its _app 404-visibility failures aren't demonstrably caused by the missing nested AppTree.

No correctness issues found. LGTM.

@ask-bonk

ask-bonk Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2562.

Summary

I reviewed PR #2562 (fix(pages): pass AppTree to page initial props) thoroughly, verifying the changes against Next.js v16.3.0-canary.80 source and running the relevant test suites locally. No correctness issues found — LGTM.

Key findings:

Correct parity with Next.js:

  • Nested ctx.AppTree now populated everywhere, matching Next.js's Router.getInitialProps (ctx.AppTree = AppTree) and client/index.tsx error context duplication.
  • The committedComponent design correctly uses the previous page during navigation's data phase, exactly mirroring how Next.js's _wrapApp uses the module-level CachedComponent (updated only in render(), after getInitialProps runs).
  • The precedence chain committedComponent ?? window.__VINEXT_PAGE_COMPONENT__ ?? PageComponent correctly handles first-navigation-after-hydration.
  • Dev error pages now run App.getInitialProps (previously skipped), aligning dev with prod (which already handled this via resolvePagesPageData).
  • statusCode is still force-merged, preserving the _error contract.

Test coverage is focused and appropriate; the ISR appTreeWrapCount 1→2 change is a legitimate consequence of the new fixture invoking AppTree.

Local validation: all targeted test files pass (pages-page-data, pages-get-initial-props, shims -t AppTree, pages-router error/ISR/AppTree), and typecheck is clean.

Non-blocking nits: flagged minor duplication of the AppTree-construction pattern across four sites (shims/router.ts, two spots in dev-server.ts, pages-page-handler.ts) that could be consolidated into a shared server/* helper per AGENTS.md guidance, and noted the no-page-props exclusion is reasonably justified.

github run

@james-elicx james-elicx marked this pull request as ready for review July 8, 2026 17:56
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.

1 participant