mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #767] [MERGED] feat: static/dynamic layout detection for skip-header optimization #829
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#829
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/767
Author: @NathanDrake2406
Created: 4/3/2026
Status: ✅ Merged
Merged: 4/13/2026
Merged by: @james-elicx
Base:
main← Head:feat/layout-persistence-pr-6-static-dynamic📝 Commits (10+)
50fa22ffeat: add classifyLayoutSegmentConfig for layout segment config detection1b85117feat: add module graph layout classification (Layer 2)8604239feat: per-layout dynamic detection in probe phase (Layer 3)6d99a42feat: add __layoutFlags payload metadata and thread through router statebfbbd59refactor: group classification options into single LayoutClassificationOptions type4392f14fix: default to dynamic flag when layout probe throws non-special errorbcdc760refactor: replace parallel arrays with LayoutEntry struct, tighten names8e09f70style: fix prettier formatting1faf2cdRevert "style: fix prettier formatting"5e7cf12Reapply "style: fix prettier formatting"📊 Changes
14 files changed (+939 additions, -40 deletions)
View changed files
➕
packages/vinext/src/build/layout-classification.ts(+115 -0)📝
packages/vinext/src/build/report.ts(+31 -0)📝
packages/vinext/src/server/app-browser-entry.ts(+7 -0)📝
packages/vinext/src/server/app-browser-state.ts(+6 -1)📝
packages/vinext/src/server/app-elements.ts(+29 -1)📝
packages/vinext/src/server/app-page-execution.ts(+71 -11)📝
packages/vinext/src/server/app-page-probe.ts(+21 -5)📝
packages/vinext/src/server/app-page-render.ts(+3 -3)📝
tests/app-browser-entry.test.ts(+50 -0)📝
tests/app-elements.test.ts(+29 -0)📝
tests/app-page-execution.test.ts(+198 -3)📝
tests/app-page-probe.test.ts(+98 -16)📝
tests/build-report.test.ts(+37 -0)➕
tests/layout-classification.test.ts(+244 -0)📄 Description
Summary
Part of #726 (Phase 3, PR 1). Adds a three-layer classification system that determines whether each layout in an App Router route tree is static or dynamic. This metadata is the foundation for PR 7's skip-header optimization, which avoids re-rendering static layouts on RSC navigations. Wiring is done in PR 768.
Three-layer classification
Layer 1 — Segment config (
classifyLayoutSegmentConfiginreport.ts). Readsexport const dynamic/export const revalidatefrom layout source files. Unlike page classification, positiverevalidatevalues return null (ISR is a page concept) — only the extremes (0→ dynamic,Infinity→ static) are decisive.Layer 2 — Module graph (
classifyLayoutByModuleGraphinlayout-classification.ts). BFS traversal of each layout's dependency tree via aModuleInfoProviderabstraction over Vite'sthis.getModuleInfo. If no transitive dynamic shim import (headers, cache, server) is found, the layout is provably static without any runtime cost.Layer 3 — Runtime probe (extended
probeAppPageLayoutsinapp-page-execution.ts). For layouts that import dynamic shims but may not call them on every request, the probe runs the layout function with isolated dynamic scope tracking. TherunWithIsolatedDynamicScopecallback is injected from the RSC entry, keeping the execution module decoupled from ALS internals.Wire format
__layoutFlagsmetadata key in the RSC payload:Record<string, "s" | "d">mapping layout IDs to compact flags. Follows the established pattern of__route,__rootLayout,__interceptionContext.On client-side navigation, the router merges new flags with existing ones (navigate) or replaces them entirely (replace), building up cumulative knowledge of which layouts are safe to skip.
Type design
LayoutClassificationOptionsgroups the three classification fields (getLayoutId,runWithIsolatedDynamicScope,buildTimeClassifications) into a single optional object. This makes the all-or-nothing invariant type-safe — you either provide the full classification context or nothing.LayoutFlagsis canonically defined inapp-elements.tsand re-exported fromapp-page-execution.ts.readAppElementsMetadataacceptsRecord<string, unknown>since the RSC payload carries heterogeneous values.parseLayoutFlagsvalidates at the wire boundary using a type predicate guard.New files
packages/vinext/src/build/layout-classification.tstests/layout-classification.test.tsTest plan
tests/build-report.test.ts— 7 tests forclassifyLayoutSegmentConfigtests/layout-classification.test.ts— 13 tests for module graph traversal + combined classificationtests/app-page-execution.test.ts— 5 tests for per-layout probe classification (dynamic detection, build-time skip, backward compat, error default)tests/app-page-probe.test.ts— 2 tests for layout flags propagation + special error handling with classificationtests/app-elements.test.ts— 2 tests for__layoutFlagsmetadata parsing + backward compattests/app-browser-entry.test.ts— 2 tests for layoutFlags merge (navigate) and replace semanticstests/app-router.test.ts— 276 integration tests pass with zero regressionsvp check— format, lint, type checks all cleanRefs #726
Closes #756
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.