You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Updated 2026-04-30: this is the governing architecture for the App Router layout-persistence follow-up. It supersedes the original flat-map-first roadmap and earlier fixed-PR-count wording where they conflict.
The flat AppElements payload 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.
The next problem is semantic authority.
Vinext needs to stop route meaning from leaking into wire keys, cache suffixes, local navigation guards, and missing payload entries. Future features like skip/reuse, streaming, prefetch, server actions, refresh, Activity preservation, and Cloudflare cache coherence need one clear source of truth.
This architecture makes Vinext behave like:
a compiler for route facts
a transaction system for navigation
a cache-coherence system for reuse
a flat transport shell for payload delivery
a React tree projection at the end
The law is simple:
No proof, no reuse.
No proof, no skip.
No proof, no visible commit.
No proof, render fresh.
Current Status
The first layout-persistence milestone has landed.
Vinext no longer needs to replace the whole App Router RSC tree on every client navigation. The current flat keyed AppElements payload gives us a practical bridge:
The next phase is about separating transport from meaning. The flat map is useful for payload delivery, merge buffers, compatibility, and encoding. It is not the right place to decide topology, slot preservation, interception, cache identity, root-layout transitions, skip/reuse, streaming, or stale async commit behavior.
The target architecture is:
Compiler-backed router kernel
+ navigation transaction authority
+ data dependency / resource graph
+ semantic cache-coherence model
+ Cloudflare-first runtime profile
+ flat wire transport
+ React tree projection
One-line version:
Compile stable facts, reduce live events, gate commits, prove cache reuse, execute first-class on Cloudflare, keep the wire shell dumb.
Problem
App Router behavior depends on more than the URL.
A client navigation can preserve layouts. A parallel route slot can remain mounted even when it is not represented in the new URL. An intercepted route can render as a modal during soft navigation, but as a full page on refresh or direct load. A refresh can target the same URL but still need a new visible commit. A server action can redirect, revalidate, patch, or force a refresh. A prefetch can complete after a newer navigation and must not mutate visible UI. A streamed chunk can arrive late and belong to a dead navigation.
A missing map key might mean preserve, delete, default, skipped, cached, failed, streamed later, stale, or irrelevant. Once absence starts carrying semantics, every feature adds another special case. That is how routers become hard to debug.
The architecture should make meaning explicit.
First Principles
The filesystem tells us what can exist. Runtime state tells us what currently exists. Events tell us what happened. The kernel decides what those facts mean. The lifecycle controller decides whether this result is still allowed to become visible. The cache model proves whether reuse is safe. The Cloudflare runtime makes proven reuse fast. The wire format carries payloads. React receives a tree at the end.
Core rules:
Transport moves data.
Topology describes structure.
State describes continuity.
Events describe time.
The kernel decides route meaning.
The lifecycle controller decides temporal commit authority.
Commit decisions and browser deltas mutate visibility.
CacheVariant defines equivalence; ResourceDependencyGraph defines invalidation and ownership.
InvalidationScope defines staleness.
The Cloudflare runtime implements the hot path.
Projection builds the React tree.
Any part doing more than its job will eventually become a second router.
The safety/performance law:
No proof, no reuse.
No proof, no skip.
No proof, no visible commit.
No proof, render fresh.
Uncertainty must degrade to a safe outcome:
fresh render
cache miss
discarded chunk
cache-seed only
noCommit
hard navigation
Uncertainty must never degrade to reuse.
Performance should come from certainty, not optimistic reuse:
compiler emits stable facts
-> kernel proves reuse cheaply
-> cache coordinator proves artifact compatibility
-> server skips render work
-> server sends fewer RSC bytes
-> Cloudflare runtime serves approved artifacts from the fastest safe layer
Architecture Overview
flowchart TD
subgraph Build["Build time"]
A["Developer app/ filesystem"] --> B["Build-time compiler"]
B --> C["RouteSemanticIR"]
C --> D["CompiledRouteSchema / StaticSegmentGraph"]
C --> G["DataDependencyIR / ResourceDependencyGraph"]
D --> E["graphVersion + graph-minted IDs + route fact tables"]
end
subgraph Decision["Router decision path"]
U["User intent / async result"] --> EV["RouterEvent"]
S["RouterState / RouteSnapshot"] --> K["routerKernel.reduce"]
E --> K
G --> K
EV --> K
K --> R["RouterReduction / CommitProposal"]
R --> L["NavigationLifecycleController"]
L -->|approved| X["RouterExecutor / shell"]
L -->|stale / aborted / superseded| N["reject or cache-seed only"]
L -->|cross-root / incompatible graph| H["hard navigate"]
end
subgraph ServerEncode["Server encode path"]
X --> SR["RSC render / payload materialization"]
SR --> WE["AppElementsWire encode"]
WE --> NET["network payload"]
end
subgraph ClientCommit["Client decode + projection path"]
NET --> WD["AppElementsWire decode"]
WD --> ES["ElementStore"]
X --> BD["approved BrowserDelta application"]
ES --> P["ReactProjection"]
P --> UI["Visible UI"]
BD --> UI
end
X --> CC["CacheCoordinator"]
G --> CC
CC --> CF["Cloudflare runtime profile"]
CF --> WC["Workers Cache API hot local cache"]
CF --> R2["R2 immutable artifacts"]
CF --> DO["Durable Objects SQLite transaction shards"]
CF --> Q["Queues background work"]
CF --> KV["KV hints/config only"]
These hints are not permission to be reckless. Runtime outcomes can always downgrade cacheability. If a render observes cookies, headers, auth, draft mode, dynamic fetches, thrown boundaries, or other dynamic inputs, the cache model must encode a safe dimension or mark the response private/uncacheable.
Compile facts, not every possible transition. A full generated transition automaton is not the next move. The compiler should produce stable facts, graph structure, canonical constructors, and conservative classifications. Runtime transition decisions remain in the kernel.
1b. DataDependencyIR / ResourceDependencyGraph
RouteSemanticIR says what can exist. ResourceDependencyGraph says what a render, payload, cached artifact, stream chunk, prefetch, or hidden route depends on and what can invalidate it.
CacheVariant proves whether two outputs are equivalent.
ResourceDependencyGraph proves what can invalidate that equivalence and who owns the resource lifetime.
It must not decide visible commits, browser mutation, or wire encoding.
Without a snapshot, params, search, mountedSlots, interceptionContext, display URL, matched URL, and cache context become separate fields that merely hope to be updated together.
The kernel proposes. The lifecycle controller approves. The executor applies.
The kernel must be pure:
no fetch
no cache write
no browser history mutation
no React state mutation
no Worker binding access
no transition promise settlement
5. SegmentRenderPlan
SegmentRenderPlan may remain useful, but it should be a traceable substructure of RouterReduction, not the center of the architecture.
It can explain:
which segments render
which layouts reuse
which slots preserve
which defaults activate
which entries are skipped
which cache variant applies
which root boundary is active
which interception rule applies
It must not add decisions after reduction.
It must not reference raw AppElementsWire keys.
6. NavigationLifecycleController
The lifecycle controller owns time.
It owns:
operation identity
operation lane
base visible commit version
operation token compatibility
terminal state
commit permission
transition promise settlement
RouterState / PreviousRouterState updates after successful commit
newer visible work supersedes older visible work
prefetch never commits visible UI
refresh is a real operation and can be superseded
same-URL commits use visibleCommitVersion, not URL alone
stale candidate commits must not patch visible state
RSC redirects stay inside one operation lifecycle when possible
server-action results are ordered by OperationToken and visibleCommitVersion
older action results may invalidate or cache-seed only; they may not overwrite newer visible state
cross-tab invalidation signals enter as RouterEvent; they never mutate visible state directly
7. CommitDecision And BrowserDelta
Visible browser mutation must go through one gate.
Hard navigation is terminal. It should not sit beside normal browser deltas. If a root-layout boundary is crossed, the browser takes one path.
transitionMode is part of commit authority. The shell may implement it with a synchronous update, React startTransition, or document.startViewTransition, but it must not choose a different mode as a side-channel semantic decision.
If viewTransition is requested but unavailable or unsafe for the current browser/runtime, the executor may degrade only through a lifecycle-approved fallback rule recorded in trace, such as viewTransition -> transition -> sync. Feature detection may select an execution mechanism; it may not silently change commit semantics.
A browser delta says what visible element changed. The store decides how the payload is materialized.
10. ReactProjection
ReactProjection builds the renderable tree.
Input:
RouterState
+ StaticSegmentGraph
+ ElementStore
Output:
React tree
Tree is the correct final shape for React. It is not the correct universal shape for the whole router.
Use the right shape for the job:
graph for topology
event for time
transaction for lifecycle
variant for cache equivalence
delta for mutation
map for payload storage
tree for rendering
Projection contract:
preserved InstanceId => stable React key
stable key shape across releases unless remount is intentional
changing key shape is a visible state-loss bug
AppElementsWire is how payloads travel.
It is not how the router thinks.
Correctness Oracle
Correctness must mean observable behavior matches a defined contract, not that the architecture feels principled.
Every semantic PR in this track must carry one of these labels:
Next public semantics conformance:
matches documented or source/test-observed Next behavior
Vinext internal invariant:
behavior is not user-visible Next API surface, but protects router/cache correctness
Intentional documented divergence:
Vinext deliberately differs from Next, with the reason and user-visible effect documented
Do not accept unlabelled semantic changes. If a PR changes layout preservation, slot matching, default/unmatched behavior, interception, redirects, refresh, cache reuse, prefetch, streaming, hidden routes, or server actions, it needs an oracle.
Do not encode one transient canary behavior as permanent truth without naming the pinned commit and the reason. If docs and source disagree, document the conflict and choose the compatibility contract deliberately.
Compatibility-first rule:
Match Next public semantics first.
Diverge only deliberately.
Optimize only after the contract is explicit.
Semantic Cache Coherence
This is the part that decides whether Vinext can safely become faster than Next in this slice.
A cache key is a semantic equivalence claim:
These two requests may reuse the same output.
So CacheVariant must not be a fancy string builder. It should be a typed proof object.
CacheVariant is not the full dependency model. It answers "may these outputs be reused as equivalent?" The ResourceDependencyGraph answers "what inputs, resources, tags, actions, and deployment facts can make that equivalence stale?"
RenderIdentity, ReuseIdentity, and InvalidationScope often overlap. They are not the same thing.
Two responses may render the same bytes but have different invalidation scopes. If that distinction collapses into one string too early, revalidateTag() and revalidatePath() become half-correct.
If CacheVariant omits a semantic input, stale or private UI can leak.
If CacheVariant includes unnecessary inputs, the cache hit rate collapses.
Variant dimension policy must be explicit before implementation:
mandatory dimensions: graph/deployment/root/route/render identity, privacy, invalidation floor
conditional dimensions: params, search, mounted slots, interception, dynamic inputs observed by the route
bounded dimensions: params/search/header-derived values must be canonical, allowlisted, and bounded
forbidden dimensions: raw cookies, raw headers, bearer tokens, user secrets, unbounded raw user input
fallback dimensions: private, uncacheable, or render fresh when safe public variation is not known
No unbounded raw user input may become a public cache dimension.
Measure CacheVariant cardinality before enabling skip transport. If safe dimensions explode cardinality, degrade to private, uncacheable, cache miss, or fresh render instead of poisoning hit rate.
Build-time classification is a conservative hint, not final authority. Runtime observation may always downgrade cacheability:
Cloudflare is the reference runtime, not an adapter bolted on later.
The semantic core must not import raw Cloudflare bindings, but the official Cloudflare runtime should be first-class, optimized, and tested first. Other runtimes may implement the same semantic contracts later; they are not the optimization target for this issue.
Recommended Cloudflare profile:
Workers Static Assets:
immutable client/build assets deployed with the Worker
Workers Cache API:
fastest hot local response/artifact cache
data-center-local and ephemeral
never the sole source of truth
R2:
durable immutable HTML/RSC/payload artifacts
direct binding path for strong global object consistency
do not rely on cached public R2-domain responses for correctness
artifact keys are immutable; publish new records instead of mutating live identity
renderEpoch pairing is semantic compatibility, not a workaround for R2 eventual consistency
Durable Objects with SQLite:
sharded transaction log
renderEpoch publish authority
invalidation floors
HTML/RSC pairing authority
operation/cache coordination where strong ordering is required
Queues:
background revalidation
cache prewarm
artifact cleanup
purge fanout
idempotent at-least-once jobs
idempotency keys: route+variant+epoch for prewarm, artifact key for cleanup, scope+epoch for purge fanout
KV:
hints, config, warm indexes, read-mostly metadata only
never correctness authority
D1:
optional queryable metadata/admin/reporting store
not required on the hot cache path
The runtime layering:
L0 RequestMemo:
per-request dedupe only
L1 IsolateMicrocache:
opportunistic memory cache, no correctness authority
L2 Workers Cache API:
hot local response/artifact cache
L3 R2 Artifact Store:
durable immutable artifact blobs
Coordinator Durable Objects:
sharded truth for renderEpoch, invalidation floors, pairing, transactions
Async Queues:
background revalidation, prewarm, cleanup, fanout
Cloudflare Read Path
request
-> build CacheVariant
-> check whether the route class requires coherence lookup
-> ask Durable Object shard for required invalidation floor / published epoch when needed
-> check RequestMemo
-> check IsolateMicrocache
-> check Workers Cache API
-> if hit: validate semantic metadata
-> if miss or invalid: read published artifact refs
-> fetch immutable blobs from R2
-> fill Workers Cache API
-> return response
Fast path is Cache API. Truth path is Durable Objects plus R2.
Do not put the full distributed coherence path on every request by default. Static public hits should not pay for Durable Object and R2 reads when local cache metadata is already sufficient. The runtime should use the cheapest proof that is valid for the route class.
Route/cache class budget:
Route/cache class
Hot-path rule
static-public-local-valid
Workers Cache API only when embedded graph/deployment/variant/epoch metadata proves freshness. No Durable Object call.
static-public-revalidation-aware
Workers Cache API hit is accepted only if embedded epoch/floor metadata proves it is above the required invalidation floor.
tag-or-path-invalidated
Durable Object floor check is required unless the request already carries a fresh invalidation token proving the floor.
auth-cookie-header-draft
Private, per-request, explicitly dimensioned, or uncacheable. Never public by accident.
server-action-refresh
Invalidation token travels with the current operation and participates in the lifecycle commit decision.
Cloudflare Write Path
render fresh HTML/RSC
-> write immutable blobs to R2
-> Durable Object transaction publishes RouteCacheRecord
-> write local response to Workers Cache API with epoch/variant metadata
-> queue prewarm / cleanup / purge jobs
server action / revalidateTag / revalidatePath / draft/auth boundary
-> route to CacheCoordinator Durable Object shard
-> transaction increments invalidation epoch/floor
-> return invalidation token to current request
-> enqueue background cleanup/prewarm
Shard coordination by stable scope. Do not use one global Durable Object.
Example shard keys:
appId + deployment family + tag hash
appId + deployment family + path hash
appId + route identity hash
Hot-shard rules:
No global coordinator Durable Object.
High-traffic tags must not collapse all reads/writes onto one unbounded shard.
Shard by the smallest coordination atom that preserves correctness.
Allow partitioned or hierarchical epoch floors when one tag/path becomes hot.
Prefer local embedded epoch metadata on public hot reads when it is enough proof.
Measure Durable Object calls and p95/p99 separately for tag/path-invalidated routes.
Use location hints or Smart Placement only as latency tools, not correctness assumptions.
Hot-shard or coordinator failure must degrade safely:
serve stale only when policy explicitly allows it
otherwise render fresh/private
or treat as cache miss
or noCommit / hardNavigate when visible state cannot be proven safe
never silently reuse because the coordinator is unavailable or slow
KV may mirror hints for speed, but Durable Objects own authoritative invalidation state.
Cross-Tab Coherence
A tab can learn that another tab changed the route/cache world, but it must not patch UI through a side channel.
Allowed signal sources:
BroadcastChannel for same-browser tabs
focus / visibilitychange / pageshow checks
optional Durable Object or service subscription later
polling when stronger live subscription is not available
Rule:
Cross-tab signals emit RouterEvent or cache-invalidation events.
They never apply BrowserDelta, mutate RouterState, or rewrite ElementStore directly.
Deployment Compatibility
Graph-minted IDs are semantic identity only inside one graph version.
Rules:
Same graphVersion:
reuse may be allowed if CacheVariant, renderEpoch, invalidation floor, and privacy match.
Compatible graphVersion:
reuse requires an explicit generated compatibility/alias map.
Incompatible graphVersion:
reject, render fresh, or hard navigate.
Never reinterpret an old ElementId, SlotId, LayoutId, or cached artifact under a new incompatible graph.
Compatibility/alias maps must be generated build artifacts, not hand-authored policy. Humans should review the generated compatibility explanation, not manually declare old and new IDs equivalent.
Deployment publish order matters:
Build graph/schema/client assets.
Publish immutable Static Assets and any precomputed R2 artifacts.
Publish generated compatibility metadata.
Publish deploymentVersion last.
If deployment metadata points to an artifact that is missing or incompatible, the safe fallback is render fresh or hard navigate, not reuse.
Deployment skew cases that must be handled:
hydration from deployment A, navigation against deployment B
prefetch from deployment A, visible state on deployment B
RSC artifact from old graph after schema changed
HTML artifact from one deployment paired with RSC from another
hidden route retained across incompatible graph/deployment
stream chunk from superseded graph/deployment
Cloudflare Compatibility Rules
No cache entry is reusable unless graphVersion, deploymentVersion, CacheVariant, renderEpoch, invalidation floor, and privacy are compatible.
HTML and RSC artifacts for one visible route must come from compatible renderEpochs.
KV may not be used as the authoritative source for invalidation floors, current renderEpoch, HTML/RSC pairing, or private/auth cache decisions.
Workers Cache API hits must still be validated against semantic metadata.
R2 artifact keys should be immutable. Publish new records instead of mutating live artifact identity.
Durable Objects must be sharded by app/route/tag/path scope, not one global coordinator.
Queue jobs must be idempotent because delivery can be at least once.
Skip Transport: Performance From Proof-Backed Omission
The server should not render or send entries the client claims to have and the server can verify.
ClientReuseManifest is an untrusted hint. The client claims; the server, kernel, and cache coordinator prove. Treat it like If-None-Match: useful for avoiding work, never authoritative.
A skipped entry is valid only if the same kernel that would render it
can prove the existing client entry is semantically equivalent.
A client reuse manifest never proves reuse by assertion.
It only gives the server something to verify.
Every skipped entry carries a ReuseProof that trace and tests can inspect.
No byte surgery.
No "the path matches so layout is safe".
No "the key is absent so it must be reusable".
This is where the architecture can beat Next on performance: not by shaving microseconds from a reducer, but by avoiding server work and network bytes entirely.
Streaming
Streaming chunks must carry operation identity.
Reveal granularity must be explicit. A streamed payload should reveal through a known boundary, not through whatever React tree shape happens to exist that day.
Operation-token compatibility is necessary but not sufficient. A streamed chunk must still target the current snapshot, element ownership generation, and reveal boundary. If the same ElementId has been replaced, hidden, evicted, or re-owned, the old chunk cannot reveal visible UI.
A stale chunk may seed compatible cache when explicitly allowed. It may not mutate visible UI. A lifecycle-approved operation can still reject a chunk at the element-generation boundary if ownership has moved.
A commit once applied is not rolled back by a late chunk. Late chunks may extend an approved commit. If a late chunk would invalidate prior visible state, the kernel must produce recovery or hard navigation.
Activity / Hidden Route Preservation
Hidden route preservation should be modeled as retained router state with policy, not as entries that accidentally remain in a map.
A hidden route is mounted state with an owner, epoch, invalidation scope, and eviction policy.
RouterTrace
Observability should arrive early, not after the architecture is already large.
A minimal RouterTrace and invariant checker must land with the first kernel/reduction contracts. The expanded harness can grow later, but every semantic-promotion PR should have an explanation oracle from the beginning.
RouterTrace should explain:
why this segment rendered
why this layout reused
why this slot preserved
why this slot defaulted
why this route intercepted
why this cache entry varied
why this operation committed
why this operation was rejected
why the browser hard-navigated
why a cache entry was accepted or rejected
why an entry was skipped
Debugging should not require reverse-engineering flat-map absence.
Non-Negotiable Laws
1. AppElementsWire is transport, never meaning.
2. RouteSemanticIR / StaticSegmentGraph own build-time topology and route facts.
3. Stable graph-minted IDs, scoped by GraphVersion, are semantic identity.
4. RouterState owns continuity.
5. RouterEvent is the only input shape for runtime transitions.
6. routerKernel.reduce() owns route-topology and route-continuity decisions.
7. NavigationLifecycleController owns temporal commit authority.
8. CommitDecision / BrowserDelta own visible browser mutation.
9. CacheVariant owns cache equivalence.
10. ResourceDependencyGraph owns resource dependency, invalidation, and ownership facts.
11. InvalidationScope owns staleness.
12. Cloudflare runtime owns first-class execution of the cache profile, not route semantics.
13. ElementStore owns payload storage, not semantic interpretation.
14. ReactProjection owns final tree shape.
15. Generated code may call typed contracts but may not recreate their logic.
16. Missing payload, missing wire key, missing branch, and cache miss never imply semantic preservation.
17. Every semantic decision has exactly one writer.
18. When a decision moves into the compiler/kernel/lifecycle/cache/runtime owner, delete the old writer in the same PR.
19. Every semantic PR declares its correctness oracle: Next public semantics, Vinext internal invariant, or intentional documented divergence.
20. Unknown state degrades to fresh render, cache miss, discarded chunk, cache-seed only, noCommit, or hard navigation. Never to reuse.
21. ClientReuseManifest is untrusted. The server verifies every claimed reusable entry.
22. Graph compatibility maps are generated build artifacts, not hand-authored equivalence policy.
23. Boundary outcomes are route/cache/commit outcomes, not generic render failures.
24. CommitDecision.apply owns transitionMode; shell code may execute it but not reinterpret it.
25. Cross-tab coherence signals become RouterEvent or cache-invalidation events, never direct visible mutation.
26. Queue jobs must be idempotent because duplicate delivery is allowed.
27. Skip/reuse requires ReuseProof, not just a matching path, key, or client claim.
Sharper authority split:
No route-topology or route-continuity decision outside routerKernel.reduce().
No temporal commit decision outside NavigationLifecycleController.
No browser-visible mutation outside approved CommitDecision / BrowserDelta application.
No cache equivalence decision outside CacheVariant / InvalidationScope.
No Cloudflare storage hit bypasses semantic compatibility checks.
Validity Rules
A reduction or runtime action is invalid if:
rootLayoutTransition = crossRoot
and CommitDecision is not hardNavigate
BrowserDelta preserves a slot
but RouterState cannot prove the slot exists or is retained
slot default is rendered
but StaticSegmentGraph has no default for that SlotId
slot is marked unmatched
but graph resolution found a matching slot route or default
route is intercepted
but event/context is not interception-capable
layout is reused across a crossed root boundary
skip/reuse is emitted without compatible CacheVariant, ResourceDependencyGraph, and ReuseProof
cache read occurs without CacheVariant compatibility
cache entry has compatible CacheVariant but incompatible dependency fingerprint or invalidation ownership
HTML/RSC pair crosses renderEpoch incompatibly
successful RSC payload is paired with error/notFound/unauthorized HTML, or the reverse
cached notFound/forbidden/unauthorized boundary is reused as a successful route payload
global-error from a stale operation mutates visible state
private/auth/cookie/header/draft-sensitive output is cached as public
async result commits with stale visibleCommitVersion
older server-action result overwrites newer visible state instead of rejecting, cache-seeding, or invalidating through lifecycle
CommitDecision.apply is executed with a transition mode chosen outside commit authority
cross-tab signal mutates RouterState, BrowserDelta, or ElementStore directly instead of entering as RouterEvent/cache invalidation
stream chunk mutates visible UI without lifecycle approval
stream chunk mutates visible UI after its target snapshot, elementGeneration, or reveal boundary is no longer current
server accepts ClientReuseManifest entries without verifying graph, deployment, variant, epoch, payload hash, and invalidation compatibility
skip transport is enabled before CacheVariant cardinality is measured and bounded
unbounded raw user input becomes a public CacheVariant dimension
coordinator unavailable/slow path silently reuses cache instead of choosing policy-approved stale, fresh render, cache miss, noCommit, or hard navigation
KV decides invalidation floor, current renderEpoch, HTML/RSC pairing, or private cache safety
queue job lacks an idempotency key for its route/variant/epoch, artifact key, or invalidation scope
generated code constructs RouterReduction, BrowserDelta, or CacheVariant directly
wire-key absence is used as preserve/delete/default/skip proof
uncertain cache/deployment/graph compatibility results in reuse instead of safe fallback
Navigation Kind Semantics
The kernel must branch on explicit navigation kind, not infer it from payload shape.
hydrate:
may use previous state: SSR snapshot only
may preserve slots: only if SSR snapshot proves them
may apply interception: only if encoded in hydration snapshot
commit: no visible mutation unless recovery is required
csrInitialise:
may use previous state: no
may preserve slots: no
may apply interception: no, unless explicit initial interception exists
commit: initialise
soft:
may use previous state: yes
may preserve slots: yes
may apply interception: yes
commit: soft commit
hard:
may use previous state: no
may preserve slots: no
may apply interception: no
commit: full document navigation
refresh:
may use previous state: yes
may preserve slots: yes
may apply interception: current-context only
commit: refresh commit
traverse / popstate:
may use previous state: yes
may preserve slots: yes, from history-derived state
may apply interception: history-context only
commit: traverse commit
prefetch:
may use previous state: limited
may preserve slots: no visible commit
may apply interception: target-context only
commit: none
server-action refresh:
may use previous state: yes
may preserve slots: yes
may apply interception: current-context only
commit: action refresh commit
Import And Codegen Restrictions
Dependency direction should be mechanically enforced.
StaticSegmentGraph must not import AppElementsWire.
routerKernel must not import AppElementsWire.
routerKernel must not import Cloudflare bindings.
NavigationLifecycleController must not decode wire keys.
CacheCoordinator consumes CacheVariant, not raw router internals.
Browser runtime consumes CommitDecision, not decoded wire keys.
ElementStore consumes ElementStoreOp / PayloadRef, not route semantics.
Generated code may call schema/kernel/lifecycle/executor/encoder entrypoints.
Generated code may not construct semantic decisions manually.
Generated code may not hand-build wire keys.
Generated code may not hand-build cache variants.
Cloudflare runtime modules may import Cloudflare bindings.
Semantic core modules may not.
Add lint, grep, dependency-boundary tests, or module-boundary tests.
Architecture that is not mechanically defended will be violated eventually. Not because people are careless, but because future contributors will be working under pressure.
Implementation Dependency Layers
This is not a fixed PR budget. Split into as many small, reviewable PRs as necessary.
The dependency order matters more than the count.
Layer 0: Keep The Landed Foundation
Do not redo the flat payload milestone.
Keep:
flat keyed AppElements payload
layout/page/template/slot/route entries
browser merge/replace behavior
SSR/browser deserialization symmetry
root-layout hard navigation behavior, until promoted
absent-key soft-navigation preservation, until promoted
The bridge stays. The city moves off the bridge.
Layer 1: Freeze And Fence The Wire Shell
Goal: make current behavior safe to refactor around.
Deliverables:
compatibility tests for current flat-payload behavior
AppElementsWire codec boundary
forbidden raw wire-key parsing checks
import-direction tests
No behavior change.
No semantic promotion.
No raw wire-key parsing outside codec/encoder boundary.
Goal: route semantics pass through one pure reducer.
Deliverables:
routerKernel.reduce() that mirrors current behavior
minimal reduction validation
current AppElementsWire emitted from reduction + ElementStore projection
wire explanation checks
trace output for every promoted decision path
The reducer should be boring. Boring is good here.
Layer 5: Add Lifecycle Transactions
Goal: prevent stale async work from committing visible state.
Boundary outcomes must land before aggressive cache coherence and skip rollout. notFound, error, forbidden, unauthorized, and global-error affect visible commit state, cacheability, streaming, and reuse proof.
Each promotion moves one decision area.
Each promotion deletes the old writer in the same PR.
No dual authority.
Layer 7: Semantic Cache Coherence
Goal: make cache reuse safe before making it aggressive.
This can begin earlier in small form. It should be fully active before skip, streaming, and Activity work are considered complete.
Hot Path Budget
The Cloudflare runtime profile must not turn every request into a distributed coordination exercise.
Track budgets for:
static route hit
dynamic route hit
soft navigation
refresh
server-action redirect
prefetch
stream chunk reveal
At minimum, record:
Workers Cache API reads/writes
Durable Object calls
Durable Object shard hotness and p95/p99 by route/cache class
R2 reads/writes
Queue writes and duplicate/idempotent replays
Worker subrequests
server CPU time
RSC bytes sent
layouts rendered
client remount count
p95/p99 navigation latency
cache variant cardinality
ElementStore memory
hidden-route retained memory
stale commit rejection count
Performance claims should be based on work avoided:
fewer layout renders
fewer RSC bytes
fewer remounts
lower p95/p99 latency
bounded coherence calls on hot paths
Do not claim the architecture is faster merely because it is more formal.
The budget must be reported by route/cache class. A static public local hit that calls a Durable Object on every request has failed the hot-path design even if it is formally correct.
Test Strategy
The most important tests are sequence tests and property-style tests, not snapshots.
A -> B -> C, with B resolving last
prefetch A
navigate B
old A prefetch resolves
server action resolves after same-URL refresh
server actions A and B resolve out of order
cross-tab invalidation arrives while navigation is pending
back/back/forward while RSC responses stream
cross-root navigation while old chunks arrive
slot mounted
slot unmatched
refresh
action redirect
HTML cache hit but RSC cache miss
RSC cache hit but HTML stale
deployment A client receives deployment B payload
Required invariants:
No stale operation commits visible state.
No visible state mutates without lifecycle-approved BrowserDelta.
No commit runs under a transitionMode chosen outside CommitDecision.
No older server-action result overwrites newer visible state.
No cross-tab signal mutates visible state outside RouterEvent/lifecycle.
No cache entry is read without CacheVariant compatibility.
No HTML/RSC pair crosses renderEpoch incompatibly.
No layout is reused across root-layout boundary.
No slot is preserved unless RouterState proves it existed.
No interception applies without an interception-capable context.
No generated code constructs semantic decisions manually.
No AppElementsWire absence has semantic meaning.
No private/cookie/auth-sensitive response is cached under a public variant.
No KV value is treated as authoritative invalidation or renderEpoch state.
No ClientReuseManifest entry is trusted without server-side verification.
No streamed chunk reveals into a stale target snapshot, elementGeneration, or reveal boundary.
No queue duplicate changes the final cache/invalidation/artifact state.
No uncertain graph/deployment/cache state results in reuse.
Every semantic change names its oracle: Next public semantics, Vinext internal invariant, or intentional documented divergence.
Performance And Correctness Claim
The claim is intentionally narrow.
Do not say Vinext is globally better than Next.js.
Say:
Vinext is designed to become more correct and more inspectable than Next.js
in the App Router navigation/cache slice, while matching Next public semantics
unless divergence is deliberate and documented.
And:
Vinext is designed to beat Next on Cloudflare in selected measured paths
through proof-backed omission, semantic cache coherence, and first-class
Cloudflare runtime execution.
The honest performance story:
Compiler-backed route facts make semantic decisions cheaper.
Kernel + cache model make safe reuse provable.
Cloudflare runtime makes approved reuse fast.
Skip transport and streaming reduce render work and RSC bytes.
Benchmarks decide when faster-than-Next is earned.
Do not claim guaranteed speed before the skip/cache/streaming layers exist and benchmarks prove it.
What Changes From The Current Flat-Map Model
From:
flat map carries payload and implicit meaning
missing keys imply preserve/default/skip
cache paths append their own suffixes
local guards decide stale commit behavior
generated code duplicates route decisions
browser merge logic becomes semantic
HTML and RSC cache entries may drift
edge consistency is treated as infrastructure detail
KV is tempting as a correctness shortcut
To:
flat map carries payload only
compiled graph owns topology
RouterState owns continuity
RouterEvent models time
kernel owns route semantics
lifecycle owns temporal commit authority
BrowserDelta owns visible mutation
CacheVariant owns equivalence
InvalidationScope owns staleness
Cloudflare runtime owns first-class execution
Durable Objects own transactional cache-coherence state
R2 owns immutable durable artifacts
Workers Cache API owns hot local cache
KV owns hints/config only
ElementStore owns payload storage
ReactProjection owns tree output
Success Criteria
The migration succeeds when:
AppElementsWire is never used as semantic input.
Every route-semantic decision comes from routerKernel.reduce().
Every visible browser mutation comes from lifecycle-approved BrowserDelta.
Every applied commit has an explicit transitionMode and lifecycle-approved fallback path.
Every async result carries operation identity and visible commit version.
Every stale operation is rejected, cache-seeded only, or terminally settled.
Every cache read uses CacheVariant compatibility.
Every cache reuse checks ResourceDependencyGraph dependency compatibility.
Every cache entry has graphVersion and deploymentVersion.
Every HTML/RSC pair has compatible renderEpoch.
Every skip is explained by graph proof, cache equivalence, dependency compatibility, and ReuseProof.
Every streamed chunk is operation-tokened and checked against target snapshot / element generation / reveal boundary.
Every cross-tab coherence signal enters through RouterEvent or cache invalidation, not direct mutation.
Every queue job is idempotent.
Every hidden route has owner, epoch, invalidation scope, and eviction policy.
Every ClientReuseManifest entry is treated as untrusted until the server verifies it.
Every graph compatibility map is generated and versioned.
CacheVariant cardinality is measured before skip transport is enabled.
Public CacheVariant dimensions are canonical, allowlisted, and bounded.
Boundary outcomes cannot be paired or reused across incompatible success/error/notFound/unauthorized states.
Generated code cannot recreate semantic decisions.
Missing payload, missing wire key, missing branch, and cache miss have no semantic meaning.
KV never owns correctness-critical cache state.
Out Of Scope For This Migration Stage
Do not add yet:
full generated transition automaton
separate reference interpreter
global strong cache consistency for all content
new public router API surface
detailed optimistic UI / useOptimistic overlay semantics before action lifecycle exists
large behavioral rewrites before lifecycle gating exists
stronger-than-Next performance claims before benchmarks prove them
The pure reducer is the reference for now. A second semantic engine would create the drift this architecture is trying to eliminate.
This architecture is not trying to be a prettier flat map.
It is trying to make the router less magical.
Inputs are explicit. Decisions have one owner. Effects wait for approval. Cache identity is semantic. Invalidation is algebraic. Cloudflare is first-class. Debug traces explain decisions. Tests generate hostile timelines.
Performance comes from certainty:
Because we know this layout is static, skip it.
Because we know this slot was mounted, preserve it.
Because we know this action is stale, reject it.
Because we know this cache variant is compatible, reuse it.
Because we know this graph version changed, hard navigate.
Because we do not know, render fresh.
Updated 2026-04-30: this is the governing architecture for the App Router layout-persistence follow-up. It supersedes the original flat-map-first roadmap and earlier fixed-PR-count wording where they conflict.
App Router layout persistence: compiler-backed router kernel architecture
What This Issue Is Really About
The flat
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.The next problem is semantic authority.
Vinext needs to stop route meaning from leaking into wire keys, cache suffixes, local navigation guards, and missing payload entries. Future features like skip/reuse, streaming, prefetch, server actions, refresh, Activity preservation, and Cloudflare cache coherence need one clear source of truth.
This architecture makes Vinext behave like:
The law is simple:
Current Status
The first layout-persistence milestone has landed.
Vinext no longer needs to replace the whole App Router RSC tree on every client navigation. The current flat keyed
AppElementspayload gives us a practical bridge:That bridge should stay.
But it should not become the router's brain.
The next phase is about separating transport from meaning. The flat map is useful for payload delivery, merge buffers, compatibility, and encoding. It is not the right place to decide topology, slot preservation, interception, cache identity, root-layout transitions, skip/reuse, streaming, or stale async commit behavior.
The target architecture is:
One-line version:
Problem
App Router behavior depends on more than the URL.
A client navigation can preserve layouts. A parallel route slot can remain mounted even when it is not represented in the new URL. An intercepted route can render as a modal during soft navigation, but as a full page on refresh or direct load. A refresh can target the same URL but still need a new visible commit. A server action can redirect, revalidate, patch, or force a refresh. A prefetch can complete after a newer navigation and must not mutate visible UI. A streamed chunk can arrive late and belong to a dead navigation.
Those behaviors cannot be safely inferred from:
Missing data is not meaning.
A missing map key might mean preserve, delete, default, skipped, cached, failed, streamed later, stale, or irrelevant. Once absence starts carrying semantics, every feature adds another special case. That is how routers become hard to debug.
The architecture should make meaning explicit.
First Principles
The filesystem tells us what can exist. Runtime state tells us what currently exists. Events tell us what happened. The kernel decides what those facts mean. The lifecycle controller decides whether this result is still allowed to become visible. The cache model proves whether reuse is safe. The Cloudflare runtime makes proven reuse fast. The wire format carries payloads. React receives a tree at the end.
Core rules:
Any part doing more than its job will eventually become a second router.
The safety/performance law:
Uncertainty must degrade to a safe outcome:
Uncertainty must never degrade to reuse.
Performance should come from certainty, not optimistic reuse:
Architecture Overview
flowchart TD subgraph Build["Build time"] A["Developer app/ filesystem"] --> B["Build-time compiler"] B --> C["RouteSemanticIR"] C --> D["CompiledRouteSchema / StaticSegmentGraph"] C --> G["DataDependencyIR / ResourceDependencyGraph"] D --> E["graphVersion + graph-minted IDs + route fact tables"] end subgraph Decision["Router decision path"] U["User intent / async result"] --> EV["RouterEvent"] S["RouterState / RouteSnapshot"] --> K["routerKernel.reduce"] E --> K G --> K EV --> K K --> R["RouterReduction / CommitProposal"] R --> L["NavigationLifecycleController"] L -->|approved| X["RouterExecutor / shell"] L -->|stale / aborted / superseded| N["reject or cache-seed only"] L -->|cross-root / incompatible graph| H["hard navigate"] end subgraph ServerEncode["Server encode path"] X --> SR["RSC render / payload materialization"] SR --> WE["AppElementsWire encode"] WE --> NET["network payload"] end subgraph ClientCommit["Client decode + projection path"] NET --> WD["AppElementsWire decode"] WD --> ES["ElementStore"] X --> BD["approved BrowserDelta application"] ES --> P["ReactProjection"] P --> UI["Visible UI"] BD --> UI end X --> CC["CacheCoordinator"] G --> CC CC --> CF["Cloudflare runtime profile"] CF --> WC["Workers Cache API hot local cache"] CF --> R2["R2 immutable artifacts"] CF --> DO["Durable Objects SQLite transaction shards"] CF --> Q["Queues background work"] CF --> KV["KV hints/config only"]Core Ownership Model
1. RouteSemanticIR / CompiledRouteSchema / StaticSegmentGraph
The build-time compiler owns route facts and topology.
It compiles:
Example shape:
The compiler may emit conservative purity/cache hints:
These hints are not permission to be reckless. Runtime outcomes can always downgrade cacheability. If a render observes cookies, headers, auth, draft mode, dynamic fetches, thrown boundaries, or other dynamic inputs, the cache model must encode a safe dimension or mark the response private/uncacheable.
Compile facts, not every possible transition. A full generated transition automaton is not the next move. The compiler should produce stable facts, graph structure, canonical constructors, and conservative classifications. Runtime transition decisions remain in the kernel.
1b. DataDependencyIR / ResourceDependencyGraph
RouteSemanticIRsays what can exist.ResourceDependencyGraphsays what a render, payload, cached artifact, stream chunk, prefetch, or hidden route depends on and what can invalidate it.Owns:
Rule:
It must not decide visible commits, browser mutation, or wire encoding.
2. RouterState And RouteSnapshot
RouterStateowns continuity.It must never be reconstructed from wire keys.
RouteSnapshotkeeps facts that must change together:Without a snapshot,
params,search,mountedSlots,interceptionContext, display URL, matched URL, and cache context become separate fields that merely hope to be updated together.3. RouterEvent
Every router input becomes an event.
Initial user intent events do not need to carry an operation token. The lifecycle controller creates operation identity after the event is classified.
Async result events must carry proof that they belong to a specific causal world.
A fetch result is not allowed to commit because it finished. It may commit only if it still belongs to the current visible world.
4. routerKernel.reduce
The kernel owns route semantics.
It answers one question:
Shape:
Suggested output:
Use
commitProposal, notcommitDeltas.The kernel proposes. The lifecycle controller approves. The executor applies.
The kernel must be pure:
5. SegmentRenderPlan
SegmentRenderPlanmay remain useful, but it should be a traceable substructure ofRouterReduction, not the center of the architecture.It can explain:
It must not add decisions after reduction.
It must not reference raw
AppElementsWirekeys.6. NavigationLifecycleController
The lifecycle controller owns time.
It owns:
Every visible navigation is a transaction:
Lifecycle decision:
Rules:
7. CommitDecision And BrowserDelta
Visible browser mutation must go through one gate.
Hard navigation is terminal. It should not sit beside normal browser deltas. If a root-layout boundary is crossed, the browser takes one path.
transitionModeis part of commit authority. The shell may implement it with a synchronous update, ReactstartTransition, ordocument.startViewTransition, but it must not choose a different mode as a side-channel semantic decision.If
viewTransitionis requested but unavailable or unsafe for the current browser/runtime, the executor may degrade only through a lifecycle-approved fallback rule recorded in trace, such asviewTransition -> transition -> sync. Feature detection may select an execution mechanism; it may not silently change commit semantics.Browser-visible deltas:
Transition settlement is lifecycle/accounting, not visible browser mutation:
Do not put server cache writes, Workers cache effects, RSC stream control, prefetch storage, or lifecycle settlement into
BrowserDelta.The split is:
8. RouterExecutor
The executor owns imperative work.
It may:
Executor may apply
LifecycleSettlement; it may not derive settlement.It may not decide:
The executor is the hand, not the brain.
9. ElementStore
ElementStoreowns payload storage.It stores:
It does not decide:
Payload references should be indirect:
A browser delta says what visible element changed. The store decides how the payload is materialized.
10. ReactProjection
ReactProjectionbuilds the renderable tree.Input:
Output:
Tree is the correct final shape for React. It is not the correct universal shape for the whole router.
Use the right shape for the job:
Projection contract:
11. AppElementsWire
AppElementsWireremains flat.It owns:
It does not own:
The rule:
Correctness Oracle
Correctness must mean observable behavior matches a defined contract, not that the architecture feels principled.
Every semantic PR in this track must carry one of these labels:
Do not accept unlabelled semantic changes. If a PR changes layout preservation, slot matching, default/unmatched behavior, interception, redirects, refresh, cache reuse, prefetch, streaming, hidden routes, or server actions, it needs an oracle.
Oracle hierarchy:
Do not encode one transient canary behavior as permanent truth without naming the pinned commit and the reason. If docs and source disagree, document the conflict and choose the compatibility contract deliberately.
Compatibility-first rule:
Semantic Cache Coherence
This is the part that decides whether Vinext can safely become faster than Next in this slice.
A cache key is a semantic equivalence claim:
So
CacheVariantmust not be a fancy string builder. It should be a typed proof object.CacheVariantis not the full dependency model. It answers "may these outputs be reused as equivalent?" TheResourceDependencyGraphanswers "what inputs, resources, tags, actions, and deployment facts can make that equivalence stale?"Split the concepts:
RenderIdentity,ReuseIdentity, andInvalidationScopeoften overlap. They are not the same thing.Two responses may render the same bytes but have different invalidation scopes. If that distinction collapses into one string too early,
revalidateTag()andrevalidatePath()become half-correct.Route artifact record:
Invalid state:
Valid cache reuse requires:
Cache completeness rule:
Variant dimension policy must be explicit before implementation:
No unbounded raw user input may become a public cache dimension.
Measure
CacheVariantcardinality before enabling skip transport. If safe dimensions explode cardinality, degrade to private, uncacheable, cache miss, or fresh render instead of poisoning hit rate.Build-time classification is a conservative hint, not final authority. Runtime observation may always downgrade cacheability:
Cloudflare-First Runtime Profile
Cloudflare is the reference runtime, not an adapter bolted on later.
The semantic core must not import raw Cloudflare bindings, but the official Cloudflare runtime should be first-class, optimized, and tested first. Other runtimes may implement the same semantic contracts later; they are not the optimization target for this issue.
Recommended Cloudflare profile:
The runtime layering:
Cloudflare Read Path
Fast path is Cache API. Truth path is Durable Objects plus R2.
Do not put the full distributed coherence path on every request by default. Static public hits should not pay for Durable Object and R2 reads when local cache metadata is already sufficient. The runtime should use the cheapest proof that is valid for the route class.
Route/cache class budget:
static-public-local-validstatic-public-revalidation-awaretag-or-path-invalidatedauth-cookie-header-draftserver-action-refreshCloudflare Write Path
Use immutable artifact keys:
/artifacts/{deploymentVersion}/{graphVersion}/{renderEpoch}/{hash}.rsc /artifacts/{deploymentVersion}/{graphVersion}/{renderEpoch}/{hash}.htmlDo not overwrite live artifact identity.
Cloudflare Invalidation Path
Shard coordination by stable scope. Do not use one global Durable Object.
Example shard keys:
Hot-shard rules:
Hot-shard or coordinator failure must degrade safely:
KV may mirror hints for speed, but Durable Objects own authoritative invalidation state.
Cross-Tab Coherence
A tab can learn that another tab changed the route/cache world, but it must not patch UI through a side channel.
Allowed signal sources:
Rule:
Deployment Compatibility
Graph-minted IDs are semantic identity only inside one graph version.
Rules:
Never reinterpret an old ElementId, SlotId, LayoutId, or cached artifact under a new incompatible graph.
Compatibility/alias maps must be generated build artifacts, not hand-authored policy. Humans should review the generated compatibility explanation, not manually declare old and new IDs equivalent.
Deployment publish order matters:
If deployment metadata points to an artifact that is missing or incompatible, the safe fallback is render fresh or hard navigate, not reuse.
Deployment skew cases that must be handled:
Cloudflare Compatibility Rules
Skip Transport: Performance From Proof-Backed Omission
The server should not render or send entries the client claims to have and the server can verify.
ClientReuseManifestis an untrusted hint. The client claims; the server, kernel, and cache coordinator prove. Treat it likeIf-None-Match: useful for avoiding work, never authoritative.Client sends a reuse manifest:
Server responds with a render plan:
Law:
No byte surgery.
No "the path matches so layout is safe".
No "the key is absent so it must be reusable".
This is where the architecture can beat Next on performance: not by shaving microseconds from a reducer, but by avoiding server work and network bytes entirely.
Streaming
Streaming chunks must carry operation identity.
Reveal granularity must be explicit. A streamed payload should reveal through a known boundary, not through whatever React tree shape happens to exist that day.
Operation-token compatibility is necessary but not sufficient. A streamed chunk must still target the current snapshot, element ownership generation, and reveal boundary. If the same
ElementIdhas been replaced, hidden, evicted, or re-owned, the old chunk cannot reveal visible UI.Handling rule:
A stale chunk may seed compatible cache when explicitly allowed. It may not mutate visible UI. A lifecycle-approved operation can still reject a chunk at the element-generation boundary if ownership has moved.
A commit once applied is not rolled back by a late chunk. Late chunks may extend an approved commit. If a late chunk would invalidate prior visible state, the kernel must produce recovery or hard navigation.
Activity / Hidden Route Preservation
Hidden route preservation should be modeled as retained router state with policy, not as entries that accidentally remain in a map.
Hidden routes must still obey:
A hidden route is mounted state with an owner, epoch, invalidation scope, and eviction policy.
RouterTrace
Observability should arrive early, not after the architecture is already large.
A minimal
RouterTraceand invariant checker must land with the first kernel/reduction contracts. The expanded harness can grow later, but every semantic-promotion PR should have an explanation oracle from the beginning.RouterTraceshould explain:Shape:
Debugging should not require reverse-engineering flat-map absence.
Non-Negotiable Laws
Sharper authority split:
Validity Rules
A reduction or runtime action is invalid if:
Navigation Kind Semantics
The kernel must branch on explicit navigation kind, not infer it from payload shape.
Import And Codegen Restrictions
Dependency direction should be mechanically enforced.
Add lint, grep, dependency-boundary tests, or module-boundary tests.
Architecture that is not mechanically defended will be violated eventually. Not because people are careless, but because future contributors will be working under pressure.
Implementation Dependency Layers
This is not a fixed PR budget. Split into as many small, reviewable PRs as necessary.
The dependency order matters more than the count.
Layer 0: Keep The Landed Foundation
Do not redo the flat payload milestone.
Keep:
The bridge stays. The city moves off the bridge.
Layer 1: Freeze And Fence The Wire Shell
Goal: make current behavior safe to refactor around.
Deliverables:
No behavior change.
No semantic promotion.
No raw wire-key parsing outside codec/encoder boundary.
Layer 2: Compile Route Facts
Goal: make route topology cheap and typed.
Deliverables:
Compile facts, not transitions.
Layer 3: Add Runtime Contracts
Goal: define the new vocabulary before moving behavior.
Deliverables:
No major behavior change yet.
Layer 4: Add The Boring Pure Kernel
Goal: route semantics pass through one pure reducer.
Deliverables:
The reducer should be boring. Boring is good here.
Layer 5: Add Lifecycle Transactions
Goal: prevent stale async work from committing visible state.
Deliverables:
Async results return as
RouterEvent.Only lifecycle-approved commits update visible
RouterState.Prefetch may seed cache, never visible UI.
Layer 6: Promote Semantic Decisions One By One
Goal: delete old semantic writers.
Decision areas:
Boundary outcomes must land before aggressive cache coherence and skip rollout.
notFound,error,forbidden,unauthorized, andglobal-erroraffect visible commit state, cacheability, streaming, and reuse proof.Each promotion moves one decision area.
Each promotion deletes the old writer in the same PR.
No dual authority.
Layer 7: Semantic Cache Coherence
Goal: make cache reuse safe before making it aggressive.
Deliverables:
This layer must land before serious skip transport.
A skip system without cache coherence is a faster way to be wrong.
Layer 8: Cloudflare Runtime Profile
Goal: implement the official Cloudflare hot path.
Deliverables:
Cloudflare is first-class here.
The semantic core stays binding-free.
Layer 9: Proof-Backed Skip Transport
Goal: reduce server work and network bytes only when equivalence is proven.
Deliverables:
Law:
Layer 10: Streaming With Operation-Tokened Chunks
Goal: support progressive reveal without stale chunk corruption.
Deliverables:
Streaming must not bypass lifecycle commit authority.
Layer 11: Activity / Hidden Route Preservation
Goal: preserve route state with ownership, invalidation, and bounds.
Deliverables:
Invisible does not mean dead.
Retained does not mean immortal.
Layer 12: Invariant Harness
Goal: make invalid states loud.
Deliverables:
This can begin earlier in small form. It should be fully active before skip, streaming, and Activity work are considered complete.
Hot Path Budget
The Cloudflare runtime profile must not turn every request into a distributed coordination exercise.
Track budgets for:
At minimum, record:
Performance claims should be based on work avoided:
Do not claim the architecture is faster merely because it is more formal.
The budget must be reported by route/cache class. A static public local hit that calls a Durable Object on every request has failed the hot-path design even if it is formally correct.
Test Strategy
The most important tests are sequence tests and property-style tests, not snapshots.
Generate apps with:
Generate event sequences like:
Required invariants:
Performance And Correctness Claim
The claim is intentionally narrow.
Do not say Vinext is globally better than Next.js.
Say:
And:
The honest performance story:
Do not claim guaranteed speed before the skip/cache/streaming layers exist and benchmarks prove it.
What Changes From The Current Flat-Map Model
From:
To:
Success Criteria
The migration succeeds when:
Out Of Scope For This Migration Stage
Do not add yet:
The pure reducer is the reference for now. A second semantic engine would create the drift this architecture is trying to eliminate.
Related Issues / PRs / References
__VINEXT_CLASSstub patching ingenerateBundle#863: generated-code lifecycle seam should move toward owned typed contractsFinal Statement
This architecture is not trying to be a prettier flat map.
It is trying to make the router less magical.
Inputs are explicit. Decisions have one owner. Effects wait for approval. Cache identity is semantic. Invalidation is algebraic. Cloudflare is first-class. Debug traces explain decisions. Tests generate hostile timelines.
Performance comes from certainty:
That last line is the discipline.