mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #750] [MERGED] feat: flat keyed payload for App Router layout persistence #818
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#818
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 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:
main← Head:feat/layout-persistence-pr-2c📝 Commits (10+)
7f4f8ebExtract app page route wiring helpers5d8525bAdd slot client primitivesbe33773Fix app page error boundary serializationca40d05Fix client error boundary pathname resetbddda39Document Next.js error boundary verification38d33caMerge local PR 2a into PR 2c base8c22db3Merge local PR 2b into PR 2c based488978Implement flat App Router payload for layout persistenceec008fafix: address review findings in flat payload implementation5395efcfix: 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
ReactNodepayload to a flat keyedRecord<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
buildAppPageElements()replaces the inline monolithic tree builder. Produces flat entries keyed by tree path (includes route groups), plus__routeand__rootLayoutmetadata. All RSC render paths switched: normal routes, ISR, error/not-found/forbidden fallbacks, server actionsuseReducer-based router state with atomicelements+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)VinextFlightRootreads flat payload, validates__route, renders throughElementsContext+Slot. GlobalErrorBoundary, ServerInsertedHTMLContext, font injection preserved at same positions"__VINEXT_UNMATCHED_SLOT__"normalized toUNMATCHED_SLOTSymbol after deserialization. Three states: absent key = persisted from soft nav, Symbol = unmatched (notFound), present null = valid render from default.tsxWhat 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 distinctiontests/app-elements.test.ts— normalize,__routeinvariant,__rootLayoutvalidationtests/app-browser-entry.test.ts— reducer merge/replace, root-layout hard nav, torn URL, refresh/back-forward/server-action mergetests/app-page-route-wiring.test.ts— flat-map entries, tree-path IDs, per-slot segment providers, template keystests/error-boundary.test.ts— pathname reset preservedtests/entry-templates.test.ts— snapshots updated for flat payloadtests/app-router.test.ts— integration test for flat payload contractvp check— 0 lint/type errors🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.