Skip to content

perf(server): cache App Router route wiring plans#2179

Open
NathanDrake2406 wants to merge 4 commits into
cloudflare:mainfrom
NathanDrake2406:nathan-optimise
Open

perf(server): cache App Router route wiring plans#2179
NathanDrake2406 wants to merge 4 commits into
cloudflare:mainfrom
NathanDrake2406:nathan-optimise

Conversation

@NathanDrake2406

Copy link
Copy Markdown
Contributor

Overview

Item Detail
Goal Reduce App Router SSR route wiring overhead shown in CPU profiles.
Core change Cache route-invariant App Router wiring plans in a WeakMap keyed by the generated route object.
Main boundary Keep params, dependency barriers, and React element creation per request while hoisting stable layout/template/error/slot derivations.
Primary file packages/vinext/src/server/app-page-route-wiring.tsx
Expected impact Lower buildAppPageElements self-time on warm SSR requests.

Why

Generated App Router route objects are stable for the server process, but the SSR path rebuilt derived layout, template, error, tree-position, and slot metadata for every request. Those derivations are route-invariant, while params and render dependencies remain request-scoped.

Area Principle / invariant What this PR changes
Route wiring Generated route structure is stable after startup. Adds a per-route cached wiring plan for derived entries, maps, ids, slot entries, and source page data.
Request render state Params, dependency barriers, and React elements are request-scoped. Leaves these on the request path.
Slots/templates Empty feature sets should not allocate support structures per request. Avoids slot dependency snapshots and template maps when the matched route does not use them.

Profiling

Warm SSR profile of benchmarks/vinext at /, built with --sourcemap --minify false, using 1000 warmup requests plus 20000 measured requests.

Metric Before After Change
buildAppPageElements self-time 85.83ms 38.75ms -54.9%
app-page-route-wiring source self-time 104.88ms 48.62ms -53.6%
20k measured HTTP wall time 13.49s 10.62s -21.3%

CPU self-time is the primary signal here. HTTP timing is included as supporting context only.

Validation

Commands run
vp check tests/app-page-route-wiring.test.ts tests/app-page-element-builder.test.ts tests/app-static-siblings.test.ts
vp test run tests/app-page-route-wiring.test.ts tests/app-page-element-builder.test.ts tests/app-static-siblings.test.ts
vp run vinext#build
node benchmarks/generate-app.mjs
cd benchmarks/vinext && vp build --sourcemap --minify false

The final pre-commit hook also ran staged vp check --fix, full check, and knip.

Risk / Compatibility

Notes
  • Runtime behaviour should be unchanged. The cache stores only route-invariant structure derived from the generated route object.
  • The cache is a WeakMap, so route objects can still be collected if a dev/runtime graph drops them.
  • Per-request state remains per request: matched params, slot override params, render dependencies, search params, and React elements are not cached.
  • This does not include the separate safe JSON or Vary optimizations from PRs perf(app-router): trim AppElements Flight metadata #2176 and perf(server): skip duplicate App RSC vary merge #2177.

App Router SSR repeatedly rebuilds route-invariant layout, template, error, and slot wiring metadata while constructing each page response. That work is unnecessary for generated route objects that are stable for the lifetime of the server process.

Cache the derived wiring plan per route object and keep per-request work focused on params, dependency barriers, and React element construction. Also avoid template and slot dependency allocations on routes that do not use those features.
@NathanDrake2406 NathanDrake2406 marked this pull request as ready for review June 19, 2026 13:52
@pkg-pr-new

pkg-pr-new Bot commented Jun 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2179
npm i https://pkg.pr.new/vinext@2179

commit: 2f4da1e

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 2f4da1e against base 94eb44e using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 132.8 KB 132.8 KB ⚫ -0.0%
Client entry size (gzip) vinext 118.6 KB 118.6 KB ⚫ -0.0%
Dev server cold start vinext 2.34 s 2.32 s ⚫ -0.5%
Production build time vinext 2.79 s 2.81 s ⚫ +0.7%
RSC entry size (gzip) vinext 60.1 KB 60.3 KB ⚫ +0.4%
Server bundle size (gzip) vinext 175.4 KB 175.6 KB ⚫ +0.1%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant