[PR #1018] [MERGED] refactor(app-router): extract buildPageElements into typed helper module #1022

Closed
opened 2026-05-06 13:11:36 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/1018
Author: @NathanDrake2406
Created: 5/2/2026
Status: Merged
Merged: 5/2/2026
Merged by: @james-elicx

Base: mainHead: extract/build-page-elements


📝 Commits (2)

  • f4c2ec6 refactor(app-router): extract buildPageElements into typed helper module
  • caced88 chore: register app-page-element-builder in knip entry list

📊 Changes

8 files changed (+540 additions, -741 deletions)

View changed files

📝 knip.ts (+1 -0)
📝 packages/vinext/src/entries/app-rsc-entry.ts (+11 -104)
packages/vinext/src/server/app-page-element-builder.ts (+188 -0)
📝 packages/vinext/src/server/app-page-route-wiring.tsx (+2 -2)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+42 -624)
tests/app-page-element-builder.test.ts (+283 -0)
📝 tests/app-router.test.ts (+4 -6)
📝 tests/entry-templates.test.ts (+9 -5)

📄 Description

What this changes

Extracts the buildPageElements function (the central element-construction path in the App Router RSC handler) from the generated entry template into a properly typed, independently testable module at server/app-page-element-builder.ts.

The generated entry now delegates to this module with a thin 12-line closure that passes per-app module references. No behavioural changes.

Why

The generated RSC entry template owned ~110 lines of orchestration logic inline: checking for page default exports, resolving page head metadata (including parallel route interception), building page React elements, and wiring everything into the nested layout + boundary tree via buildAppPageElements.

This had two problems:

  1. Untestable directly. Any change to the element-construction path could only be verified through snapshot diffs of the generated output, not through behavioural assertions about what the function produces.

  2. Violates the codegen principle. The generated entry should describe the app shape (route manifests, module references, thin wiring closures). Runtime behaviour — request/response orchestration, element tree construction, streaming — belongs in normal typed modules that can be reasoned about and tested independently.

Approach

  • New module server/app-page-element-builder.ts with fully typed options (BuildPageElementsOptions), a route shape (AppPageBuildRoute extending the existing AppPageRouteWiringRoute), and intercept types
  • Exports AppPageErrorModule and AppPageRouteWiringRoute from app-page-route-wiring.tsx so the new helper can reference them (previously local types)
  • The generated entry (app-rsc-entry.ts) replaces the inline function body with a delegation: return __buildPageElements({ route, params, routePath, pageRequest, ...generatedModuleRefs })
  • Removes now-unused imports (resolveAppPageHead, resolveActiveParallelRouteHeadInputs, markDynamicUsage, buildAppPageElements, createAppPageTreePath) from the generated entry template

Next.js source references

  • Component tree construction: create-component-tree.tsx — builds the nested React element tree with layouts, boundaries, slots, and templates (our buildAppPageElements)
  • Page head resolution: create-metadata.tsx — resolves metadata from layouts and pages including parallel route interception (our resolveAppPageHead)

Validation

  • New tests: tests/app-page-element-builder.test.ts — 9 focused unit tests covering:
    • Error payload construction when a page module has no default export
    • Interception context threading into route IDs
    • Root layout tree path computation for error payloads
    • Full element tree construction for pages with default exports
    • markDynamicUsage call when search params have content (and absence when empty)
    • Slot override wiring from interception opts
    • Parameter threading through matchedParams
    • Thenable params proxy behaviour (Promise + property access)
  • Updated tests: tests/entry-templates.test.ts — snapshot assertions updated to reflect the new delegation pattern (import, thin closure, absence of inlined helper logic)
  • Full check suite: vp check passes with zero lint, format, or type errors

