[PR #750] [MERGED] feat: flat keyed payload for App Router layout persistence #818

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

📋 Pull Request Information

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

Base: mainHead: feat/layout-persistence-pr-2c


📝 Commits (10+)

  • 7f4f8eb Extract app page route wiring helpers
  • 5d8525b Add slot client primitives
  • be33773 Fix app page error boundary serialization
  • ca40d05 Fix client error boundary pathname reset
  • bddda39 Document Next.js error boundary verification
  • 38d33ca Merge local PR 2a into PR 2c base
  • 8c22db3 Merge local PR 2b into PR 2c base
  • d488978 Implement flat App Router payload for layout persistence
  • ec008fa fix: address review findings in flat payload implementation
  • 5395efc fix: normalize flat payload after use(), not before

📊 Changes

31 files changed (+2904 additions, -1083 deletions)

View changed files

📝 packages/vinext/src/entries/app-rsc-entry.ts (+89 -75)
📝 packages/vinext/src/routing/app-router.ts (+38 -47)
📝 packages/vinext/src/server/app-browser-entry.ts (+264 -140)
packages/vinext/src/server/app-browser-state.ts (+140 -0)
packages/vinext/src/server/app-elements.ts (+60 -0)
📝 packages/vinext/src/server/app-page-boundary-render.ts (+55 -2)
📝 packages/vinext/src/server/app-page-render.ts (+2 -2)
📝 packages/vinext/src/server/app-page-request.ts (+22 -5)
📝 packages/vinext/src/server/app-page-route-wiring.tsx (+390 -116)
packages/vinext/src/server/app-render-dependency.tsx (+65 -0)
📝 packages/vinext/src/server/app-ssr-entry.ts (+17 -4)
📝 packages/vinext/src/shims/slot.tsx (+27 -34)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+528 -420)
tests/app-browser-entry.test.ts (+221 -0)
tests/app-elements.test.ts (+78 -0)
📝 tests/app-page-boundary-render.test.ts (+110 -2)
📝 tests/app-page-request.test.ts (+13 -19)
📝 tests/app-page-route-wiring.test.ts (+393 -100)
tests/app-render-dependency.test.ts (+83 -0)
📝 tests/app-router.test.ts (+41 -2)

...and 11 more files

📄 Description

Summary

