mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[GH-ISSUE #726] App Router layout persistence: Route Manifest and Navigation Planner architecture #155
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#155
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?
Originally created by @NathanDrake2406 on GitHub (Mar 31, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/726
App Router layout persistence: Route Manifest and Navigation Planner architecture
What This Issue Is Really About
The flat keyed
AppElementspayload solved the first layout-persistence problem: the browser can merge layout, page, template, slot, and route entries instead of replacing the whole App Router tree.That bridge should stay.
But the flat map must not become the router's brain.
Vinext now needs to move route meaning out of:
The target architecture is not a giant rewrite. It is a controlled migration from implicit meaning to owned decisions:
The discipline is:
But that law has an important qualifier:
Correctness is mandatory. Performance comes from narrow, proven reuse, not optimism.
Architecture Overview
Current Status
The first layout-persistence milestone has landed.
Vinext currently has:
The current code also already contains the future architecture in scattered form:
The first implementation step is not to replace this system. It is to make these existing ownership seams explicit, typed, and testable.
Main Architectural Concerns From Adversarial Review
1. Scope Risk
This issue must not be treated as a single implementation epic that lands compiler facts, lifecycle, cache coherence, runtime storage, skip transport, streaming, and Activity preservation together.
The sane path is:
Avoid PRs that only add large unused type systems. Early PRs must either:
2. NavigationPlanner Must Stay Small
navigationPlanner.plan()should be a small semantic planner, not the whole router.It can decide:
It must not do:
A pure reducer is only realistic if async facts re-enter as explicit events.
The model is two phase:
The planner must not pretend to know render-time facts before render. Redirects,
notFound, boundary errors, dynamic request API reads, cacheability downgrades, stream failures, and server-action revalidation effects are observed outcomes that feed back into the planner as events.3. Lifecycle Authority Comes First
The most concrete current pressure is stale async work committing visible state.
activeNavigationIdis not strong enough for same-URL refresh and server-action races. Same URL does not mean same visible world. Vinext needs avisibleCommitVersionand one lifecycle owner before broad compiler/cache work.The lifecycle controller should wrap the existing candidate-commit seam first:
The first controller should consolidate existing behavior. It should not rewrite all router semantics.
4. Safe Fallbacks Can Become A Performance Trap
The law says uncertainty must not degrade to reuse. That is correct.
But a naive implementation can collapse cache hit rate by treating routine runtime observations as global uncertainty. For example, a route that reads a generic header or cookie should not automatically poison every cache class unless that input actually affects the output and can be modeled safely.
Rules:
Do not optimize with probabilistic reuse. Do optimize by keeping proof scopes narrow.
5. Skip Transport Can Backfire
ClientReuseManifestis an untrusted hint. It must not become a server CPU or storage-IO amplifier.Skip transport must start with the cheapest proven class, likely static layout entries:
Hard rule:
Abuse limits are protocol requirements:
Skip transport is a later optimization, not a prerequisite for the router spine.
6. Deployment Compatibility Must Be Algebra, Not Strict Equality Everywhere
Strict
graphVersionanddeploymentVersionequality is too brittle for rolling deploys, multi-region edges, previews, canaries, and rollback windows.The architecture needs a deployment compatibility protocol before hard-navigation decisions depend on version mismatches.
Required concepts:
A version mismatch may require a hard navigation in some cases. It must not produce reload loops or break SPA behavior during normal rolling deploys.
7. Runtime Profile Must Not Own Semantics
Cloudflare Workers are the primary production target for vinext.
That matters. The semantic core must be binding-free. Cloudflare-specific storage, Durable Objects, KV, Cache API, Queues, or R2 integration belongs behind a runtime profile boundary.
Runtime profile may execute:
Runtime profile must not decide:
Runtime storage hits are not semantic proof by themselves.
8. Streaming And Activity Are Follow-Up Specs
Streaming chunks and Activity/hidden-route preservation are real future needs, but they should not block the lifecycle spine.
Before streaming reveal is implemented, the architecture must specify:
Before Activity preservation is implemented, it must specify:
Until then, these remain out of scope for the first migration stage.
9. Correctness Oracle Without Process Theater
Every semantic PR must name its oracle:
This is required because vinext aims to match Next public behavior unless a divergence is deliberate.
But do not require full
NavigationTrace, cache cardinality proof, and runtime coherence proof for every small PR. The process should scale with the semantic risk.Minimal early requirement:
10. Lock Criteria From Final Review
Lock the direction, not every future type shape. The architecture is only useful if future PRs cannot rebuild the old implicit router under cleaner names.
Non-negotiable lock criteria:
Core Ownership Model
1. AppElementsWire
Implementation name:
AppElementsWire.Owns:
Does not own:
The flat payload is how data travels. It is not how the router thinks.
2. RouterState And RouteSnapshot
Implementation names:
RouterState,RouteSnapshot.Route state owns visible continuity.
It must keep together the facts that change together:
State must not be reconstructed from wire keys.
3. Navigation Events
Implementation name:
NavigationEvent.Every router input becomes an event.
Minimum v0 event set:
Async result events carry causal proof:
A result is not allowed to commit because it finished. It may commit only if lifecycle still authorizes it.
4. NavigationPlanner
Implementation name:
navigationPlanner.plan().The planner owns route semantics, but only at the semantic planning and interpretation layer.
Shape:
Minimum v0 output:
Promote
SegmentOp,SlotOp, richer effect sets, and cache-specific decisions only when a real semantic slice needs them. Do not ship a fat reducer before one current path proves the spine.NavigationTracemust be reason-code based: compact codes plus structured fields that explain the decision without becoming another router object graph.The planner proposes. The lifecycle gate approves. The executor applies.
The planner must be boring at first. Boring is good here.
5. NavigationLifecycleController
Implementation name:
NavigationLifecycleController.Owns:
Operation lanes:
Terminal states:
Rules:
6. CommitDecision, ApprovedVisibleCommit, And BrowserDelta
Visible browser mutation must go through one approved commit transaction.
An approved visible commit is the only path that may mutate:
There must be exactly one place where
visibleCommitVersion++happens. Everything else is either candidate work, rejected work, hard navigation, or non-visible cache seeding.Hard navigation is terminal. It should not sit beside normal browser deltas.
transitionModeis part of commit authority. Shell code may execute the mode, but must not reinterpret the semantic decision.7. Route Facts Compiler
Implementation names:
RouteManifestBuilder,RouteManifest,StaticSegmentGraph.The compiler owns build-time topology and stable facts.
It compiles facts the current implementation already knows implicitly:
The compiler should not emit a full transition automaton. Runtime transition decisions remain in the planner.
Build-time classifications are hints. Runtime observations can always downgrade cacheability.
8. Render Observation Protocol
Implementation names:
RenderOutcome,RenderObservation.This is the missing bridge between pure planning and real execution.
A render must report what actually happened before cache write, reuse proof, streaming reveal, or visible commit approval can rely on it:
Render observations are not side-channel decisions. They re-enter the planner/lifecycle/cache model as explicit events or result metadata.
Absence of a recorded dynamic read is not enough for reuse unless the render scope was observed completely enough to prove that absence. A reusable output needs a scoped bill of health: what it did observe, what it did not observe, what boundary outcome it produced, and which artifact envelope it belongs to.
9. CacheVariant And Resource Dependencies
Implementation names:
CacheVariant,ResourceDependencyGraph.Cache identity answers:
Resource dependencies answer:
Start narrow. Do not attempt full cache algebra before the lifecycle spine exists.
Cache v1 should cover:
Mandatory cache dimension rules:
Mandatory cache budget rules:
If a route exceeds its variant ceiling, the fallback is private, uncacheable, or fresh render for the affected output. Measurement is not enough without an enforcement path.
10. Artifact Compatibility Envelope
Implementation name:
ArtifactCompatibilityEnvelope.Every payload or artifact that may later participate in cache reuse or skip transport must carry compatibility metadata before those systems rely on it:
Cache coherence and skip transport can remain disabled at first. The envelope should land early so old-client/new-server, new-client/old-server, rolling deploy, canary, and rollback behavior has a protocol instead of a boolean equality check.
11. Runtime Profile Boundary
Implementation names:
RuntimeProfile,CacheCoordinator,ArtifactStore.Runtime profiles execute approved work.
Cloudflare is the first production target, but the semantic core must not import raw runtime bindings.
Generic layers:
Hot-path rule:
Validity Rules
A reduction or runtime action is invalid if:
Navigation Kind Semantics
The planner must branch on explicit navigation kind, not infer it from payload shape.
Implementation Plan
This is not a fixed PR count. Split into small, reviewable PRs.
Governing sequence:
Layer 0: Keep The Landed Foundation
Do not redo the flat payload milestone.
Keep:
Layer 1: Lifecycle Spine First
Goal: make visible commit authority explicit while preserving behavior.
Deliverables:
Acceptance:
Layer 2: Fence AppElementsWire
Goal: stop semantic meaning from spreading through flat wire keys.
Deliverables:
No semantic promotion yet.
Layer 3: Minimal Route Manifest
Goal: compile the facts needed for the first promotions.
Deliverables:
Not yet:
Layer 4: NavigationPlanner v0
Goal: route one existing navigation path through the new ownership boundaries while preserving behavior.
Deliverables:
The planner must remain small and pure. Async facts re-enter as events.
Layer 5: Promote First Semantic Decisions
Promote one decision area per PR and delete the old writer in the same PR.
Recommended order:
Each PR must state:
Layer 6: Cache Coherence v1
Goal: make existing cache reuse safer before making it more aggressive.
Deliverables:
Do not enable broad skip transport in this layer.
Layer 7: Proof-Backed Skip Transport v1
Goal: reduce server work and bytes only where proof is cheap.
Initial eligible class:
Deliverables:
Layer 8: Runtime Profile v1
Goal: keep runtime execution behind typed contracts after the semantic spine exists.
Deliverables:
The semantic core stays binding-free.
Later Layers: Streaming And Activity
Separate follow-up specs required for:
Do not implement these before lifecycle authority, Route Manifest data, and cache coherence v1 exist.
Test Strategy
The most important tests are sequence tests and hostile timelines, not giant snapshots.
Generate apps with:
Generate event sequences like:
Required invariants:
Hot Path Budget
Track performance by route/cache class. Do not claim the architecture is faster merely because it is more formal.
At minimum, measure:
Performance claims should be based on work avoided:
Success Criteria
This migration succeeds when:
Out Of Scope For The First Migration Stage
Do not add yet:
Related Issues / PRs / References
Final Statement
This architecture is not trying to make a prettier flat map.
It is trying to make the router less magical without making it slower, more brittle, or impossible to ship.
The NavigationPlanner should be small. Lifecycle authority should land early. Cache proof should start narrow. Skip transport should prove that it saves work before it is trusted. Deployment compatibility must be designed for rolling edges. Runtime profiles execute approved work; they do not own route meaning.
The discipline remains:
The implementation discipline is just as important:
@NathanDrake2406 commented on GitHub (May 4, 2026):
@NathanDrake2406 commented on GitHub (May 4, 2026):