Risks / follow-ups

  • Low risk: This is a pure refactor with no behavioural changes. All existing integration tests (app-router.test.ts) exercise the same code path through the generated entry.
  • The __interceptLayouts test in app-router.test.ts was updated to verify the delegation pattern instead of asserting on inlined helper imports that are now in the helper module.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/1018 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 5/2/2026 **Status:** ✅ Merged **Merged:** 5/2/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `extract/build-page-elements` --- ### 📝 Commits (2) - [`f4c2ec6`](https://github.com/cloudflare/vinext/commit/f4c2ec69c09c1995ec8b61dc921049d1d40eab14) refactor(app-router): extract buildPageElements into typed helper module - [`caced88`](https://github.com/cloudflare/vinext/commit/caced88d3c564576177e8c27d557992c6f57b256) chore: register app-page-element-builder in knip entry list ### 📊 Changes **8 files changed** (+540 additions, -741 deletions) <details> <summary>View changed files</summary> 📝 `knip.ts` (+1 -0) 📝 `packages/vinext/src/entries/app-rsc-entry.ts` (+11 -104) ➕ `packages/vinext/src/server/app-page-element-builder.ts` (+188 -0) 📝 `packages/vinext/src/server/app-page-route-wiring.tsx` (+2 -2) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+42 -624) ➕ `tests/app-page-element-builder.test.ts` (+283 -0) 📝 `tests/app-router.test.ts` (+4 -6) 📝 `tests/entry-templates.test.ts` (+9 -5) </details> ### 📄 Description ## What this changes Extracts the `buildPageElements` function (the central element-construction path in the App Router RSC handler) from the generated entry template into a properly typed, independently testable module at `server/app-page-element-builder.ts`. The generated entry now delegates to this module with a thin 12-line closure that passes per-app module references. No behavioural changes. ## Why The generated RSC entry template owned ~110 lines of orchestration logic inline: checking for page default exports, resolving page head metadata (including parallel route interception), building page React elements, and wiring everything into the nested layout + boundary tree via `buildAppPageElements`. This had two problems: 1. **Untestable directly.** Any change to the element-construction path could only be verified through snapshot diffs of the generated output, not through behavioural assertions about what the function produces. 2. **Violates the codegen principle.** The generated entry should describe the *app shape* (route manifests, module references, thin wiring closures). Runtime behaviour — request/response orchestration, element tree construction, streaming — belongs in normal typed modules that can be reasoned about and tested independently. ## Approach - New module `server/app-page-element-builder.ts` with fully typed options (`BuildPageElementsOptions`), a route shape (`AppPageBuildRoute` extending the existing `AppPageRouteWiringRoute`), and intercept types - Exports `AppPageErrorModule` and `AppPageRouteWiringRoute` from `app-page-route-wiring.tsx` so the new helper can reference them (previously local types) - The generated entry (`app-rsc-entry.ts`) replaces the inline function body with a delegation: `return __buildPageElements({ route, params, routePath, pageRequest, ...generatedModuleRefs })` - Removes now-unused imports (`resolveAppPageHead`, `resolveActiveParallelRouteHeadInputs`, `markDynamicUsage`, `buildAppPageElements`, `createAppPageTreePath`) from the generated entry template ## Next.js source references - **Component tree construction**: [create-component-tree.tsx](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/create-component-tree.tsx) — builds the nested React element tree with layouts, boundaries, slots, and templates (our `buildAppPageElements`) - **Page head resolution**: [create-metadata.tsx](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/create-metadata.tsx) — resolves metadata from layouts and pages including parallel route interception (our `resolveAppPageHead`) ## Validation - **New tests**: `tests/app-page-element-builder.test.ts` — 9 focused unit tests covering: - Error payload construction when a page module has no default export - Interception context threading into route IDs - Root layout tree path computation for error payloads - Full element tree construction for pages with default exports - `markDynamicUsage` call when search params have content (and absence when empty) - Slot override wiring from interception opts - Parameter threading through `matchedParams` - Thenable params proxy behaviour (Promise + property access) - **Updated tests**: `tests/entry-templates.test.ts` — snapshot assertions updated to reflect the new delegation pattern (import, thin closure, absence of inlined helper logic) - **Full check suite**: `vp check` passes with zero lint, format, or type errors ## Risks / follow-ups - **Low risk**: This is a pure refactor with no behavioural changes. All existing integration tests (app-router.test.ts) exercise the same code path through the generated entry. - The `__interceptLayouts` test in `app-router.test.ts` was updated to verify the delegation pattern instead of asserting on inlined helper imports that are now in the helper module. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:36 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#1022
No description provided.