Switches the App Router from a monolithic ReactNode payload to a flat keyed Record<string, ReactNode> element map (#726, Phase 1 PR 2c). Layouts now persist across navigations — the server emits separate entries for each layout, template, page, and parallel slot, and the browser merges new entries into the existing map instead of replacing the entire tree.

This is the core cutover. Includes PRs 2a (client primitives) and 2b (route wiring extraction) as base commits.

What changed

  • ServerbuildAppPageElements() replaces the inline monolithic tree builder. Produces flat entries keyed by tree path (includes route groups), plus __route and __rootLayout metadata. All RSC render paths switched: normal routes, ISR, error/not-found/forbidden fallbacks, server actions
  • BrowseruseReducer-based router state with atomic elements + routeId + rootLayoutTreePath. Navigation/refresh/server actions/back-forward use merge semantics. HMR uses replace. Root layout switches trigger hard navigation (window.location.assign). URL state deferred until payload resolves (torn URL fix)
  • SSRVinextFlightRoot reads flat payload, validates __route, renders through ElementsContext + Slot. GlobalErrorBoundary, ServerInsertedHTMLContext, font injection preserved at same positions
  • Unmatched slots — wire marker "__VINEXT_UNMATCHED_SLOT__" normalized to UNMATCHED_SLOT Symbol after deserialization. Three states: absent key = persisted from soft nav, Symbol = unmatched (notFound), present null = valid render from default.tsx
  • ErrorBoundary — pathname-based reset added (same pattern as NotFoundBoundary), so error state clears on same-route navigations

What this does NOT do

Skip-header optimization, entry eviction/LRU, interception-context encoding, incremental per-entry streaming. Those are Phases 2-4.

Test plan

  • tests/slot.test.ts — wire marker normalization, three-state distinction
  • tests/app-elements.test.ts — normalize, __route invariant, __rootLayout validation
  • tests/app-browser-entry.test.ts — reducer merge/replace, root-layout hard nav, torn URL, refresh/back-forward/server-action merge
  • tests/app-page-route-wiring.test.ts — flat-map entries, tree-path IDs, per-slot segment providers, template keys
  • tests/error-boundary.test.ts — pathname reset preserved
  • tests/entry-templates.test.ts — snapshots updated for flat payload
  • tests/app-router.test.ts — integration test for flat payload contract
  • vp check — 0 lint/type errors

🔄 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/750 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 4/2/2026 **Status:** ✅ Merged **Merged:** 4/10/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `feat/layout-persistence-pr-2c` --- ### 📝 Commits (10+) - [`7f4f8eb`](https://github.com/cloudflare/vinext/commit/7f4f8ebb019ab08597e0eba0a978bf98f5e7745d) Extract app page route wiring helpers - [`5d8525b`](https://github.com/cloudflare/vinext/commit/5d8525b9f72a9f5df0054cf2b46bfaf0c08c9a97) Add slot client primitives - [`be33773`](https://github.com/cloudflare/vinext/commit/be33773470b44b3767e6f3b2ed4aecceefa075b4) Fix app page error boundary serialization - [`ca40d05`](https://github.com/cloudflare/vinext/commit/ca40d05f70e9fad6b5e557d5233cbf020573a081) Fix client error boundary pathname reset - [`bddda39`](https://github.com/cloudflare/vinext/commit/bddda39ac3ee2504bd99df2b71248024b5d2efe8) Document Next.js error boundary verification - [`38d33ca`](https://github.com/cloudflare/vinext/commit/38d33cacd0a2a914eff407910c4de8aa59015648) Merge local PR 2a into PR 2c base - [`8c22db3`](https://github.com/cloudflare/vinext/commit/8c22db320098bd5ef915923539193d2f5dcf6c1b) Merge local PR 2b into PR 2c base - [`d488978`](https://github.com/cloudflare/vinext/commit/d488978d6505d079f8cbc05793561a1625fa8e28) Implement flat App Router payload for layout persistence - [`ec008fa`](https://github.com/cloudflare/vinext/commit/ec008fa3adc43485c48d0a34deec9ffc76988ffd) fix: address review findings in flat payload implementation - [`5395efc`](https://github.com/cloudflare/vinext/commit/5395efc5028671e75d14822fedf215d0e5f83e51) fix: normalize flat payload after use(), not before ### 📊 Changes **31 files changed** (+2904 additions, -1083 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/entries/app-rsc-entry.ts` (+89 -75) 📝 `packages/vinext/src/routing/app-router.ts` (+38 -47) 📝 `packages/vinext/src/server/app-browser-entry.ts` (+264 -140) ➕ `packages/vinext/src/server/app-browser-state.ts` (+140 -0) ➕ `packages/vinext/src/server/app-elements.ts` (+60 -0) 📝 `packages/vinext/src/server/app-page-boundary-render.ts` (+55 -2) 📝 `packages/vinext/src/server/app-page-render.ts` (+2 -2) 📝 `packages/vinext/src/server/app-page-request.ts` (+22 -5) 📝 `packages/vinext/src/server/app-page-route-wiring.tsx` (+390 -116) ➕ `packages/vinext/src/server/app-render-dependency.tsx` (+65 -0) 📝 `packages/vinext/src/server/app-ssr-entry.ts` (+17 -4) 📝 `packages/vinext/src/shims/slot.tsx` (+27 -34) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+528 -420) ➕ `tests/app-browser-entry.test.ts` (+221 -0) ➕ `tests/app-elements.test.ts` (+78 -0) 📝 `tests/app-page-boundary-render.test.ts` (+110 -2) 📝 `tests/app-page-request.test.ts` (+13 -19) 📝 `tests/app-page-route-wiring.test.ts` (+393 -100) ➕ `tests/app-render-dependency.test.ts` (+83 -0) 📝 `tests/app-router.test.ts` (+41 -2) _...and 11 more files_ </details> ### 📄 Description ## Summary Switches the App Router from a monolithic `ReactNode` payload to a flat keyed `Record<string, ReactNode>` element map (#726, Phase 1 PR 2c). Layouts now persist across navigations — the server emits separate entries for each layout, template, page, and parallel slot, and the browser merges new entries into the existing map instead of replacing the entire tree. This is the core cutover. Includes PRs 2a (client primitives) and 2b (route wiring extraction) as base commits. ### What changed - **Server** — `buildAppPageElements()` replaces the inline monolithic tree builder. Produces flat entries keyed by tree path (includes route groups), plus `__route` and `__rootLayout` metadata. All RSC render paths switched: normal routes, ISR, error/not-found/forbidden fallbacks, server actions - **Browser** — `useReducer`-based router state with atomic `elements` + `routeId` + `rootLayoutTreePath`. Navigation/refresh/server actions/back-forward use merge semantics. HMR uses replace. Root layout switches trigger hard navigation (`window.location.assign`). URL state deferred until payload resolves (torn URL fix) - **SSR** — `VinextFlightRoot` reads flat payload, validates `__route`, renders through `ElementsContext` + `Slot`. GlobalErrorBoundary, ServerInsertedHTMLContext, font injection preserved at same positions - **Unmatched slots** — wire marker `"__VINEXT_UNMATCHED_SLOT__"` normalized to `UNMATCHED_SLOT` Symbol after deserialization. Three states: absent key = persisted from soft nav, Symbol = unmatched (notFound), present null = valid render from default.tsx - **ErrorBoundary** — pathname-based reset added (same pattern as NotFoundBoundary), so error state clears on same-route navigations ### What this does NOT do Skip-header optimization, entry eviction/LRU, interception-context encoding, incremental per-entry streaming. Those are Phases 2-4. ## Test plan - [x] `tests/slot.test.ts` — wire marker normalization, three-state distinction - [x] `tests/app-elements.test.ts` — normalize, `__route` invariant, `__rootLayout` validation - [x] `tests/app-browser-entry.test.ts` — reducer merge/replace, root-layout hard nav, torn URL, refresh/back-forward/server-action merge - [x] `tests/app-page-route-wiring.test.ts` — flat-map entries, tree-path IDs, per-slot segment providers, template keys - [x] `tests/error-boundary.test.ts` — pathname reset preserved - [x] `tests/entry-templates.test.ts` — snapshots updated for flat payload - [x] `tests/app-router.test.ts` — integration test for flat payload contract - [x] `vp check` — 0 lint/type errors --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:10:16 +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#818
No description provided.