[PR #767] [MERGED] feat: static/dynamic layout detection for skip-header optimization #829

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

📋 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: mainHead: feat/layout-persistence-pr-6-static-dynamic


📝 Commits (10+)

  • 50fa22f feat: add classifyLayoutSegmentConfig for layout segment config detection
  • 1b85117 feat: add module graph layout classification (Layer 2)
  • 8604239 feat: per-layout dynamic detection in probe phase (Layer 3)
  • 6d99a42 feat: add __layoutFlags payload metadata and thread through router state
  • bfbbd59 refactor: group classification options into single LayoutClassificationOptions type
  • 4392f14 fix: default to dynamic flag when layout probe throws non-special error
  • bcdc760 refactor: replace parallel arrays with LayoutEntry struct, tighten names
  • 8e09f70 style: fix prettier formatting
  • 1faf2cd Revert "style: fix prettier formatting"
  • 5e7cf12 Reapply "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.

Stacked on #755 (PR 5). Review only the last 6 commits.

Three-layer classification

Segment config: force-dynamic?           → DYNAMIC (final)
Segment config: force-static?            → STATIC (final)
Module graph: no dynamic shim imports?   → STATIC (proven, skip probe)
Module graph: has dynamic shim imports?  → run probe at request time
Probe: dynamicUsageDetected = true?      → DYNAMIC (observed)
Probe: dynamicUsageDetected = false?     → STATIC (observed)

Layer 1 — Segment config (classifyLayoutSegmentConfig in report.ts). Reads export const dynamic / export const revalidate from layout source files. Unlike page classification, positive revalidate values return null (ISR is a page concept) — only the extremes (0 → dynamic, Infinity → static) are decisive.

Layer 2 — Module graph (classifyLayoutByModuleGraph in layout-classification.ts). BFS traversal of each layout's dependency tree via a ModuleInfoProvider abstraction over Vite's this.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 probeAppPageLayouts in app-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. The runWithIsolatedDynamicScope callback is injected from the RSC entry, keeping the execution module decoupled from ALS internals.

Wire format

__layoutFlags metadata 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

LayoutClassificationOptions groups 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.

LayoutFlags is canonically defined in app-elements.ts and re-exported from app-page-execution.ts.

readAppElementsMetadata accepts Record<string, unknown> since the RSC payload carries heterogeneous values. parseLayoutFlags validates at the wire boundary using a type predicate guard.

New files

File Purpose
packages/vinext/src/build/layout-classification.ts Module graph traversal + combined classification
tests/layout-classification.test.ts 13 tests for module graph + combined classification

Test plan

  • tests/build-report.test.ts — 7 tests for classifyLayoutSegmentConfig
  • tests/layout-classification.test.ts — 13 tests for module graph traversal + combined classification
  • tests/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 classification
  • tests/app-elements.test.ts — 2 tests for __layoutFlags metadata parsing + backward compat
  • tests/app-browser-entry.test.ts — 2 tests for layoutFlags merge (navigate) and replace semantics
  • tests/app-router.test.ts — 276 integration tests pass with zero regressions
  • vp check — format, lint, type checks all clean
vp test run tests/build-report.test.ts -t "classifyLayoutSegmentConfig"
vp test run tests/layout-classification.test.ts
vp test run tests/app-page-execution.test.ts tests/app-page-probe.test.ts
vp test run tests/app-elements.test.ts tests/app-browser-entry.test.ts
vp test run tests/app-router.test.ts
vp check

Refs #726
Closes #756


🔄 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/767 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 4/3/2026 **Status:** ✅ Merged **Merged:** 4/13/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `feat/layout-persistence-pr-6-static-dynamic` --- ### 📝 Commits (10+) - [`50fa22f`](https://github.com/cloudflare/vinext/commit/50fa22fab70a229e71a1af8248f87db2048c9993) feat: add classifyLayoutSegmentConfig for layout segment config detection - [`1b85117`](https://github.com/cloudflare/vinext/commit/1b85117289a80709d2e26407e37a0c13671f4b42) feat: add module graph layout classification (Layer 2) - [`8604239`](https://github.com/cloudflare/vinext/commit/86042390f4437cc97cae6758049048b427f3205e) feat: per-layout dynamic detection in probe phase (Layer 3) - [`6d99a42`](https://github.com/cloudflare/vinext/commit/6d99a42646038aa56b301718ae19d7e67ce24707) feat: add __layoutFlags payload metadata and thread through router state - [`bfbbd59`](https://github.com/cloudflare/vinext/commit/bfbbd593dce6f19e37399e37195107fc893a5d43) refactor: group classification options into single LayoutClassificationOptions type - [`4392f14`](https://github.com/cloudflare/vinext/commit/4392f14512039026f230df0d19e4e206596ed21a) fix: default to dynamic flag when layout probe throws non-special error - [`bcdc760`](https://github.com/cloudflare/vinext/commit/bcdc760aed179b84825063a6e195da651c8be5bb) refactor: replace parallel arrays with LayoutEntry struct, tighten names - [`8e09f70`](https://github.com/cloudflare/vinext/commit/8e09f70b8039cd43b80839b637523f6b9be5d671) style: fix prettier formatting - [`1faf2cd`](https://github.com/cloudflare/vinext/commit/1faf2cd0810074fee16bba52f2bd6ff1d2767497) Revert "style: fix prettier formatting" - [`5e7cf12`](https://github.com/cloudflare/vinext/commit/5e7cf128191aa8354d491c6d9fbcaa40b7ca6f01) Reapply "style: fix prettier formatting" ### 📊 Changes **14 files changed** (+939 additions, -40 deletions) <details> <summary>View changed files</summary> ➕ `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) </details> ### 📄 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. > **Stacked on #755** (PR 5). Review only the last 6 commits. ### Three-layer classification ``` Segment config: force-dynamic? → DYNAMIC (final) Segment config: force-static? → STATIC (final) Module graph: no dynamic shim imports? → STATIC (proven, skip probe) Module graph: has dynamic shim imports? → run probe at request time Probe: dynamicUsageDetected = true? → DYNAMIC (observed) Probe: dynamicUsageDetected = false? → STATIC (observed) ``` **Layer 1 — Segment config** (`classifyLayoutSegmentConfig` in `report.ts`). Reads `export const dynamic` / `export const revalidate` from layout source files. Unlike page classification, positive `revalidate` values return null (ISR is a page concept) — only the extremes (`0` → dynamic, `Infinity` → static) are decisive. **Layer 2 — Module graph** (`classifyLayoutByModuleGraph` in `layout-classification.ts`). BFS traversal of each layout's dependency tree via a `ModuleInfoProvider` abstraction over Vite's `this.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 `probeAppPageLayouts` in `app-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. The `runWithIsolatedDynamicScope` callback is injected from the RSC entry, keeping the execution module decoupled from ALS internals. ### Wire format `__layoutFlags` metadata 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 `LayoutClassificationOptions` groups 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. `LayoutFlags` is canonically defined in `app-elements.ts` and re-exported from `app-page-execution.ts`. `readAppElementsMetadata` accepts `Record<string, unknown>` since the RSC payload carries heterogeneous values. `parseLayoutFlags` validates at the wire boundary using a type predicate guard. ### New files | File | Purpose | |------|---------| | `packages/vinext/src/build/layout-classification.ts` | Module graph traversal + combined classification | | `tests/layout-classification.test.ts` | 13 tests for module graph + combined classification | ## Test plan - [x] `tests/build-report.test.ts` — 7 tests for `classifyLayoutSegmentConfig` - [x] `tests/layout-classification.test.ts` — 13 tests for module graph traversal + combined classification - [x] `tests/app-page-execution.test.ts` — 5 tests for per-layout probe classification (dynamic detection, build-time skip, backward compat, error default) - [x] `tests/app-page-probe.test.ts` — 2 tests for layout flags propagation + special error handling with classification - [x] `tests/app-elements.test.ts` — 2 tests for `__layoutFlags` metadata parsing + backward compat - [x] `tests/app-browser-entry.test.ts` — 2 tests for layoutFlags merge (navigate) and replace semantics - [x] `tests/app-router.test.ts` — 276 integration tests pass with zero regressions - [x] `vp check` — format, lint, type checks all clean ```bash vp test run tests/build-report.test.ts -t "classifyLayoutSegmentConfig" vp test run tests/layout-classification.test.ts vp test run tests/app-page-execution.test.ts tests/app-page-probe.test.ts vp test run tests/app-elements.test.ts tests/app-browser-entry.test.ts vp test run tests/app-router.test.ts vp check ``` Refs #726 Closes #756 --- <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:21 +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#829
No description provided.