diff --git a/.github/workflows/client-nav-benchmarks.yml b/.github/workflows/client-nav-benchmarks.yml
index cd47ca3ba1..eae3cd7448 100644
--- a/.github/workflows/client-nav-benchmarks.yml
+++ b/.github/workflows/client-nav-benchmarks.yml
@@ -36,6 +36,17 @@ jobs:
benchmark:
- client-nav
- ssr
+ - memory-server
+ - memory-client
+ include:
+ - benchmark: client-nav
+ mode: simulation
+ - benchmark: ssr
+ mode: simulation
+ - benchmark: memory-server
+ mode: memory
+ - benchmark: memory-client
+ mode: memory
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -46,8 +57,19 @@ jobs:
- name: Setup Tools
uses: TanStack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main
+ # Run the build outside of the CodSpeed action to avoid being slowed by intrumentation overhead.
+ # (and then run the task itself with --excludeTaskDependencies)
+ - name: Prepare ${{ matrix.benchmark }}:${{ matrix.framework }} benchmark
+ run: >-
+ pnpm nx run
+ @benchmarks/${{ matrix.benchmark }}:build:${{ matrix.framework }}
+
- name: Run ${{ matrix.benchmark }}:${{ matrix.framework }} CodSpeed benchmark
uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4.17.0
with:
- mode: simulation
- run: WITH_INSTRUMENTATION=1 pnpm nx run @benchmarks/${{ matrix.benchmark }}:test:perf:${{ matrix.framework }}
+ mode: ${{ matrix.mode }}
+ run: >-
+ WITH_INSTRUMENTATION=1
+ pnpm nx run
+ @benchmarks/${{ matrix.benchmark }}:test:perf:${{ matrix.framework }}
+ --excludeTaskDependencies
diff --git a/.gitignore b/.gitignore
index 4c5184f4d8..1699592784 100644
--- a/.gitignore
+++ b/.gitignore
@@ -90,3 +90,6 @@ vite.config.ts.timestamp_*
# eslint-plugin-start perf fixtures
/e2e/eslint-plugin-start/src/perf/generated
+
+# local memory flame profiles
+benchmarks/memory/**/.profiles/
diff --git a/benchmarks/memory/README.md b/benchmarks/memory/README.md
new file mode 100644
index 0000000000..d9fb24f752
--- /dev/null
+++ b/benchmarks/memory/README.md
@@ -0,0 +1,186 @@
+# Memory Benchmarks
+
+Dedicated memory benchmarks for TanStack Router / Start, measured with the
+CodSpeed **memory instrument** (`mode: memory` in
+`.github/workflows/client-nav-benchmarks.yml`). Two separate benchmarks:
+
+- `server/` (`@benchmarks/memory-server`) — React/Solid/Vue Start apps, requests against
+ the built server handler (`handler.fetch`), Node environment.
+- `client/` (`@benchmarks/memory-client`) — router-only React/Solid/Vue apps in jsdom.
+
+These deliberately do **not** reuse the CPU scenarios in `benchmarks/ssr` and
+`benchmarks/client-nav`: memory benches need their own iteration counts,
+payload sizes, and route shapes, and tuning those must never shift the CPU
+baselines. Each scenario keeps a framework level (`react/`, `solid/`, `vue/`)
+so framework ports can be added without renames.
+
+## Layout
+
+```text
+benchmarks/memory//
+ package.json Nx targets: build:, test:perf:, test:flame:, test:types
+ bench-utils.ts memoryBenchOptions, seeded LCG (+ sequential request loop on the server side)
+ vitest..config.ts aggregates scenarios/*//vite.config.ts
+ scenarios///
+ one isolated app per scenario + setup.ts + memory.bench.ts + memory.flame.ts
+```
+
+One app per scenario; apps and bench names are stable once landed (CodSpeed
+continuity). Never grow an existing scenario for a new case — add a scenario.
+`setup.ts` imports the built app and exports the concrete workload;
+`memory.bench.ts` registers `bench(...)` directly, and `memory.flame.ts` runs the
+same workload through the Flame profiler.
+
+## How the memory instrument executes a bench
+
+- The bench function is warmed up, then **measured exactly once**, starting
+ after a forced GC. Under plain `vitest bench` the suites only smoke-test:
+ timing output is meaningless; real numbers come from CodSpeed.
+- Under CodSpeed the bench fn runs several warmup invocations plus the
+ measured one **on the same mount**, so bench fns must be idempotent and
+ module-level counters/LCGs are used where ids must never repeat across
+ invocations.
+- Plain `vitest bench` never runs suite hooks (`beforeAll`/`afterAll`) and
+ only honors tinybench's `setup`/`teardown` options; the CodSpeed runner
+ does the exact opposite. Client benches therefore register **both** — in
+ any given mode exactly one pair runs.
+- The process runs with V8 determinism flags (predictable GC schedule,
+ `--no-opt`). Never call `global.gc()` manually. Because of `--no-opt`,
+ allocation counts overstate production; numbers are for regression
+ tracking, not absolute claims.
+- Keep each bench under **~1.5M allocations** (instrument overhead grows past
+ 2M); this is the main constraint when tuning iteration counts.
+
+## Bench shapes and signals
+
+- **Churn (leak detector):** N sequential iterations at steady state. If one
+ iteration leaks L bytes, peak grows by ~N·L; healthy builds show a flat
+ timeline floor independent of N. Tuning check: doubling N must leave peak
+ roughly unchanged.
+- **Peak (footprint):** one (or very few) large operations; peak memory
+ scaling with the workload is the signal.
+
+## Scenarios
+
+### Server
+
+| Scenario | Shape | Guards against |
+| ----------------------- | ----- | --------------------------------------------------------- |
+| `request-churn` | churn | cross-request retention in document SSR (unique URLs) |
+| `server-fn-churn` | churn | retention in the server-function RPC path |
+| `error-paths` | churn | redirect/notFound/error/unmatched paths pinning contexts |
+| `aborted-requests` | churn | dangling streams/listeners after mid-stream client aborts |
+| `peak-large-page` | peak | per-request peak scaling with page size |
+| `streaming-peak` | peak | streaming buffering O(document) instead of O(chunk) |
+| `serialization-payload` | peak | double-buffering / string-copy blowups in dehydration |
+
+### Client
+
+| Scenario | Shape | Guards against |
+| ------------------------- | ----- | -------------------------------------------------------- |
+| `navigation-churn` | churn | per-navigation retention at steady state |
+| `unique-location-churn` | churn | unbounded href/search-keyed caches (never-repeated URLs) |
+| `preload-churn` | churn | preload-cache eviction not releasing memory |
+| `loader-data-retention` | churn | departed routes' loader data staying pinned (gcTime 0) |
+| `mount-unmount` | churn | router instances not collectable after dispose |
+| `interrupted-navigations` | churn | superseded navigations retaining closures/contexts |
+
+## Conventions
+
+- Strictly sequential work: at most one request/navigation in flight; each
+ server response is fully consumed before the next request. Pairing a single
+ navigation with its render signal via `Promise.all([navigate, rendered])`
+ is fine — never overlap distinct work items.
+- Randomness only via the seeded LCG in `bench-utils.ts`; no `Math.random`,
+ `Date.now`, or timers — with one documented exception: `streaming-peak`'s
+ deferred sections use small `setTimeout` delays so deferred stream chunks are
+ observable across framework renderers.
+- Sanity assertions run once at module load and throw on wrong
+ status/markers, so a bench can never silently measure the wrong thing.
+- Server requests follow `benchmarks/ssr` conventions: document GETs send
+ `accept: text/html`, server-fn requests send `sec-fetch-site: same-origin`
+ with bodies precomputed at module level.
+- Client apps export `mountTestApp` from `app.tsx`; benches import the built
+ `dist/app.js`; navigations use `replace: true`; unmount does full teardown
+ (framework root, `__TSR_ROUTER__`, `history.destroy()`); large loader payloads
+ are never rendered into the DOM.
+- `NODE_ENV=production` everywhere (the Nx targets set it).
+
+## Run
+
+Smoke-test the CodSpeed/Vitest benchmark entrypoints and typecheck the
+scenarios:
+
+```bash
+pnpm nx run @benchmarks/memory-server:test:perf:react --outputStyle=stream --skipRemoteCache
+pnpm nx run @benchmarks/memory-server:test:perf:solid --outputStyle=stream --skipRemoteCache
+pnpm nx run @benchmarks/memory-server:test:perf:vue --outputStyle=stream --skipRemoteCache
+pnpm nx run @benchmarks/memory-client:test:perf:react --outputStyle=stream --skipRemoteCache
+pnpm nx run @benchmarks/memory-client:test:perf:solid --outputStyle=stream --skipRemoteCache
+pnpm nx run @benchmarks/memory-client:test:perf:vue --outputStyle=stream --skipRemoteCache
+pnpm nx run @benchmarks/memory-server:test:types --outputStyle=stream --skipRemoteCache
+pnpm nx run @benchmarks/memory-client:test:types --outputStyle=stream --skipRemoteCache
+```
+
+Local attribution profiling, without CodSpeed CLI/login/sudo/upload, uses
+`@datadog/pprof` heap sampling and `@platformatic/flame` only to render the
+captured pprof files as HTML/Markdown. These targets rebuild the scenarios with
+`--sourcemap true` so the generated profile reports can point back to source;
+the normal CodSpeed benchmark builds are unchanged. Local aggregate scripts run
+with `--parallel=1`, and scenario `test:flame` targets opt out of Nx parallelism
+so profiling workloads do not overlap and bias each other. The Vitest aggregate
+configs also set `fileParallelism: false` so benchmark files run sequentially
+inside `test:perf:react`.
+
+```bash
+pnpm benchmark:memory:server:flame
+pnpm benchmark:memory:client:flame
+pnpm benchmark:memory:server:flame:solid
+pnpm benchmark:memory:client:flame:solid
+pnpm benchmark:memory:server:flame:vue
+pnpm benchmark:memory:client:flame:vue
+```
+
+To profile one scenario, run its `test:flame` target directly:
+
+```bash
+pnpm nx run @benchmarks/memory-server-request-churn-react:test:flame --outputStyle=stream --skipRemoteCache
+pnpm nx run @benchmarks/memory-client-navigation-churn-react:test:flame --outputStyle=stream --skipRemoteCache
+```
+
+Flame writes reports under the scenario's ignored `.profiles//`
+directory, including `heap-profile-*.html` and `heap-profile-*.md`. The
+`memory.flame.ts` entrypoints run the same workload shape as `memory.bench.ts`
+but manually start profiling after sanity/setup work and stop it after the
+measured workload. Treat these profiles as diagnostic heap-sampling attribution;
+they are not CodSpeed memory metrics such as peak memory, allocated bytes, or
+allocation counts. The heap sampler is stopped before profile conversion and
+Flame report generation, so Flame/pprof report-generation work should not appear
+as part of the captured workload. Flame runs do not force GC before profiling;
+doing so would perturb the workload and still would not make heap sampling
+equivalent to CodSpeed memory metrics.
+
+Clean local Flame profile output with:
+
+```bash
+pnpm --filter @benchmarks/memory-server clean:profiles
+pnpm --filter @benchmarks/memory-client clean:profiles
+```
+
+Client memory benches are useful for regression tracking of router/React/jsdom
+integration behavior, especially retained route/cache data. They are not pure
+browser-memory measurements, and local Flame attribution can include jsdom,
+React DOM, and profiler shutdown frames.
+
+Real memory measurement, locally (requires the CodSpeed CLI, `codspeed setup`
+once to install the memory executor, and sudo; **uploads results to the
+CodSpeed dashboard** — local runs do not affect PR baselines):
+
+```bash
+WITH_INSTRUMENTATION=1 codspeed run --mode memory -- pnpm nx run @benchmarks/memory-server:test:perf:react
+WITH_INSTRUMENTATION=1 codspeed run --mode memory -- pnpm nx run @benchmarks/memory-server:test:perf:solid
+WITH_INSTRUMENTATION=1 codspeed run --mode memory -- pnpm nx run @benchmarks/memory-server:test:perf:vue
+WITH_INSTRUMENTATION=1 codspeed run --mode memory -- pnpm nx run @benchmarks/memory-client:test:perf:react
+WITH_INSTRUMENTATION=1 codspeed run --mode memory -- pnpm nx run @benchmarks/memory-client:test:perf:solid
+WITH_INSTRUMENTATION=1 codspeed run --mode memory -- pnpm nx run @benchmarks/memory-client:test:perf:vue
+```
diff --git a/benchmarks/memory/client/bench-utils.ts b/benchmarks/memory/client/bench-utils.ts
new file mode 100644
index 0000000000..0cbaedbd71
--- /dev/null
+++ b/benchmarks/memory/client/bench-utils.ts
@@ -0,0 +1,20 @@
+export const memoryBenchOptions = {
+ iterations: 1,
+ warmupIterations: 1,
+ time: 0,
+ warmupTime: 0,
+ throws: true,
+}
+
+export function createDeterministicRandom(seed: number) {
+ let state = seed >>> 0
+
+ return () => {
+ state = (state * 1664525 + 1013904223) >>> 0
+ return state / 0x100000000
+ }
+}
+
+export function randomSegment(random: () => number) {
+ return Math.floor(random() * 1_000_000_000).toString(36)
+}
diff --git a/benchmarks/memory/client/benchmark.ts b/benchmarks/memory/client/benchmark.ts
new file mode 100644
index 0000000000..8b877b0740
--- /dev/null
+++ b/benchmarks/memory/client/benchmark.ts
@@ -0,0 +1,7 @@
+export interface ClientMemoryWorkload {
+ name: string
+ before?: () => Promise | void
+ run: () => Promise | void
+ sanity: () => Promise | void
+ after?: () => Promise | void
+}
diff --git a/benchmarks/memory/client/flame-runner.ts b/benchmarks/memory/client/flame-runner.ts
new file mode 100644
index 0000000000..04941fda67
--- /dev/null
+++ b/benchmarks/memory/client/flame-runner.ts
@@ -0,0 +1,14 @@
+import { profileFlameWorkload } from '../flame-control.ts'
+import { window } from './jsdom.ts'
+import type { ClientMemoryWorkload } from './benchmark.ts'
+
+export async function runClientFlameBenchmark(workload: ClientMemoryWorkload) {
+ try {
+ await workload.sanity()
+ await workload.before?.()
+ await profileFlameWorkload(workload.run, workload.name)
+ } finally {
+ await workload.after?.()
+ window.close()
+ }
+}
diff --git a/benchmarks/memory/client/jsdom.ts b/benchmarks/memory/client/jsdom.ts
new file mode 100644
index 0000000000..4df480df68
--- /dev/null
+++ b/benchmarks/memory/client/jsdom.ts
@@ -0,0 +1,51 @@
+import { JSDOM } from 'jsdom'
+
+const dom = new JSDOM('', {
+ url: 'http://localhost/',
+})
+
+const { window } = dom
+
+function setGlobal(name: string, value: unknown) {
+ Object.defineProperty(globalThis, name, {
+ value,
+ configurable: true,
+ writable: true,
+ })
+}
+
+setGlobal('window', window)
+setGlobal('document', window.document)
+setGlobal('self', window)
+setGlobal('navigator', window.navigator)
+setGlobal('location', window.location)
+setGlobal('history', window.history)
+setGlobal('HTMLElement', window.HTMLElement)
+setGlobal('Element', window.Element)
+setGlobal('SVGElement', window.SVGElement)
+setGlobal('DocumentFragment', window.DocumentFragment)
+setGlobal('Node', window.Node)
+setGlobal('MouseEvent', window.MouseEvent)
+setGlobal('MutationObserver', window.MutationObserver)
+setGlobal('sessionStorage', window.sessionStorage)
+setGlobal('localStorage', window.localStorage)
+setGlobal('getComputedStyle', window.getComputedStyle.bind(window))
+
+setGlobal(
+ 'requestAnimationFrame',
+ window.requestAnimationFrame?.bind(window) ??
+ ((callback: (time: number) => void) =>
+ setTimeout(() => callback(performance.now()), 16)),
+)
+
+setGlobal(
+ 'cancelAnimationFrame',
+ window.cancelAnimationFrame?.bind(window) ??
+ ((handle: number) => clearTimeout(handle)),
+)
+
+const scrollTo = () => {}
+window.scrollTo = scrollTo
+setGlobal('scrollTo', scrollTo)
+
+export { window }
diff --git a/benchmarks/memory/client/lifecycle.ts b/benchmarks/memory/client/lifecycle.ts
new file mode 100644
index 0000000000..c4a62f0f96
--- /dev/null
+++ b/benchmarks/memory/client/lifecycle.ts
@@ -0,0 +1,46 @@
+export type Framework = 'react' | 'solid' | 'vue'
+
+export type MountedApp = {
+ router: unknown
+ unmount: () => void
+}
+
+export type MountTestApp = (container: HTMLDivElement) => MountedApp
+
+const frameworkNames = {
+ react: 'React',
+ solid: 'Solid',
+ vue: 'Vue',
+} satisfies Record
+
+export function noop() {}
+
+export function warnClientMemoryDevMode(framework: Framework) {
+ if (process.env.NODE_ENV !== 'production') {
+ console.warn(
+ `memory client benchmark is running without NODE_ENV=production; ${frameworkNames[framework]} dev overhead will dominate results.`,
+ )
+ }
+}
+
+export function createBenchContainer() {
+ const container = document.createElement('div')
+ document.body.append(container)
+
+ return container
+}
+
+export function removeBenchContainer(container: HTMLDivElement | undefined) {
+ container?.remove()
+}
+
+export function nextAnimationFrame() {
+ return new Promise((resolve) => {
+ requestAnimationFrame(() => resolve())
+ })
+}
+
+export async function drainMicrotasks() {
+ await Promise.resolve()
+ await Promise.resolve()
+}
diff --git a/benchmarks/memory/client/package.json b/benchmarks/memory/client/package.json
new file mode 100644
index 0000000000..2624b9e4e5
--- /dev/null
+++ b/benchmarks/memory/client/package.json
@@ -0,0 +1,260 @@
+{
+ "name": "@benchmarks/memory-client",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "clean:profiles": "rm -rf scenarios/*/*/.profiles"
+ },
+ "imports": {
+ "#memory-client/benchmark": "./benchmark.ts",
+ "#memory-client/bench-utils": "./bench-utils.ts",
+ "#memory-client/flame-runner": "./flame-runner.ts",
+ "#memory-client/lifecycle": "./lifecycle.ts"
+ },
+ "dependencies": {
+ "@tanstack/react-router": "workspace:*",
+ "@tanstack/router-core": "workspace:*",
+ "@tanstack/solid-router": "workspace:*",
+ "@tanstack/vue-router": "workspace:*",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "solid-js": "^1.9.10",
+ "vue": "^3.5.16"
+ },
+ "devDependencies": {
+ "@codspeed/vitest-plugin": "^5.5.0",
+ "@datadog/pprof": "^5.13.2",
+ "@platformatic/flame": "^1.6.0",
+ "@testing-library/react": "^16.2.0",
+ "@vitejs/plugin-react": "^6.0.1",
+ "@vitejs/plugin-vue": "^6.0.5",
+ "@vitejs/plugin-vue-jsx": "^5.1.5",
+ "@types/jsdom": "28.0.0",
+ "jsdom": "29.1.1",
+ "typescript": "^6.0.2",
+ "vite": "^8.0.14",
+ "vite-plugin-solid": "^2.11.11",
+ "vitest": "^4.1.4"
+ },
+ "nx": {
+ "targets": {
+ "build:react": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-react",
+ "@benchmarks/memory-client-unique-location-churn-react",
+ "@benchmarks/memory-client-preload-churn-react",
+ "@benchmarks/memory-client-loader-data-retention-react",
+ "@benchmarks/memory-client-mount-unmount-react",
+ "@benchmarks/memory-client-interrupted-navigations-react"
+ ],
+ "target": "build:client"
+ }
+ ]
+ },
+ "build:solid": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-solid",
+ "@benchmarks/memory-client-unique-location-churn-solid",
+ "@benchmarks/memory-client-preload-churn-solid",
+ "@benchmarks/memory-client-loader-data-retention-solid",
+ "@benchmarks/memory-client-mount-unmount-solid",
+ "@benchmarks/memory-client-interrupted-navigations-solid"
+ ],
+ "target": "build:client"
+ }
+ ]
+ },
+ "build:vue": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-vue",
+ "@benchmarks/memory-client-unique-location-churn-vue",
+ "@benchmarks/memory-client-preload-churn-vue",
+ "@benchmarks/memory-client-loader-data-retention-vue",
+ "@benchmarks/memory-client-mount-unmount-vue",
+ "@benchmarks/memory-client-interrupted-navigations-vue"
+ ],
+ "target": "build:client"
+ }
+ ]
+ },
+ "build:react:flame": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-react",
+ "@benchmarks/memory-client-unique-location-churn-react",
+ "@benchmarks/memory-client-preload-churn-react",
+ "@benchmarks/memory-client-loader-data-retention-react",
+ "@benchmarks/memory-client-mount-unmount-react",
+ "@benchmarks/memory-client-interrupted-navigations-react"
+ ],
+ "target": "build:client:flame"
+ }
+ ]
+ },
+ "build:solid:flame": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-solid",
+ "@benchmarks/memory-client-unique-location-churn-solid",
+ "@benchmarks/memory-client-preload-churn-solid",
+ "@benchmarks/memory-client-loader-data-retention-solid",
+ "@benchmarks/memory-client-mount-unmount-solid",
+ "@benchmarks/memory-client-interrupted-navigations-solid"
+ ],
+ "target": "build:client:flame"
+ }
+ ]
+ },
+ "build:vue:flame": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-vue",
+ "@benchmarks/memory-client-unique-location-churn-vue",
+ "@benchmarks/memory-client-preload-churn-vue",
+ "@benchmarks/memory-client-loader-data-retention-vue",
+ "@benchmarks/memory-client-mount-unmount-vue",
+ "@benchmarks/memory-client-interrupted-navigations-vue"
+ ],
+ "target": "build:client:flame"
+ }
+ ]
+ },
+ "test:flame:react": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-react",
+ "@benchmarks/memory-client-unique-location-churn-react",
+ "@benchmarks/memory-client-preload-churn-react",
+ "@benchmarks/memory-client-loader-data-retention-react",
+ "@benchmarks/memory-client-mount-unmount-react",
+ "@benchmarks/memory-client-interrupted-navigations-react"
+ ],
+ "target": "test:flame"
+ }
+ ]
+ },
+ "test:flame:solid": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-solid",
+ "@benchmarks/memory-client-unique-location-churn-solid",
+ "@benchmarks/memory-client-preload-churn-solid",
+ "@benchmarks/memory-client-loader-data-retention-solid",
+ "@benchmarks/memory-client-mount-unmount-solid",
+ "@benchmarks/memory-client-interrupted-navigations-solid"
+ ],
+ "target": "test:flame"
+ }
+ ]
+ },
+ "test:flame:vue": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-vue",
+ "@benchmarks/memory-client-unique-location-churn-vue",
+ "@benchmarks/memory-client-preload-churn-vue",
+ "@benchmarks/memory-client-loader-data-retention-vue",
+ "@benchmarks/memory-client-mount-unmount-vue",
+ "@benchmarks/memory-client-interrupted-navigations-vue"
+ ],
+ "target": "test:flame"
+ }
+ ]
+ },
+ "test:perf:react": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": [
+ "build:react"
+ ],
+ "options": {
+ "command": "NODE_ENV=production vitest bench --config ./vitest.react.config.ts",
+ "cwd": "benchmarks/memory/client"
+ }
+ },
+ "test:perf:solid": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": [
+ "build:solid"
+ ],
+ "options": {
+ "command": "NODE_ENV=production vitest bench --config ./vitest.solid.config.ts",
+ "cwd": "benchmarks/memory/client"
+ }
+ },
+ "test:perf:vue": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": [
+ "build:vue"
+ ],
+ "options": {
+ "command": "NODE_ENV=production vitest bench --config ./vitest.vue.config.ts",
+ "cwd": "benchmarks/memory/client"
+ }
+ },
+ "test:types": {
+ "executor": "nx:noop",
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-client-navigation-churn-react",
+ "@benchmarks/memory-client-unique-location-churn-react",
+ "@benchmarks/memory-client-preload-churn-react",
+ "@benchmarks/memory-client-loader-data-retention-react",
+ "@benchmarks/memory-client-mount-unmount-react",
+ "@benchmarks/memory-client-interrupted-navigations-react",
+ "@benchmarks/memory-client-navigation-churn-solid",
+ "@benchmarks/memory-client-unique-location-churn-solid",
+ "@benchmarks/memory-client-preload-churn-solid",
+ "@benchmarks/memory-client-loader-data-retention-solid",
+ "@benchmarks/memory-client-mount-unmount-solid",
+ "@benchmarks/memory-client-interrupted-navigations-solid",
+ "@benchmarks/memory-client-navigation-churn-vue",
+ "@benchmarks/memory-client-unique-location-churn-vue",
+ "@benchmarks/memory-client-preload-churn-vue",
+ "@benchmarks/memory-client-loader-data-retention-vue",
+ "@benchmarks/memory-client-mount-unmount-vue",
+ "@benchmarks/memory-client-interrupted-navigations-vue"
+ ],
+ "target": "test:types:client"
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/memory.bench.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/react/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/memory.flame.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/react/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/project.json b/benchmarks/memory/client/scenarios/interrupted-navigations/react/project.json
new file mode 100644
index 0000000000..0ed437c9ca
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-interrupted-navigations-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/setup.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/react/setup.ts
new file mode 100644
index 0000000000..6e072ad5f3
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/setup.ts
@@ -0,0 +1,19 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const {
+ mountTestApp,
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+} = (await import(/* @vite-ignore */ appModulePath)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'react',
+ mountTestApp,
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+)
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/app.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/app.tsx
new file mode 100644
index 0000000000..bd605a78ee
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/app.tsx
@@ -0,0 +1,37 @@
+import { RouterProvider } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { getRouter } from './router'
+
+export {
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+} from '../../slow-loaders'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const reactRoot = createRoot(container)
+ let didUnmount = false
+
+ reactRoot.render()
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ reactRoot.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..475085a433
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routeTree.gen.ts
@@ -0,0 +1,95 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as SlowIdRouteImport } from './routes/slow.$id'
+import { Route as FastIdRouteImport } from './routes/fast.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SlowIdRoute = SlowIdRouteImport.update({
+ id: '/slow/$id',
+ path: '/slow/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FastIdRoute = FastIdRouteImport.update({
+ id: '/fast/$id',
+ path: '/fast/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/fast/$id' | '/slow/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/fast/$id' | '/slow/$id'
+ id: '__root__' | '/' | '/fast/$id' | '/slow/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ FastIdRoute: typeof FastIdRoute
+ SlowIdRoute: typeof SlowIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/slow/$id': {
+ id: '/slow/$id'
+ path: '/slow/$id'
+ fullPath: '/slow/$id'
+ preLoaderRoute: typeof SlowIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/fast/$id': {
+ id: '/fast/$id'
+ path: '/fast/$id'
+ fullPath: '/fast/$id'
+ preLoaderRoute: typeof FastIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ FastIdRoute: FastIdRoute,
+ SlowIdRoute: SlowIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/router.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/router.tsx
new file mode 100644
index 0000000000..ac27b20f79
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..889395056b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/fast.$id.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/fast.$id.tsx
new file mode 100644
index 0000000000..d5fd7ade46
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/fast.$id.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { fixedTimestamp } from '../../../slow-loaders'
+
+export const Route = createFileRoute('/fast/$id')({
+ loader: ({ params }) => ({
+ id: params.id,
+ kind: 'fast' as const,
+ ts: fixedTimestamp,
+ }),
+ component: FastComponent,
+})
+
+function FastComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data.kind}:${data.id}:${data.ts}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/index.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/index.tsx
new file mode 100644
index 0000000000..a425ac79c4
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return shell
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/slow.$id.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/slow.$id.tsx
new file mode 100644
index 0000000000..61bbf328b7
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/slow.$id.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { getSlowLoaderDeferred } from '../../../slow-loaders'
+
+export const Route = createFileRoute('/slow/$id')({
+ loader: async ({ params }) => {
+ const deferred = getSlowLoaderDeferred(params.id)
+
+ return await deferred.promise
+ },
+ component: SlowComponent,
+})
+
+function SlowComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data.kind}:${data.id}:${data.ts}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/tsconfig.json b/benchmarks/memory/client/scenarios/interrupted-navigations/react/tsconfig.json
new file mode 100644
index 0000000000..ea566061d9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/react/vite.config.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/react/vite.config.ts
new file mode 100644
index 0000000000..048a9cc833
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/react/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client interrupted-navigations (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts
new file mode 100644
index 0000000000..0e01e92e08
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts
@@ -0,0 +1,320 @@
+import {
+ createDeterministicRandom,
+ randomSegment,
+} from '#memory-client/bench-utils'
+import {
+ createBenchContainer,
+ drainMicrotasks,
+ nextAnimationFrame,
+ noop,
+ removeBenchContainer,
+ warnClientMemoryDevMode,
+} from '#memory-client/lifecycle'
+import type { Framework, MountTestApp } from '#memory-client/lifecycle'
+
+type NavigationSettlement =
+ | {
+ status: 'fulfilled'
+ value: void
+ }
+ | {
+ status: 'rejected'
+ reason: unknown
+ }
+
+type ResolveAllSlowLoaders = () => void
+type ResolveSlowLoader = (id: string) => void
+type SlowLoaderRegistry = {
+ has: (id: string) => boolean
+}
+
+type RenderedEvent = {
+ toLocation: {
+ pathname: string
+ }
+}
+
+type InterruptedNavigationRouter = {
+ latestLoadPromise: Promise | undefined
+ load: () => Promise
+ navigate: (options: {
+ to: '/fast/$id' | '/slow/$id'
+ params: { id: string }
+ replace: true
+ }) => Promise
+ subscribe: (
+ event: 'onRendered',
+ listener: (event: RenderedEvent) => void,
+ ) => () => void
+}
+
+const interruptedNavigationIterations = 150
+const interruptedNavigationPairs = createInterruptedNavigationPairs(
+ interruptedNavigationIterations,
+)
+
+const uninitialized = () =>
+ Promise.reject(
+ new Error('interrupted-navigations benchmark is not initialized'),
+ )
+
+const uninitializedSettlement = () =>
+ Promise.resolve({
+ status: 'rejected',
+ reason: new Error('interrupted-navigations benchmark is not initialized'),
+ })
+
+function createInterruptedNavigationPairs(iterations: number) {
+ const random = createDeterministicRandom(13)
+
+ return Array.from({ length: iterations }, (_, index) => ({
+ slowId: `slow-${index}-${randomSegment(random)}`,
+ fastId: `fast-${index}-${randomSegment(random)}`,
+ }))
+}
+
+function formatReason(reason: unknown) {
+ if (reason instanceof Error) {
+ return `${reason.name}: ${reason.message}`
+ }
+
+ return String(reason)
+}
+
+function assertSlowNavigationSettlement(settlement: NavigationSettlement) {
+ if (settlement.status === 'fulfilled') {
+ if (settlement.value !== undefined) {
+ throw new Error('Expected slow navigation to fulfill with undefined')
+ }
+
+ return
+ }
+
+ if (
+ reasonHasAbortShape(settlement.reason) ||
+ reasonHasCancellationShape(settlement.reason)
+ ) {
+ return
+ }
+
+ throw new Error(
+ `Expected slow navigation to settle as void or cancellation, got ${formatReason(
+ settlement.reason,
+ )}`,
+ )
+}
+
+async function awaitExpectedLoadSettlement(loadPromise: Promise) {
+ try {
+ await loadPromise
+ } catch (reason) {
+ if (reasonHasAbortShape(reason) || reasonHasCancellationShape(reason)) {
+ return
+ }
+
+ throw reason
+ }
+}
+
+function reasonHasAbortShape(reason: unknown) {
+ return reason instanceof DOMException && reason.name === 'AbortError'
+}
+
+function reasonHasCancellationShape(reason: unknown) {
+ return (
+ reason instanceof Error &&
+ (reason.name === 'AbortError' || reason.name === 'CancelledError')
+ )
+}
+
+export function createWorkload(
+ framework: Framework,
+ mountTestApp: MountTestApp,
+ resolveAllSlowLoaders: ResolveAllSlowLoaders,
+ resolveSlowLoader: ResolveSlowLoader,
+ slowLoaderRegistry: SlowLoaderRegistry,
+) {
+ warnClientMemoryDevMode(framework)
+
+ let container: HTMLDivElement | undefined = undefined
+ let unmount = noop
+ let unsub = noop
+ let resolveRendered: () => void = noop
+ let expectedRenderedPath: string | undefined = undefined
+ let navigateFast: (id: string) => Promise = uninitialized
+ let startSlowNavigation: (id: string) => Promise =
+ uninitializedSettlement
+ let getLatestLoadPromise: () => Promise | undefined = () => undefined
+
+ function assertRenderedPage(page: 'shell' | 'fast', id?: string) {
+ const element = container?.querySelector('[data-bench-page]')
+ const actualPage = element?.dataset.benchPage
+ const actualId = element?.dataset.benchId
+
+ if (actualPage !== page) {
+ throw new Error(`Expected rendered page ${page}, got ${actualPage}`)
+ }
+
+ if (id !== undefined && actualId !== id) {
+ throw new Error(`Expected rendered id ${id}, got ${actualId}`)
+ }
+ }
+
+ async function waitForRenderedPage(page: 'shell' | 'fast', id?: string) {
+ for (let attempt = 0; attempt < 10; attempt++) {
+ try {
+ assertRenderedPage(page, id)
+ return
+ } catch {
+ await nextAnimationFrame()
+ }
+ }
+
+ assertRenderedPage(page, id)
+ }
+
+ async function waitForSlowLoader(id: string) {
+ for (let attempt = 0; attempt < 20; attempt++) {
+ if (slowLoaderRegistry.has(id)) {
+ return
+ }
+
+ await drainMicrotasks()
+ }
+
+ throw new Error(`Slow loader was not registered for id: ${id}`)
+ }
+
+ function waitForNextRender(pathname: string) {
+ expectedRenderedPath = pathname
+
+ return new Promise((resolve) => {
+ resolveRendered = resolve
+ })
+ }
+
+ async function before() {
+ if (container) {
+ after()
+ }
+
+ container = createBenchContainer()
+
+ const mounted = mountTestApp(container)
+ const router = mounted.router as InterruptedNavigationRouter
+ unmount = mounted.unmount
+ getLatestLoadPromise = () => router.latestLoadPromise
+
+ unsub = router.subscribe('onRendered', (event) => {
+ if (
+ expectedRenderedPath &&
+ event.toLocation.pathname !== expectedRenderedPath
+ ) {
+ return
+ }
+
+ const resolve = resolveRendered
+ resolveRendered = noop
+ expectedRenderedPath = undefined
+ resolve()
+ })
+
+ navigateFast = async (id) => {
+ const rendered = waitForNextRender(`/fast/${id}`)
+ await Promise.all([
+ router.navigate({
+ to: '/fast/$id',
+ params: { id },
+ replace: true,
+ }),
+ rendered,
+ ])
+ }
+
+ startSlowNavigation = (id) => {
+ const navigation = router.navigate({
+ to: '/slow/$id',
+ params: { id },
+ replace: true,
+ })
+
+ return navigation
+ .then((value): NavigationSettlement => ({ status: 'fulfilled', value }))
+ .catch(
+ (reason: unknown): NavigationSettlement => ({
+ status: 'rejected',
+ reason,
+ }),
+ )
+ }
+
+ await router.load()
+ await waitForRenderedPage('shell')
+ }
+
+ function after() {
+ resolveAllSlowLoaders()
+ unmount()
+ removeBenchContainer(container)
+ unsub()
+
+ container = undefined
+ unmount = noop
+ unsub = noop
+ resolveRendered = noop
+ expectedRenderedPath = undefined
+ navigateFast = uninitialized
+ startSlowNavigation = uninitializedSettlement
+ getLatestLoadPromise = () => undefined
+ }
+
+ async function interrupt(
+ slowId: string,
+ fastId: string,
+ assertSettlement = true,
+ ) {
+ const slowNavigation = startSlowNavigation(slowId)
+
+ await waitForSlowLoader(slowId)
+ const slowLoadPromise = getLatestLoadPromise()
+
+ if (!slowLoadPromise) {
+ throw new Error(`Slow navigation did not start a load for id: ${slowId}`)
+ }
+
+ await navigateFast(fastId)
+ resolveSlowLoader(slowId)
+
+ const settlement = await slowNavigation
+
+ if (assertSettlement) {
+ assertSlowNavigationSettlement(settlement)
+ }
+
+ await awaitExpectedLoadSettlement(slowLoadPromise)
+ await drainMicrotasks()
+ }
+
+ return {
+ name: `mem interrupted-navigations (${framework})`,
+ before,
+ interrupt,
+ async run() {
+ for (const pair of interruptedNavigationPairs) {
+ await interrupt(pair.slowId, pair.fastId)
+ }
+ },
+ async sanity() {
+ await before()
+
+ try {
+ assertRenderedPage('shell')
+ await interrupt('sanity-slow', 'sanity-fast', false)
+ assertRenderedPage('fast', 'sanity-fast')
+ } finally {
+ after()
+ }
+ },
+ after,
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/slow-loaders.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/slow-loaders.ts
new file mode 100644
index 0000000000..c70ccac3da
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/slow-loaders.ts
@@ -0,0 +1,54 @@
+export const fixedTimestamp = 1_700_000_000_000
+
+type SlowLoaderPayload = {
+ id: string
+ kind: 'slow'
+ ts: number
+}
+
+type SlowLoaderDeferred = {
+ promise: Promise
+ resolve: () => void
+}
+
+export const slowLoaderRegistry = new Map()
+
+export function getSlowLoaderDeferred(id: string) {
+ const existing = slowLoaderRegistry.get(id)
+
+ if (existing) {
+ return existing
+ }
+
+ let resolvePromise!: (payload: SlowLoaderPayload) => void
+ const promise = new Promise((resolve) => {
+ resolvePromise = resolve
+ })
+
+ const deferred: SlowLoaderDeferred = {
+ promise,
+ resolve() {
+ slowLoaderRegistry.delete(id)
+ resolvePromise({ id, kind: 'slow', ts: fixedTimestamp })
+ },
+ }
+
+ slowLoaderRegistry.set(id, deferred)
+ return deferred
+}
+
+export function resolveSlowLoader(id: string) {
+ const deferred = slowLoaderRegistry.get(id)
+
+ if (!deferred) {
+ throw new Error(`No pending slow loader for id: ${id}`)
+ }
+
+ deferred.resolve()
+}
+
+export function resolveAllSlowLoaders() {
+ for (const id of Array.from(slowLoaderRegistry.keys())) {
+ resolveSlowLoader(id)
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/memory.bench.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/memory.flame.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/project.json b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/project.json
new file mode 100644
index 0000000000..4aff39500e
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-interrupted-navigations-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/setup.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/setup.ts
new file mode 100644
index 0000000000..e736111663
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/setup.ts
@@ -0,0 +1,19 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const {
+ mountTestApp,
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+} = (await import(/* @vite-ignore */ appModulePath)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'solid',
+ mountTestApp,
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+)
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/app.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/app.tsx
new file mode 100644
index 0000000000..6288b5fab1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/app.tsx
@@ -0,0 +1,35 @@
+import { RouterProvider } from '@tanstack/solid-router'
+import { render } from 'solid-js/web'
+import { getRouter } from './router'
+
+export {
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+} from '../../slow-loaders'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const dispose = render(() => , container)
+ let didUnmount = false
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ dispose()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..f42255963c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routeTree.gen.ts
@@ -0,0 +1,95 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as SlowIdRouteImport } from './routes/slow.$id'
+import { Route as FastIdRouteImport } from './routes/fast.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SlowIdRoute = SlowIdRouteImport.update({
+ id: '/slow/$id',
+ path: '/slow/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FastIdRoute = FastIdRouteImport.update({
+ id: '/fast/$id',
+ path: '/fast/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/fast/$id' | '/slow/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/fast/$id' | '/slow/$id'
+ id: '__root__' | '/' | '/fast/$id' | '/slow/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ FastIdRoute: typeof FastIdRoute
+ SlowIdRoute: typeof SlowIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/slow/$id': {
+ id: '/slow/$id'
+ path: '/slow/$id'
+ fullPath: '/slow/$id'
+ preLoaderRoute: typeof SlowIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/fast/$id': {
+ id: '/fast/$id'
+ path: '/fast/$id'
+ fullPath: '/fast/$id'
+ preLoaderRoute: typeof FastIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ FastIdRoute: FastIdRoute,
+ SlowIdRoute: SlowIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/router.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/router.tsx
new file mode 100644
index 0000000000..884aa3438f
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..cb8d5a688d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/fast.$id.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/fast.$id.tsx
new file mode 100644
index 0000000000..511e4712b7
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/fast.$id.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { fixedTimestamp } from '../../../slow-loaders'
+
+export const Route = createFileRoute('/fast/$id')({
+ loader: ({ params }) => ({
+ id: params.id,
+ kind: 'fast' as const,
+ ts: fixedTimestamp,
+ }),
+ component: FastComponent,
+})
+
+function FastComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data().kind}:${data().id}:${data().ts}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/index.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..592b2d666e
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return shell
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/slow.$id.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/slow.$id.tsx
new file mode 100644
index 0000000000..9aaaef5568
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/slow.$id.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { getSlowLoaderDeferred } from '../../../slow-loaders'
+
+export const Route = createFileRoute('/slow/$id')({
+ loader: async ({ params }) => {
+ const deferred = getSlowLoaderDeferred(params.id)
+
+ return await deferred.promise
+ },
+ component: SlowComponent,
+})
+
+function SlowComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data().kind}:${data().id}:${data().ts}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/tsconfig.json b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/tsconfig.json
new file mode 100644
index 0000000000..b12dcb7ade
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/solid/vite.config.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/vite.config.ts
new file mode 100644
index 0000000000..e710e0fd12
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client interrupted-navigations (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/memory.bench.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/memory.flame.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/project.json b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/project.json
new file mode 100644
index 0000000000..01288cb235
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-interrupted-navigations-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/setup.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/setup.ts
new file mode 100644
index 0000000000..260e45b64b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/setup.ts
@@ -0,0 +1,19 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const {
+ mountTestApp,
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+} = (await import(/* @vite-ignore */ appModulePath)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'vue',
+ mountTestApp,
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+)
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/app.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/app.tsx
new file mode 100644
index 0000000000..1abb4410dc
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/app.tsx
@@ -0,0 +1,42 @@
+import { RouterProvider } from '@tanstack/vue-router'
+import { createApp } from 'vue'
+import { getRouter } from './router'
+import type {} from '@tanstack/router-core'
+
+export {
+ resolveAllSlowLoaders,
+ resolveSlowLoader,
+ slowLoaderRegistry,
+} from '../../slow-loaders'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const vueApp = createApp({
+ setup() {
+ return () =>
+ },
+ })
+ let didUnmount = false
+
+ vueApp.mount(container)
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ vueApp.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..5ea61a89a3
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routeTree.gen.ts
@@ -0,0 +1,95 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as SlowIdRouteImport } from './routes/slow.$id'
+import { Route as FastIdRouteImport } from './routes/fast.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SlowIdRoute = SlowIdRouteImport.update({
+ id: '/slow/$id',
+ path: '/slow/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FastIdRoute = FastIdRouteImport.update({
+ id: '/fast/$id',
+ path: '/fast/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/fast/$id': typeof FastIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/fast/$id' | '/slow/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/fast/$id' | '/slow/$id'
+ id: '__root__' | '/' | '/fast/$id' | '/slow/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ FastIdRoute: typeof FastIdRoute
+ SlowIdRoute: typeof SlowIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/slow/$id': {
+ id: '/slow/$id'
+ path: '/slow/$id'
+ fullPath: '/slow/$id'
+ preLoaderRoute: typeof SlowIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/fast/$id': {
+ id: '/fast/$id'
+ path: '/fast/$id'
+ fullPath: '/fast/$id'
+ preLoaderRoute: typeof FastIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ FastIdRoute: FastIdRoute,
+ SlowIdRoute: SlowIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/router.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/router.tsx
new file mode 100644
index 0000000000..4655fe8424
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..91296e6f84
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/fast.$id.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/fast.$id.tsx
new file mode 100644
index 0000000000..3bd820bd07
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/fast.$id.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { fixedTimestamp } from '../../../slow-loaders'
+
+export const Route = createFileRoute('/fast/$id')({
+ loader: ({ params }: { params: { id: string } }) => ({
+ id: params.id,
+ kind: 'fast' as const,
+ ts: fixedTimestamp,
+ }),
+ component: FastComponent,
+})
+
+function FastComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data.value.kind}:${data.value.id}:${data.value.ts}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/index.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..c30a51e1e5
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return shell
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/slow.$id.tsx b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/slow.$id.tsx
new file mode 100644
index 0000000000..68885fb918
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/slow.$id.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { getSlowLoaderDeferred } from '../../../slow-loaders'
+
+export const Route = createFileRoute('/slow/$id')({
+ loader: async ({ params }: { params: { id: string } }) => {
+ const deferred = getSlowLoaderDeferred(params.id)
+
+ return await deferred.promise
+ },
+ component: SlowComponent,
+})
+
+function SlowComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data.value.kind}:${data.value.id}:${data.value.ts}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/tsconfig.json b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/tsconfig.json
new file mode 100644
index 0000000000..9a5872a4c0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/vue/vite.config.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/vite.config.ts
new file mode 100644
index 0000000000..b2912ce9d1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/interrupted-navigations/vue/vite.config.ts
@@ -0,0 +1,36 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client interrupted-navigations (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/loader-data.ts b/benchmarks/memory/client/scenarios/loader-data-retention/loader-data.ts
new file mode 100644
index 0000000000..3bd59c82ef
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/loader-data.ts
@@ -0,0 +1,48 @@
+import {
+ createDeterministicRandom,
+ randomSegment,
+} from '#memory-client/bench-utils'
+
+export const loaderPayloadRecordCount = 512
+
+const loaderPayloadCharsPerRecord = 1024
+
+type LoaderRecord = {
+ key: string
+ value: string
+}
+
+export function createLoaderData(id: string) {
+ const random = createDeterministicRandom(hashId(id))
+ const records: Array = []
+
+ for (let index = 0; index < loaderPayloadRecordCount; index++) {
+ records.push({
+ key: `${id}:${index}:${randomSegment(random)}`,
+ value: createRecordValue(id, index, random),
+ })
+ }
+
+ return { id, records }
+}
+
+function createRecordValue(id: string, index: number, random: () => number) {
+ const firstSegment = randomSegment(random)
+ const secondSegment = randomSegment(random)
+ const segment = `${id}:${index}:${firstSegment}:${secondSegment}:`
+
+ return segment
+ .repeat(Math.ceil(loaderPayloadCharsPerRecord / segment.length))
+ .slice(0, loaderPayloadCharsPerRecord)
+}
+
+function hashId(id: string) {
+ let hash = 2166136261
+
+ for (let index = 0; index < id.length; index++) {
+ hash ^= id.charCodeAt(index)
+ hash = Math.imul(hash, 16777619)
+ }
+
+ return hash >>> 0
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/memory.bench.ts b/benchmarks/memory/client/scenarios/loader-data-retention/react/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/memory.flame.ts b/benchmarks/memory/client/scenarios/loader-data-retention/react/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/project.json b/benchmarks/memory/client/scenarios/loader-data-retention/react/project.json
new file mode 100644
index 0000000000..713038e20f
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-loader-data-retention-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/setup.ts b/benchmarks/memory/client/scenarios/loader-data-retention/react/setup.ts
new file mode 100644
index 0000000000..fafaf13e6f
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { loaderPayloadRecordCount, mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'react',
+ mountTestApp,
+ loaderPayloadRecordCount,
+)
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/src/app.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/app.tsx
new file mode 100644
index 0000000000..5eef150c2c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/app.tsx
@@ -0,0 +1,33 @@
+import { RouterProvider } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { getRouter } from './router'
+
+export { loaderPayloadRecordCount } from '../../loader-data'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const reactRoot = createRoot(container)
+ let didUnmount = false
+
+ reactRoot.render()
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ reactRoot.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..ea96d809c8
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routeTree.gen.ts
@@ -0,0 +1,102 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ShellRouteImport } from './routes/shell'
+import { Route as PageIdRouteImport } from './routes/page.$id'
+import { Route as ShellIndexRouteImport } from './routes/shell.index'
+
+const ShellRoute = ShellRouteImport.update({
+ id: '/shell',
+ path: '/shell',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PageIdRoute = PageIdRouteImport.update({
+ id: '/page/$id',
+ path: '/page/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ShellIndexRoute = ShellIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => ShellRoute,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/shell': typeof ShellRouteWithChildren
+ '/page/$id': typeof PageIdRoute
+ '/shell/': typeof ShellIndexRoute
+}
+export interface FileRoutesByTo {
+ '/page/$id': typeof PageIdRoute
+ '/shell': typeof ShellIndexRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/shell': typeof ShellRouteWithChildren
+ '/page/$id': typeof PageIdRoute
+ '/shell/': typeof ShellIndexRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/shell' | '/page/$id' | '/shell/'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/page/$id' | '/shell'
+ id: '__root__' | '/shell' | '/page/$id' | '/shell/'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ShellRoute: typeof ShellRouteWithChildren
+ PageIdRoute: typeof PageIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/shell': {
+ id: '/shell'
+ path: '/shell'
+ fullPath: '/shell'
+ preLoaderRoute: typeof ShellRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/page/$id': {
+ id: '/page/$id'
+ path: '/page/$id'
+ fullPath: '/page/$id'
+ preLoaderRoute: typeof PageIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/shell/': {
+ id: '/shell/'
+ path: '/'
+ fullPath: '/shell/'
+ preLoaderRoute: typeof ShellIndexRouteImport
+ parentRoute: typeof ShellRoute
+ }
+ }
+}
+
+interface ShellRouteChildren {
+ ShellIndexRoute: typeof ShellIndexRoute
+}
+
+const ShellRouteChildren: ShellRouteChildren = {
+ ShellIndexRoute: ShellIndexRoute,
+}
+
+const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ ShellRoute: ShellRouteWithChildren,
+ PageIdRoute: PageIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/src/router.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/router.tsx
new file mode 100644
index 0000000000..5a5c8210d9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/router.tsx
@@ -0,0 +1,19 @@
+import { createMemoryHistory, createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/shell'],
+ }),
+ routeTree,
+ defaultGcTime: 0,
+ defaultPreloadGcTime: 0,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..889395056b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/page.$id.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/page.$id.tsx
new file mode 100644
index 0000000000..63af9ed429
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/page.$id.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { createLoaderData } from '../../../loader-data'
+
+export const Route = createFileRoute('/page/$id')({
+ loader: ({ params }) => createLoaderData(params.id),
+ component: PageComponent,
+})
+
+function PageComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data.id}:${data.records.length}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/shell.index.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/shell.index.tsx
new file mode 100644
index 0000000000..d78bb8bfb9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/shell.index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/shell/')({
+ component: ShellIndexComponent,
+})
+
+function ShellIndexComponent() {
+ return ready
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/shell.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/shell.tsx
new file mode 100644
index 0000000000..024a58c243
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/shell.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/shell')({
+ component: ShellComponent,
+})
+
+function ShellComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/tsconfig.json b/benchmarks/memory/client/scenarios/loader-data-retention/react/tsconfig.json
new file mode 100644
index 0000000000..ea566061d9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/react/vite.config.ts b/benchmarks/memory/client/scenarios/loader-data-retention/react/vite.config.ts
new file mode 100644
index 0000000000..555e5f5612
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/react/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client loader-data-retention (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/shared.ts b/benchmarks/memory/client/scenarios/loader-data-retention/shared.ts
new file mode 100644
index 0000000000..a5c496cb81
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/shared.ts
@@ -0,0 +1,187 @@
+import {
+ createDeterministicRandom,
+ randomSegment,
+} from '#memory-client/bench-utils'
+import {
+ createBenchContainer,
+ nextAnimationFrame,
+ noop,
+ removeBenchContainer,
+ warnClientMemoryDevMode,
+} from '#memory-client/lifecycle'
+import type { Framework, MountTestApp } from '#memory-client/lifecycle'
+
+type RenderEvent = {
+ toLocation: {
+ pathname: string
+ }
+}
+
+type LoaderDataRouter = {
+ load: () => Promise
+ navigate: (options: {
+ to: '/page/$id'
+ params: { id: string }
+ replace: true
+ }) => Promise
+ subscribe: (
+ event: 'onRendered',
+ listener: (event: RenderEvent) => void,
+ ) => () => void
+}
+
+const loaderDataRetentionNavigationCount = 20
+const pageIds = createPageIds()
+
+const uninitialized = () =>
+ Promise.reject(
+ new Error('loader-data-retention benchmark is not initialized'),
+ )
+
+function createPageIds() {
+ const random = createDeterministicRandom(11)
+
+ return Array.from(
+ { length: loaderDataRetentionNavigationCount },
+ (_, index) => `${index}-${randomSegment(random)}`,
+ )
+}
+
+export function createWorkload(
+ framework: Framework,
+ mountTestApp: MountTestApp,
+ loaderPayloadRecordCount: number,
+) {
+ warnClientMemoryDevMode(framework)
+
+ let container: HTMLDivElement | undefined = undefined
+ let unmount = noop
+ let unsub = noop
+ let resolveRendered: () => void = noop
+ let expectedRenderedPath: string | undefined = undefined
+ let navigateTo: (id: string) => Promise = uninitialized
+
+ function assertRenderedShell() {
+ const actual =
+ container?.querySelector('[data-bench-page]')?.dataset
+ .benchPage
+
+ if (actual !== 'shell') {
+ throw new Error(`Expected rendered shell page, got ${actual}`)
+ }
+ }
+
+ function assertRenderedPage(id: string) {
+ const page = container?.querySelector(
+ '[data-bench-page="page"]',
+ )
+ const actualId = page?.dataset.benchId
+ const actualCount = page?.dataset.benchCount
+ const expectedCount = String(loaderPayloadRecordCount)
+
+ if (actualId !== id || actualCount !== expectedCount) {
+ throw new Error(
+ `Expected rendered page ${id}:${expectedCount}, got ${actualId}:${actualCount}`,
+ )
+ }
+ }
+
+ async function waitForRenderedShell() {
+ for (let attempt = 0; attempt < 10; attempt++) {
+ try {
+ assertRenderedShell()
+ return
+ } catch {
+ await nextAnimationFrame()
+ }
+ }
+
+ assertRenderedShell()
+ }
+
+ function waitForNextRender(pathname: string) {
+ expectedRenderedPath = pathname
+
+ return new Promise((resolve) => {
+ resolveRendered = resolve
+ })
+ }
+
+ async function before() {
+ if (container) {
+ after()
+ }
+
+ container = createBenchContainer()
+
+ const mounted = mountTestApp(container)
+ const router = mounted.router as LoaderDataRouter
+ unmount = mounted.unmount
+
+ unsub = router.subscribe('onRendered', (event) => {
+ if (
+ expectedRenderedPath &&
+ event.toLocation.pathname !== expectedRenderedPath
+ ) {
+ return
+ }
+
+ const resolve = resolveRendered
+ resolveRendered = noop
+ expectedRenderedPath = undefined
+ resolve()
+ })
+
+ navigateTo = async (id) => {
+ const pathname = `/page/${id}`
+ const rendered = waitForNextRender(pathname)
+
+ await router.navigate({
+ to: '/page/$id',
+ params: { id },
+ replace: true,
+ })
+ await rendered
+ assertRenderedPage(id)
+ }
+
+ await router.load()
+ await waitForRenderedShell()
+ }
+
+ function after() {
+ unmount()
+ removeBenchContainer(container)
+ unsub()
+
+ container = undefined
+ unmount = noop
+ unsub = noop
+ resolveRendered = noop
+ expectedRenderedPath = undefined
+ navigateTo = uninitialized
+ }
+
+ return {
+ name: `mem loader-data-retention (${framework})`,
+ before,
+ navigate: (id: string) => navigateTo(id),
+ async run() {
+ for (const id of pageIds) {
+ await navigateTo(id)
+ }
+ },
+ async sanity() {
+ await before()
+
+ try {
+ assertRenderedShell()
+ await navigateTo('sanity-a')
+ assertRenderedPage('sanity-a')
+ } finally {
+ after()
+ }
+ },
+ after,
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/memory.bench.ts b/benchmarks/memory/client/scenarios/loader-data-retention/solid/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/memory.flame.ts b/benchmarks/memory/client/scenarios/loader-data-retention/solid/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/project.json b/benchmarks/memory/client/scenarios/loader-data-retention/solid/project.json
new file mode 100644
index 0000000000..6c85f51896
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-loader-data-retention-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/setup.ts b/benchmarks/memory/client/scenarios/loader-data-retention/solid/setup.ts
new file mode 100644
index 0000000000..b7c80a3e49
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { loaderPayloadRecordCount, mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'solid',
+ mountTestApp,
+ loaderPayloadRecordCount,
+)
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/app.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/app.tsx
new file mode 100644
index 0000000000..4315a4b15c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/app.tsx
@@ -0,0 +1,31 @@
+import { RouterProvider } from '@tanstack/solid-router'
+import { render } from 'solid-js/web'
+import { getRouter } from './router'
+
+export { loaderPayloadRecordCount } from '../../loader-data'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const dispose = render(() => , container)
+ let didUnmount = false
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ dispose()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..a1946d187d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routeTree.gen.ts
@@ -0,0 +1,102 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ShellRouteImport } from './routes/shell'
+import { Route as ShellIndexRouteImport } from './routes/shell.index'
+import { Route as PageIdRouteImport } from './routes/page.$id'
+
+const ShellRoute = ShellRouteImport.update({
+ id: '/shell',
+ path: '/shell',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ShellIndexRoute = ShellIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => ShellRoute,
+} as any)
+const PageIdRoute = PageIdRouteImport.update({
+ id: '/page/$id',
+ path: '/page/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/shell': typeof ShellRouteWithChildren
+ '/page/$id': typeof PageIdRoute
+ '/shell/': typeof ShellIndexRoute
+}
+export interface FileRoutesByTo {
+ '/page/$id': typeof PageIdRoute
+ '/shell': typeof ShellIndexRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/shell': typeof ShellRouteWithChildren
+ '/page/$id': typeof PageIdRoute
+ '/shell/': typeof ShellIndexRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/shell' | '/page/$id' | '/shell/'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/page/$id' | '/shell'
+ id: '__root__' | '/shell' | '/page/$id' | '/shell/'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ShellRoute: typeof ShellRouteWithChildren
+ PageIdRoute: typeof PageIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/shell': {
+ id: '/shell'
+ path: '/shell'
+ fullPath: '/shell'
+ preLoaderRoute: typeof ShellRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/shell/': {
+ id: '/shell/'
+ path: '/'
+ fullPath: '/shell/'
+ preLoaderRoute: typeof ShellIndexRouteImport
+ parentRoute: typeof ShellRoute
+ }
+ '/page/$id': {
+ id: '/page/$id'
+ path: '/page/$id'
+ fullPath: '/page/$id'
+ preLoaderRoute: typeof PageIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+interface ShellRouteChildren {
+ ShellIndexRoute: typeof ShellIndexRoute
+}
+
+const ShellRouteChildren: ShellRouteChildren = {
+ ShellIndexRoute: ShellIndexRoute,
+}
+
+const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ ShellRoute: ShellRouteWithChildren,
+ PageIdRoute: PageIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/router.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/router.tsx
new file mode 100644
index 0000000000..06afdb10d3
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/router.tsx
@@ -0,0 +1,19 @@
+import { createMemoryHistory, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/shell'],
+ }),
+ routeTree,
+ defaultGcTime: 0,
+ defaultPreloadGcTime: 0,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..cb8d5a688d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/page.$id.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/page.$id.tsx
new file mode 100644
index 0000000000..fb6c438fa5
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/page.$id.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { createLoaderData } from '../../../loader-data'
+
+export const Route = createFileRoute('/page/$id')({
+ loader: ({ params }) => createLoaderData(params.id),
+ component: PageComponent,
+})
+
+function PageComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data().id}:${data().records.length}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/shell.index.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/shell.index.tsx
new file mode 100644
index 0000000000..2f4f5daee2
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/shell.index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/shell/')({
+ component: ShellIndexComponent,
+})
+
+function ShellIndexComponent() {
+ return ready
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/shell.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/shell.tsx
new file mode 100644
index 0000000000..c1e38a1aaf
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/shell.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/shell')({
+ component: ShellComponent,
+})
+
+function ShellComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/tsconfig.json b/benchmarks/memory/client/scenarios/loader-data-retention/solid/tsconfig.json
new file mode 100644
index 0000000000..b12dcb7ade
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/solid/vite.config.ts b/benchmarks/memory/client/scenarios/loader-data-retention/solid/vite.config.ts
new file mode 100644
index 0000000000..58d6b77acc
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client loader-data-retention (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/memory.bench.ts b/benchmarks/memory/client/scenarios/loader-data-retention/vue/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/memory.flame.ts b/benchmarks/memory/client/scenarios/loader-data-retention/vue/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/project.json b/benchmarks/memory/client/scenarios/loader-data-retention/vue/project.json
new file mode 100644
index 0000000000..22504c906f
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-loader-data-retention-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/setup.ts b/benchmarks/memory/client/scenarios/loader-data-retention/vue/setup.ts
new file mode 100644
index 0000000000..a83a1f70d1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { loaderPayloadRecordCount, mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'vue',
+ mountTestApp,
+ loaderPayloadRecordCount,
+)
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/app.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/app.tsx
new file mode 100644
index 0000000000..c843b4feb5
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/app.tsx
@@ -0,0 +1,38 @@
+import { RouterProvider } from '@tanstack/vue-router'
+import { createApp } from 'vue'
+import { getRouter } from './router'
+import type {} from '@tanstack/router-core'
+
+export { loaderPayloadRecordCount } from '../../loader-data'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const vueApp = createApp({
+ setup() {
+ return () =>
+ },
+ })
+ let didUnmount = false
+
+ vueApp.mount(container)
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ vueApp.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..5b09d365e7
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routeTree.gen.ts
@@ -0,0 +1,102 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ShellRouteImport } from './routes/shell'
+import { Route as PageIdRouteImport } from './routes/page.$id'
+import { Route as ShellIndexRouteImport } from './routes/shell.index'
+
+const ShellRoute = ShellRouteImport.update({
+ id: '/shell',
+ path: '/shell',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PageIdRoute = PageIdRouteImport.update({
+ id: '/page/$id',
+ path: '/page/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ShellIndexRoute = ShellIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => ShellRoute,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/shell': typeof ShellRouteWithChildren
+ '/page/$id': typeof PageIdRoute
+ '/shell/': typeof ShellIndexRoute
+}
+export interface FileRoutesByTo {
+ '/page/$id': typeof PageIdRoute
+ '/shell': typeof ShellIndexRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/shell': typeof ShellRouteWithChildren
+ '/page/$id': typeof PageIdRoute
+ '/shell/': typeof ShellIndexRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/shell' | '/page/$id' | '/shell/'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/page/$id' | '/shell'
+ id: '__root__' | '/shell' | '/page/$id' | '/shell/'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ShellRoute: typeof ShellRouteWithChildren
+ PageIdRoute: typeof PageIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/shell': {
+ id: '/shell'
+ path: '/shell'
+ fullPath: '/shell'
+ preLoaderRoute: typeof ShellRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/page/$id': {
+ id: '/page/$id'
+ path: '/page/$id'
+ fullPath: '/page/$id'
+ preLoaderRoute: typeof PageIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/shell/': {
+ id: '/shell/'
+ path: '/'
+ fullPath: '/shell/'
+ preLoaderRoute: typeof ShellIndexRouteImport
+ parentRoute: typeof ShellRoute
+ }
+ }
+}
+
+interface ShellRouteChildren {
+ ShellIndexRoute: typeof ShellIndexRoute
+}
+
+const ShellRouteChildren: ShellRouteChildren = {
+ ShellIndexRoute: ShellIndexRoute,
+}
+
+const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ ShellRoute: ShellRouteWithChildren,
+ PageIdRoute: PageIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/router.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/router.tsx
new file mode 100644
index 0000000000..84286e1dae
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/router.tsx
@@ -0,0 +1,19 @@
+import { createMemoryHistory, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/shell'],
+ }),
+ routeTree,
+ defaultGcTime: 0,
+ defaultPreloadGcTime: 0,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..91296e6f84
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/page.$id.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/page.$id.tsx
new file mode 100644
index 0000000000..9f4b3c4470
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/page.$id.tsx
@@ -0,0 +1,22 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { createLoaderData } from '../../../loader-data'
+
+export const Route = createFileRoute('/page/$id')({
+ loader: ({ params }: { params: { id: string } }) =>
+ createLoaderData(params.id),
+ component: PageComponent,
+})
+
+function PageComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data.value.id}:${data.value.records.length}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/shell.index.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/shell.index.tsx
new file mode 100644
index 0000000000..640ffeeb5c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/shell.index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/shell/')({
+ component: ShellIndexComponent,
+})
+
+function ShellIndexComponent() {
+ return ready
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/shell.tsx b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/shell.tsx
new file mode 100644
index 0000000000..f8a598bc34
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/shell.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/shell')({
+ component: ShellComponent,
+})
+
+function ShellComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/tsconfig.json b/benchmarks/memory/client/scenarios/loader-data-retention/vue/tsconfig.json
new file mode 100644
index 0000000000..9a5872a4c0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/vue/vite.config.ts b/benchmarks/memory/client/scenarios/loader-data-retention/vue/vite.config.ts
new file mode 100644
index 0000000000..bbf2fd02d8
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/loader-data-retention/vue/vite.config.ts
@@ -0,0 +1,36 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client loader-data-retention (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/memory.bench.ts b/benchmarks/memory/client/scenarios/mount-unmount/react/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/memory.flame.ts b/benchmarks/memory/client/scenarios/mount-unmount/react/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/project.json b/benchmarks/memory/client/scenarios/mount-unmount/react/project.json
new file mode 100644
index 0000000000..05d8aaa4bf
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-mount-unmount-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/setup.ts b/benchmarks/memory/client/scenarios/mount-unmount/react/setup.ts
new file mode 100644
index 0000000000..70603b5c34
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'react',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/src/app.tsx b/benchmarks/memory/client/scenarios/mount-unmount/react/src/app.tsx
new file mode 100644
index 0000000000..bdf76eaa24
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/src/app.tsx
@@ -0,0 +1,29 @@
+import { RouterProvider } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { getRouter } from './router'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const reactRoot = createRoot(container)
+ let didUnmount = false
+
+ reactRoot.render()
+
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ reactRoot.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/mount-unmount/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..5efcb2cf72
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/src/routeTree.gen.ts
@@ -0,0 +1,59 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ARouteImport } from './routes/a'
+
+const ARoute = ARouteImport.update({
+ id: '/a',
+ path: '/a',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/a': typeof ARoute
+}
+export interface FileRoutesByTo {
+ '/a': typeof ARoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/a': typeof ARoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/a'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/a'
+ id: '__root__' | '/a'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ARoute: typeof ARoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/a': {
+ id: '/a'
+ path: '/a'
+ fullPath: '/a'
+ preLoaderRoute: typeof ARouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ARoute: ARoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/src/router.tsx b/benchmarks/memory/client/scenarios/mount-unmount/react/src/router.tsx
new file mode 100644
index 0000000000..9cb85c5a1b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/a'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/mount-unmount/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..889395056b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/src/routes/a.tsx b/benchmarks/memory/client/scenarios/mount-unmount/react/src/routes/a.tsx
new file mode 100644
index 0000000000..1e268d3c42
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/src/routes/a.tsx
@@ -0,0 +1,12 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/a')({
+ loader: () => ({ id: 'a' }),
+ component: AComponent,
+})
+
+function AComponent() {
+ const data = Route.useLoaderData()
+
+ return {data.id}
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/tsconfig.json b/benchmarks/memory/client/scenarios/mount-unmount/react/tsconfig.json
new file mode 100644
index 0000000000..ea566061d9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/react/vite.config.ts b/benchmarks/memory/client/scenarios/mount-unmount/react/vite.config.ts
new file mode 100644
index 0000000000..bb0f3c2e0a
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/react/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client mount-unmount (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/shared.ts b/benchmarks/memory/client/scenarios/mount-unmount/shared.ts
new file mode 100644
index 0000000000..4f9b9f430f
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/shared.ts
@@ -0,0 +1,74 @@
+import {
+ createBenchContainer,
+ drainMicrotasks,
+ noop,
+ removeBenchContainer,
+ warnClientMemoryDevMode,
+} from '#memory-client/lifecycle'
+import type { Framework, MountTestApp } from '#memory-client/lifecycle'
+
+type RenderRouter = {
+ load: () => Promise
+ subscribe: (event: 'onRendered', listener: () => void) => () => void
+}
+
+const mountUnmountIterations = 100
+
+function assertEmptyBody() {
+ if (document.body.childNodes.length !== 0) {
+ throw new Error(
+ `Expected document.body to be empty, found ${document.body.childNodes.length} child node(s)`,
+ )
+ }
+}
+
+export function createWorkload(
+ framework: Framework,
+ mountTestApp: MountTestApp,
+) {
+ warnClientMemoryDevMode(framework)
+
+ async function cycle() {
+ const container = createBenchContainer()
+
+ let unmount = noop
+ let unsubscribe = noop
+
+ try {
+ const mounted = mountTestApp(container)
+ const router = mounted.router as RenderRouter
+ unmount = mounted.unmount
+
+ const rendered = new Promise((resolve) => {
+ unsubscribe = router.subscribe('onRendered', () => {
+ resolve()
+ })
+ })
+
+ await router.load()
+ await rendered
+ unsubscribe()
+ unsubscribe = noop
+ } finally {
+ unmount()
+ removeBenchContainer(container)
+ unsubscribe()
+ await drainMicrotasks()
+ }
+ }
+
+ return {
+ name: `mem mount-unmount (${framework})`,
+ cycle,
+ async run() {
+ for (let index = 0; index < mountUnmountIterations; index++) {
+ await cycle()
+ }
+ },
+ async sanity() {
+ assertEmptyBody()
+ await cycle()
+ assertEmptyBody()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/memory.bench.ts b/benchmarks/memory/client/scenarios/mount-unmount/solid/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/memory.flame.ts b/benchmarks/memory/client/scenarios/mount-unmount/solid/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/project.json b/benchmarks/memory/client/scenarios/mount-unmount/solid/project.json
new file mode 100644
index 0000000000..63e8e950db
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-mount-unmount-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/setup.ts b/benchmarks/memory/client/scenarios/mount-unmount/solid/setup.ts
new file mode 100644
index 0000000000..493425c6a2
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'solid',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/src/app.tsx b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/app.tsx
new file mode 100644
index 0000000000..b23bc126d5
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/app.tsx
@@ -0,0 +1,27 @@
+import { RouterProvider } from '@tanstack/solid-router'
+import { render } from 'solid-js/web'
+import { getRouter } from './router'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const dispose = render(() => , container)
+ let didUnmount = false
+
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ dispose()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..a3f9739290
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routeTree.gen.ts
@@ -0,0 +1,59 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ARouteImport } from './routes/a'
+
+const ARoute = ARouteImport.update({
+ id: '/a',
+ path: '/a',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/a': typeof ARoute
+}
+export interface FileRoutesByTo {
+ '/a': typeof ARoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/a': typeof ARoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/a'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/a'
+ id: '__root__' | '/a'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ARoute: typeof ARoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/a': {
+ id: '/a'
+ path: '/a'
+ fullPath: '/a'
+ preLoaderRoute: typeof ARouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ARoute: ARoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/src/router.tsx b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/router.tsx
new file mode 100644
index 0000000000..e8c14f12f6
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/a'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..cb8d5a688d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routes/a.tsx b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routes/a.tsx
new file mode 100644
index 0000000000..36c4be538c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/src/routes/a.tsx
@@ -0,0 +1,12 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/a')({
+ loader: () => ({ id: 'a' }),
+ component: AComponent,
+})
+
+function AComponent() {
+ const data = Route.useLoaderData()
+
+ return {data().id}
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/tsconfig.json b/benchmarks/memory/client/scenarios/mount-unmount/solid/tsconfig.json
new file mode 100644
index 0000000000..b12dcb7ade
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/solid/vite.config.ts b/benchmarks/memory/client/scenarios/mount-unmount/solid/vite.config.ts
new file mode 100644
index 0000000000..5f0ab26252
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client mount-unmount (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/memory.bench.ts b/benchmarks/memory/client/scenarios/mount-unmount/vue/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/memory.flame.ts b/benchmarks/memory/client/scenarios/mount-unmount/vue/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/project.json b/benchmarks/memory/client/scenarios/mount-unmount/vue/project.json
new file mode 100644
index 0000000000..157673cf16
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-mount-unmount-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/setup.ts b/benchmarks/memory/client/scenarios/mount-unmount/vue/setup.ts
new file mode 100644
index 0000000000..a38df04788
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'vue',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/src/app.tsx b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/app.tsx
new file mode 100644
index 0000000000..27b82fcbe1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/app.tsx
@@ -0,0 +1,34 @@
+import { RouterProvider } from '@tanstack/vue-router'
+import { createApp } from 'vue'
+import { getRouter } from './router'
+import type {} from '@tanstack/router-core'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const vueApp = createApp({
+ setup() {
+ return () =>
+ },
+ })
+ let didUnmount = false
+
+ vueApp.mount(container)
+
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ vueApp.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..1a54b81033
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routeTree.gen.ts
@@ -0,0 +1,59 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ARouteImport } from './routes/a'
+
+const ARoute = ARouteImport.update({
+ id: '/a',
+ path: '/a',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/a': typeof ARoute
+}
+export interface FileRoutesByTo {
+ '/a': typeof ARoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/a': typeof ARoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/a'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/a'
+ id: '__root__' | '/a'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ARoute: typeof ARoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/a': {
+ id: '/a'
+ path: '/a'
+ fullPath: '/a'
+ preLoaderRoute: typeof ARouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ARoute: ARoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/src/router.tsx b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/router.tsx
new file mode 100644
index 0000000000..e083495cd2
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/a'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..91296e6f84
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routes/a.tsx b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routes/a.tsx
new file mode 100644
index 0000000000..cbcd543ce5
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/src/routes/a.tsx
@@ -0,0 +1,12 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/a')({
+ loader: () => ({ id: 'a' }),
+ component: AComponent,
+})
+
+function AComponent() {
+ const data = Route.useLoaderData()
+
+ return {data.value.id}
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/tsconfig.json b/benchmarks/memory/client/scenarios/mount-unmount/vue/tsconfig.json
new file mode 100644
index 0000000000..9a5872a4c0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/mount-unmount/vue/vite.config.ts b/benchmarks/memory/client/scenarios/mount-unmount/vue/vite.config.ts
new file mode 100644
index 0000000000..0f7573a408
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/mount-unmount/vue/vite.config.ts
@@ -0,0 +1,36 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client mount-unmount (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/memory.bench.ts b/benchmarks/memory/client/scenarios/navigation-churn/react/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/memory.flame.ts b/benchmarks/memory/client/scenarios/navigation-churn/react/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/project.json b/benchmarks/memory/client/scenarios/navigation-churn/react/project.json
new file mode 100644
index 0000000000..b424277bc6
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-navigation-churn-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/setup.ts b/benchmarks/memory/client/scenarios/navigation-churn/react/setup.ts
new file mode 100644
index 0000000000..70603b5c34
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'react',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/src/app.tsx b/benchmarks/memory/client/scenarios/navigation-churn/react/src/app.tsx
new file mode 100644
index 0000000000..d714e7bfdd
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/src/app.tsx
@@ -0,0 +1,31 @@
+import { RouterProvider } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { getRouter } from './router'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const reactRoot = createRoot(container)
+ let didUnmount = false
+
+ reactRoot.render()
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ reactRoot.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/navigation-churn/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..3cca81cabb
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/src/routeTree.gen.ts
@@ -0,0 +1,77 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as BRouteImport } from './routes/b'
+import { Route as ARouteImport } from './routes/a'
+
+const BRoute = BRouteImport.update({
+ id: '/b',
+ path: '/b',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ARoute = ARouteImport.update({
+ id: '/a',
+ path: '/a',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRoutesByTo {
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/a' | '/b'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/a' | '/b'
+ id: '__root__' | '/a' | '/b'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ARoute: typeof ARoute
+ BRoute: typeof BRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/b': {
+ id: '/b'
+ path: '/b'
+ fullPath: '/b'
+ preLoaderRoute: typeof BRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/a': {
+ id: '/a'
+ path: '/a'
+ fullPath: '/a'
+ preLoaderRoute: typeof ARouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ARoute: ARoute,
+ BRoute: BRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/src/router.tsx b/benchmarks/memory/client/scenarios/navigation-churn/react/src/router.tsx
new file mode 100644
index 0000000000..9cb85c5a1b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/a'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..889395056b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/a.tsx b/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/a.tsx
new file mode 100644
index 0000000000..bdd32d0b81
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/a.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+const fixedTimestamp = 1_700_000_000_000
+
+export const Route = createFileRoute('/a')({
+ loader: () => ({ name: 'a', ts: fixedTimestamp }),
+ component: AComponent,
+})
+
+function AComponent() {
+ const data = Route.useLoaderData()
+
+ return {`${data.name}:${data.ts}`}
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/b.tsx b/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/b.tsx
new file mode 100644
index 0000000000..05deef729d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/b.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+const fixedTimestamp = 1_700_000_000_000
+
+export const Route = createFileRoute('/b')({
+ loader: () => ({ name: 'b', ts: fixedTimestamp }),
+ component: BComponent,
+})
+
+function BComponent() {
+ const data = Route.useLoaderData()
+
+ return {`${data.name}:${data.ts}`}
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/tsconfig.json b/benchmarks/memory/client/scenarios/navigation-churn/react/tsconfig.json
new file mode 100644
index 0000000000..ea566061d9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/react/vite.config.ts b/benchmarks/memory/client/scenarios/navigation-churn/react/vite.config.ts
new file mode 100644
index 0000000000..b05b999297
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/react/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client navigation-churn (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/shared.ts b/benchmarks/memory/client/scenarios/navigation-churn/shared.ts
new file mode 100644
index 0000000000..0745de2116
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/shared.ts
@@ -0,0 +1,129 @@
+import {
+ createBenchContainer,
+ nextAnimationFrame,
+ noop,
+ removeBenchContainer,
+ warnClientMemoryDevMode,
+} from '#memory-client/lifecycle'
+import type { Framework, MountTestApp } from '#memory-client/lifecycle'
+
+type Target = '/a' | '/b'
+
+type NavigationRouter = {
+ load: () => Promise
+ navigate: (options: { to: Target; replace: true }) => Promise
+ subscribe: (event: 'onRendered', listener: () => void) => () => void
+}
+
+const navigationChurnIterations = 300
+
+const uninitialized = () =>
+ Promise.reject(new Error('navigation-churn benchmark is not initialized'))
+
+export function createWorkload(
+ framework: Framework,
+ mountTestApp: MountTestApp,
+) {
+ warnClientMemoryDevMode(framework)
+
+ let container: HTMLDivElement | undefined = undefined
+ let unmount = noop
+ let unsub = noop
+ let resolveRendered: () => void = noop
+ let navigateTo: (target: Target) => Promise = uninitialized
+
+ function assertRenderedPage(target: Target) {
+ const expected = target.slice(1)
+ const actual =
+ container?.querySelector('[data-bench-page]')?.dataset
+ .benchPage
+
+ if (actual !== expected) {
+ throw new Error(`Expected rendered page ${expected}, got ${actual}`)
+ }
+ }
+
+ async function waitForRenderedPage(target: Target) {
+ for (let attempt = 0; attempt < 10; attempt++) {
+ try {
+ assertRenderedPage(target)
+ return
+ } catch {
+ await nextAnimationFrame()
+ }
+ }
+
+ assertRenderedPage(target)
+ }
+
+ function waitForNextRender() {
+ return new Promise((resolve) => {
+ resolveRendered = resolve
+ })
+ }
+
+ async function before() {
+ if (container) {
+ after()
+ }
+
+ container = createBenchContainer()
+
+ const mounted = mountTestApp(container)
+ const router = mounted.router as NavigationRouter
+ unmount = mounted.unmount
+
+ unsub = router.subscribe('onRendered', () => {
+ resolveRendered()
+ })
+
+ navigateTo = async (target) => {
+ const rendered = waitForNextRender()
+ await Promise.all([
+ router.navigate({
+ to: target,
+ replace: true,
+ }),
+ rendered,
+ ])
+ }
+
+ await router.load()
+ await waitForRenderedPage('/a')
+ }
+
+ function after() {
+ unmount()
+ removeBenchContainer(container)
+ unsub()
+
+ container = undefined
+ unmount = noop
+ unsub = noop
+ resolveRendered = noop
+ navigateTo = uninitialized
+ }
+
+ return {
+ name: `mem navigation-churn (${framework})`,
+ before,
+ navigate: (target: Target) => navigateTo(target),
+ async run() {
+ for (let index = 0; index < navigationChurnIterations; index++) {
+ await navigateTo(index % 2 === 0 ? '/b' : '/a')
+ }
+ },
+ async sanity() {
+ await before()
+
+ try {
+ assertRenderedPage('/a')
+ await navigateTo('/b')
+ assertRenderedPage('/b')
+ } finally {
+ after()
+ }
+ },
+ after,
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/memory.bench.ts b/benchmarks/memory/client/scenarios/navigation-churn/solid/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/memory.flame.ts b/benchmarks/memory/client/scenarios/navigation-churn/solid/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/project.json b/benchmarks/memory/client/scenarios/navigation-churn/solid/project.json
new file mode 100644
index 0000000000..2c581e6188
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-navigation-churn-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/setup.ts b/benchmarks/memory/client/scenarios/navigation-churn/solid/setup.ts
new file mode 100644
index 0000000000..493425c6a2
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'solid',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/src/app.tsx b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/app.tsx
new file mode 100644
index 0000000000..16000c90a1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/app.tsx
@@ -0,0 +1,29 @@
+import { RouterProvider } from '@tanstack/solid-router'
+import { render } from 'solid-js/web'
+import { getRouter } from './router'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const dispose = render(() => , container)
+ let didUnmount = false
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ dispose()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..59d7fad058
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routeTree.gen.ts
@@ -0,0 +1,77 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as BRouteImport } from './routes/b'
+import { Route as ARouteImport } from './routes/a'
+
+const BRoute = BRouteImport.update({
+ id: '/b',
+ path: '/b',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ARoute = ARouteImport.update({
+ id: '/a',
+ path: '/a',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRoutesByTo {
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/a' | '/b'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/a' | '/b'
+ id: '__root__' | '/a' | '/b'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ARoute: typeof ARoute
+ BRoute: typeof BRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/b': {
+ id: '/b'
+ path: '/b'
+ fullPath: '/b'
+ preLoaderRoute: typeof BRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/a': {
+ id: '/a'
+ path: '/a'
+ fullPath: '/a'
+ preLoaderRoute: typeof ARouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ARoute: ARoute,
+ BRoute: BRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/src/router.tsx b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/router.tsx
new file mode 100644
index 0000000000..e8c14f12f6
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/a'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..cb8d5a688d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/a.tsx b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/a.tsx
new file mode 100644
index 0000000000..0b7838fdbe
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/a.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+const fixedTimestamp = 1_700_000_000_000
+
+export const Route = createFileRoute('/a')({
+ loader: () => ({ name: 'a', ts: fixedTimestamp }),
+ component: AComponent,
+})
+
+function AComponent() {
+ const data = Route.useLoaderData()
+
+ return {`${data().name}:${data().ts}`}
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/b.tsx b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/b.tsx
new file mode 100644
index 0000000000..ec9b513ba9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/b.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+const fixedTimestamp = 1_700_000_000_000
+
+export const Route = createFileRoute('/b')({
+ loader: () => ({ name: 'b', ts: fixedTimestamp }),
+ component: BComponent,
+})
+
+function BComponent() {
+ const data = Route.useLoaderData()
+
+ return {`${data().name}:${data().ts}`}
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/tsconfig.json b/benchmarks/memory/client/scenarios/navigation-churn/solid/tsconfig.json
new file mode 100644
index 0000000000..b12dcb7ade
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/solid/vite.config.ts b/benchmarks/memory/client/scenarios/navigation-churn/solid/vite.config.ts
new file mode 100644
index 0000000000..6b6735891a
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client navigation-churn (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/memory.bench.ts b/benchmarks/memory/client/scenarios/navigation-churn/vue/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/memory.flame.ts b/benchmarks/memory/client/scenarios/navigation-churn/vue/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/project.json b/benchmarks/memory/client/scenarios/navigation-churn/vue/project.json
new file mode 100644
index 0000000000..f96d03d7d3
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-navigation-churn-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/setup.ts b/benchmarks/memory/client/scenarios/navigation-churn/vue/setup.ts
new file mode 100644
index 0000000000..a38df04788
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'vue',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/src/app.tsx b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/app.tsx
new file mode 100644
index 0000000000..fad2251cc0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/app.tsx
@@ -0,0 +1,36 @@
+import { RouterProvider } from '@tanstack/vue-router'
+import { createApp } from 'vue'
+import { getRouter } from './router'
+import type {} from '@tanstack/router-core'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const vueApp = createApp({
+ setup() {
+ return () =>
+ },
+ })
+ let didUnmount = false
+
+ vueApp.mount(container)
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ vueApp.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..12c9640358
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routeTree.gen.ts
@@ -0,0 +1,77 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as BRouteImport } from './routes/b'
+import { Route as ARouteImport } from './routes/a'
+
+const BRoute = BRouteImport.update({
+ id: '/b',
+ path: '/b',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ARoute = ARouteImport.update({
+ id: '/a',
+ path: '/a',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRoutesByTo {
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/a': typeof ARoute
+ '/b': typeof BRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/a' | '/b'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/a' | '/b'
+ id: '__root__' | '/a' | '/b'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ARoute: typeof ARoute
+ BRoute: typeof BRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/b': {
+ id: '/b'
+ path: '/b'
+ fullPath: '/b'
+ preLoaderRoute: typeof BRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/a': {
+ id: '/a'
+ path: '/a'
+ fullPath: '/a'
+ preLoaderRoute: typeof ARouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ARoute: ARoute,
+ BRoute: BRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/src/router.tsx b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/router.tsx
new file mode 100644
index 0000000000..e083495cd2
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/a'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..91296e6f84
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/a.tsx b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/a.tsx
new file mode 100644
index 0000000000..183228e748
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/a.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+const fixedTimestamp = 1_700_000_000_000
+
+export const Route = createFileRoute('/a')({
+ loader: () => ({ name: 'a', ts: fixedTimestamp }),
+ component: AComponent,
+})
+
+function AComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+ {`${data.value.name}:${data.value.ts}`}
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/b.tsx b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/b.tsx
new file mode 100644
index 0000000000..3a18ec97a9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/b.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+const fixedTimestamp = 1_700_000_000_000
+
+export const Route = createFileRoute('/b')({
+ loader: () => ({ name: 'b', ts: fixedTimestamp }),
+ component: BComponent,
+})
+
+function BComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+ {`${data.value.name}:${data.value.ts}`}
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/tsconfig.json b/benchmarks/memory/client/scenarios/navigation-churn/vue/tsconfig.json
new file mode 100644
index 0000000000..9a5872a4c0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/navigation-churn/vue/vite.config.ts b/benchmarks/memory/client/scenarios/navigation-churn/vue/vite.config.ts
new file mode 100644
index 0000000000..d26ce23d07
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/navigation-churn/vue/vite.config.ts
@@ -0,0 +1,36 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client navigation-churn (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/preload-churn/item-payload.ts b/benchmarks/memory/client/scenarios/preload-churn/item-payload.ts
new file mode 100644
index 0000000000..4a86ea7244
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/item-payload.ts
@@ -0,0 +1,51 @@
+const payloadByteLength = 20 * 1024
+const payloadChunkCount = 32
+const payloadChunkSize = payloadByteLength / payloadChunkCount
+const trackedLoaderIdPrefix = 'sanity-'
+const trackedItemLoaderCalls = new Map()
+
+export function createItemPayload(id: string) {
+ let seed = hashId(id)
+ const chunks = new Array(payloadChunkCount)
+
+ for (let index = 0; index < payloadChunkCount; index++) {
+ seed = (seed * 1664525 + 1013904223) >>> 0
+ chunks[index] = createPayloadChunk(id, index, seed)
+ }
+
+ return {
+ id,
+ chunks,
+ byteLength: payloadByteLength,
+ }
+}
+
+export function trackItemLoaderCall(id: string) {
+ if (!id.startsWith(trackedLoaderIdPrefix)) {
+ return
+ }
+
+ trackedItemLoaderCalls.set(id, (trackedItemLoaderCalls.get(id) ?? 0) + 1)
+}
+
+export function getTrackedItemLoaderCount(id: string) {
+ return trackedItemLoaderCalls.get(id) ?? 0
+}
+
+function createPayloadChunk(id: string, index: number, seed: number) {
+ const token = `${id}:${index.toString(36)}:${seed.toString(36)}:`
+ const repeatCount = Math.ceil(payloadChunkSize / token.length)
+
+ return token.repeat(repeatCount).slice(0, payloadChunkSize)
+}
+
+function hashId(id: string) {
+ let hash = 2166136261
+
+ for (let index = 0; index < id.length; index++) {
+ hash ^= id.charCodeAt(index)
+ hash = Math.imul(hash, 16777619)
+ }
+
+ return hash >>> 0
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/memory.bench.ts b/benchmarks/memory/client/scenarios/preload-churn/react/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/memory.flame.ts b/benchmarks/memory/client/scenarios/preload-churn/react/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/project.json b/benchmarks/memory/client/scenarios/preload-churn/react/project.json
new file mode 100644
index 0000000000..238d98c816
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-preload-churn-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/setup.ts b/benchmarks/memory/client/scenarios/preload-churn/react/setup.ts
new file mode 100644
index 0000000000..31cd97d135
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { getTrackedItemLoaderCount, mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'react',
+ mountTestApp,
+ getTrackedItemLoaderCount,
+)
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/src/app.tsx b/benchmarks/memory/client/scenarios/preload-churn/react/src/app.tsx
new file mode 100644
index 0000000000..b75d1fe34d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/src/app.tsx
@@ -0,0 +1,33 @@
+import { RouterProvider } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { getRouter } from './router'
+
+export { getTrackedItemLoaderCount } from '../../item-payload'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const reactRoot = createRoot(container)
+ let didUnmount = false
+
+ reactRoot.render()
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ reactRoot.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/preload-churn/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..84aaebb9c9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/src/routeTree.gen.ts
@@ -0,0 +1,77 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/items/$id'
+ id: '__root__' | '/' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/src/router.tsx b/benchmarks/memory/client/scenarios/preload-churn/react/src/router.tsx
new file mode 100644
index 0000000000..69e3180173
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/src/router.tsx
@@ -0,0 +1,18 @@
+import { createMemoryHistory, createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/'],
+ }),
+ routeTree,
+ defaultPreloadGcTime: 0,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..889395056b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/index.tsx b/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/index.tsx
new file mode 100644
index 0000000000..af735d705b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return index
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/items.$id.tsx b/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..2f52acd528
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/src/routes/items.$id.tsx
@@ -0,0 +1,20 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { createItemPayload, trackItemLoaderCall } from '../../../item-payload'
+
+export const Route = createFileRoute('/items/$id')({
+ loader: ({ params }) => {
+ trackItemLoaderCall(params.id)
+ return createItemPayload(params.id)
+ },
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data.id}:${data.byteLength}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/tsconfig.json b/benchmarks/memory/client/scenarios/preload-churn/react/tsconfig.json
new file mode 100644
index 0000000000..ea566061d9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/react/vite.config.ts b/benchmarks/memory/client/scenarios/preload-churn/react/vite.config.ts
new file mode 100644
index 0000000000..f9c426656b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/react/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client preload-churn (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/preload-churn/shared.ts b/benchmarks/memory/client/scenarios/preload-churn/shared.ts
new file mode 100644
index 0000000000..78e35c42ad
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/shared.ts
@@ -0,0 +1,220 @@
+import {
+ createDeterministicRandom,
+ randomSegment,
+} from '#memory-client/bench-utils'
+import {
+ createBenchContainer,
+ drainMicrotasks,
+ nextAnimationFrame,
+ noop,
+ removeBenchContainer,
+ warnClientMemoryDevMode,
+} from '#memory-client/lifecycle'
+import type { Framework, MountTestApp } from '#memory-client/lifecycle'
+
+type GetTrackedItemLoaderCount = (id: string) => number
+
+type PreloadRouter = {
+ load: () => Promise
+ preloadRoute: (options: {
+ to: '/items/$id'
+ params: { id: string }
+ }) => Promise
+ navigate: (
+ options:
+ | {
+ to: '/items/$id'
+ params: { id: string }
+ replace: true
+ }
+ | {
+ to: '/'
+ replace: true
+ },
+ ) => Promise
+ subscribe: (event: 'onRendered', listener: () => void) => () => void
+}
+
+// Fixed id for the eviction navigations interleaved into the bench loop; its
+// payload is a constant-size steady-state resident, never part of the signal.
+const evictionItemId = 'nav-evict'
+const preloadChurnIterations = 200
+// A navigation commit is what triggers the router's clearExpiredCache --
+// preloaded matches (defaultPreloadGcTime: 0) are only evicted then, never
+// during a preload-only loop. Interleaving a navigation every few preloads is
+// what makes the flat floor assert "eviction releases preloaded payloads".
+const preloadsPerEvictionNavigation = 10
+// Module-level so ids stay unique across runner invocations on one mount; a
+// per-invocation LCG would replay identical ids, and every preload after the
+// first invocation would dedupe against cachedMatches instead of doing work.
+const benchmarkRandom = createDeterministicRandom(0x706c6f61)
+let preloadCounter = 0
+
+const uninitialized = async (_id: string) => {
+ throw new Error('preload-churn benchmark is not initialized')
+}
+
+export function createWorkload(
+ framework: Framework,
+ mountTestApp: MountTestApp,
+ getTrackedItemLoaderCount: GetTrackedItemLoaderCount,
+) {
+ warnClientMemoryDevMode(framework)
+
+ let container: HTMLDivElement | undefined = undefined
+ let router: PreloadRouter | undefined = undefined
+ let unmount = noop
+ let unsub = noop
+ let resolveRendered = noop
+ let evictionParity = 0
+ let preloadItem: (id: string) => Promise = uninitialized
+ let navigateToItem: (id: string) => Promise = uninitialized
+ let navigateToIndex: () => Promise = () =>
+ uninitialized('navigate-to-index')
+
+ function assertRenderedIndex() {
+ const actual =
+ container?.querySelector('[data-bench-page]')?.dataset
+ .benchPage
+
+ if (actual !== 'index') {
+ throw new Error(`Expected rendered index page, got ${actual}`)
+ }
+ }
+
+ async function waitForRenderedIndex() {
+ for (let attempt = 0; attempt < 10; attempt++) {
+ try {
+ assertRenderedIndex()
+ return
+ } catch {
+ await nextAnimationFrame()
+ }
+ }
+
+ assertRenderedIndex()
+ }
+
+ function waitForNextRender() {
+ return new Promise((resolve) => {
+ resolveRendered = resolve
+ })
+ }
+
+ async function before() {
+ if (container) {
+ after()
+ }
+
+ container = createBenchContainer()
+
+ const mounted = mountTestApp(container)
+ router = mounted.router as PreloadRouter
+ unmount = mounted.unmount
+
+ unsub = router.subscribe('onRendered', () => {
+ resolveRendered()
+ })
+
+ preloadItem = async (id) => {
+ await router!.preloadRoute({
+ to: '/items/$id',
+ params: { id },
+ })
+ await drainMicrotasks()
+ }
+
+ navigateToItem = async (id) => {
+ const rendered = waitForNextRender()
+ await Promise.all([
+ router!.navigate({
+ to: '/items/$id',
+ params: { id },
+ replace: true,
+ }),
+ rendered,
+ ])
+ }
+
+ navigateToIndex = async () => {
+ const rendered = waitForNextRender()
+ await Promise.all([
+ router!.navigate({
+ to: '/',
+ replace: true,
+ }),
+ rendered,
+ ])
+ }
+
+ await router.load()
+ await waitForRenderedIndex()
+ }
+
+ function after() {
+ unmount()
+ removeBenchContainer(container)
+ unsub()
+
+ container = undefined
+ router = undefined
+ unmount = noop
+ unsub = noop
+ resolveRendered = noop
+ evictionParity = 0
+ preloadItem = uninitialized
+ navigateToItem = uninitialized
+ navigateToIndex = () => uninitialized('navigate-to-index')
+ }
+
+ // Alternate between two distinct locations so every eviction navigation
+ // changes the href (a same-href navigate would never commit or render).
+ async function evictPreloads() {
+ evictionParity = (evictionParity + 1) % 2
+
+ if (evictionParity === 1) {
+ await navigateToItem(evictionItemId)
+ } else {
+ await navigateToIndex()
+ }
+ }
+
+ return {
+ name: `mem preload-churn (${framework})`,
+ before,
+ preload: (id: string) => preloadItem(id),
+ evictPreloads,
+ async run() {
+ for (let index = 0; index < preloadChurnIterations; index++) {
+ await preloadItem(
+ `${(preloadCounter++).toString(36)}-${randomSegment(benchmarkRandom)}`,
+ )
+
+ if ((index + 1) % preloadsPerEvictionNavigation === 0) {
+ await evictPreloads()
+ }
+ }
+ },
+ async sanity() {
+ await before()
+
+ try {
+ assertRenderedIndex()
+
+ const id = 'sanity-preloaded-item'
+ const initialLoaderCount = getTrackedItemLoaderCount(id)
+ await preloadItem(id)
+
+ const preloadedLoaderCount = getTrackedItemLoaderCount(id)
+ if (preloadedLoaderCount !== initialLoaderCount + 1) {
+ throw new Error(
+ `Expected preload to run item loader once, got ${preloadedLoaderCount - initialLoaderCount}`,
+ )
+ }
+ } finally {
+ after()
+ }
+ },
+ after,
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/memory.bench.ts b/benchmarks/memory/client/scenarios/preload-churn/solid/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/memory.flame.ts b/benchmarks/memory/client/scenarios/preload-churn/solid/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/project.json b/benchmarks/memory/client/scenarios/preload-churn/solid/project.json
new file mode 100644
index 0000000000..16ef8af968
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-preload-churn-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/setup.ts b/benchmarks/memory/client/scenarios/preload-churn/solid/setup.ts
new file mode 100644
index 0000000000..4db457f561
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { getTrackedItemLoaderCount, mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'solid',
+ mountTestApp,
+ getTrackedItemLoaderCount,
+)
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/src/app.tsx b/benchmarks/memory/client/scenarios/preload-churn/solid/src/app.tsx
new file mode 100644
index 0000000000..18ad5b97e4
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/src/app.tsx
@@ -0,0 +1,31 @@
+import { RouterProvider } from '@tanstack/solid-router'
+import { render } from 'solid-js/web'
+import { getRouter } from './router'
+
+export { getTrackedItemLoaderCount } from '../../item-payload'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const dispose = render(() => , container)
+ let didUnmount = false
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ dispose()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/preload-churn/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..27749ab2af
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/src/routeTree.gen.ts
@@ -0,0 +1,77 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/items/$id'
+ id: '__root__' | '/' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/src/router.tsx b/benchmarks/memory/client/scenarios/preload-churn/solid/src/router.tsx
new file mode 100644
index 0000000000..faed87f1f4
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/src/router.tsx
@@ -0,0 +1,18 @@
+import { createMemoryHistory, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/'],
+ }),
+ routeTree,
+ defaultPreloadGcTime: 0,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..cb8d5a688d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/index.tsx b/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..b870b8bfd5
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return index
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/items.$id.tsx b/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..15fa2a34ab
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/items.$id.tsx
@@ -0,0 +1,20 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { createItemPayload, trackItemLoaderCall } from '../../../item-payload'
+
+export const Route = createFileRoute('/items/$id')({
+ loader: ({ params }) => {
+ trackItemLoaderCall(params.id)
+ return createItemPayload(params.id)
+ },
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data().id}:${data().byteLength}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/tsconfig.json b/benchmarks/memory/client/scenarios/preload-churn/solid/tsconfig.json
new file mode 100644
index 0000000000..b12dcb7ade
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/solid/vite.config.ts b/benchmarks/memory/client/scenarios/preload-churn/solid/vite.config.ts
new file mode 100644
index 0000000000..c86a6fcbb3
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client preload-churn (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/memory.bench.ts b/benchmarks/memory/client/scenarios/preload-churn/vue/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/memory.flame.ts b/benchmarks/memory/client/scenarios/preload-churn/vue/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/project.json b/benchmarks/memory/client/scenarios/preload-churn/vue/project.json
new file mode 100644
index 0000000000..e13388890f
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-preload-churn-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/setup.ts b/benchmarks/memory/client/scenarios/preload-churn/vue/setup.ts
new file mode 100644
index 0000000000..4aad4af31e
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { getTrackedItemLoaderCount, mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'vue',
+ mountTestApp,
+ getTrackedItemLoaderCount,
+)
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/src/app.tsx b/benchmarks/memory/client/scenarios/preload-churn/vue/src/app.tsx
new file mode 100644
index 0000000000..890447d88b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/src/app.tsx
@@ -0,0 +1,38 @@
+import { RouterProvider } from '@tanstack/vue-router'
+import { createApp } from 'vue'
+import { getRouter } from './router'
+import type {} from '@tanstack/router-core'
+
+export { getTrackedItemLoaderCount } from '../../item-payload'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const vueApp = createApp({
+ setup() {
+ return () =>
+ },
+ })
+ let didUnmount = false
+
+ vueApp.mount(container)
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ vueApp.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/preload-churn/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..a2216f2b67
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/src/routeTree.gen.ts
@@ -0,0 +1,77 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/items/$id'
+ id: '__root__' | '/' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/src/router.tsx b/benchmarks/memory/client/scenarios/preload-churn/vue/src/router.tsx
new file mode 100644
index 0000000000..86534112d7
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/src/router.tsx
@@ -0,0 +1,18 @@
+import { createMemoryHistory, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/'],
+ }),
+ routeTree,
+ defaultPreloadGcTime: 0,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..91296e6f84
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/index.tsx b/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..b4f871306c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return index
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/items.$id.tsx b/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..2a29245926
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/items.$id.tsx
@@ -0,0 +1,20 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { createItemPayload, trackItemLoaderCall } from '../../../item-payload'
+
+export const Route = createFileRoute('/items/$id')({
+ loader: ({ params }: { params: { id: string } }) => {
+ trackItemLoaderCall(params.id)
+ return createItemPayload(params.id)
+ },
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {`${data.value.id}:${data.value.byteLength}`}
+
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/tsconfig.json b/benchmarks/memory/client/scenarios/preload-churn/vue/tsconfig.json
new file mode 100644
index 0000000000..9a5872a4c0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/preload-churn/vue/vite.config.ts b/benchmarks/memory/client/scenarios/preload-churn/vue/vite.config.ts
new file mode 100644
index 0000000000..ea7ed7b695
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/preload-churn/vue/vite.config.ts
@@ -0,0 +1,36 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client preload-churn (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/memory.bench.ts b/benchmarks/memory/client/scenarios/unique-location-churn/react/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/memory.flame.ts b/benchmarks/memory/client/scenarios/unique-location-churn/react/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/project.json b/benchmarks/memory/client/scenarios/unique-location-churn/react/project.json
new file mode 100644
index 0000000000..1572552650
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-unique-location-churn-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/setup.ts b/benchmarks/memory/client/scenarios/unique-location-churn/react/setup.ts
new file mode 100644
index 0000000000..70603b5c34
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'react',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/src/app.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/app.tsx
new file mode 100644
index 0000000000..d714e7bfdd
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/app.tsx
@@ -0,0 +1,31 @@
+import { RouterProvider } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { getRouter } from './router'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const reactRoot = createRoot(container)
+ let didUnmount = false
+
+ reactRoot.render()
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ reactRoot.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..1165f57523
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routeTree.gen.ts
@@ -0,0 +1,59 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/items/$id'
+ id: '__root__' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/src/router.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/router.tsx
new file mode 100644
index 0000000000..50ad269060
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/items/initial?q=q-initial'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..889395056b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routes/items.$id.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..c9e823f6c2
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/src/routes/items.$id.tsx
@@ -0,0 +1,28 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+type ItemSearch = {
+ q: string
+}
+
+export const Route = createFileRoute('/items/$id')({
+ validateSearch: (search: Record): ItemSearch => ({
+ q: typeof search.q === 'string' ? search.q : '',
+ }),
+ loaderDeps: ({ search }) => ({ q: search.q }),
+ loader: ({ params, deps }) => ({
+ id: params.id,
+ q: deps.q,
+ checksum: params.id.length + deps.q.length,
+ }),
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+ {`${data.id}:${data.q}:${data.checksum}`}
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/tsconfig.json b/benchmarks/memory/client/scenarios/unique-location-churn/react/tsconfig.json
new file mode 100644
index 0000000000..ea566061d9
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/react/vite.config.ts b/benchmarks/memory/client/scenarios/unique-location-churn/react/vite.config.ts
new file mode 100644
index 0000000000..f424c57cef
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/react/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client unique-location-churn (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/shared.ts b/benchmarks/memory/client/scenarios/unique-location-churn/shared.ts
new file mode 100644
index 0000000000..95b382269d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/shared.ts
@@ -0,0 +1,149 @@
+import {
+ createDeterministicRandom,
+ randomSegment,
+} from '#memory-client/bench-utils'
+import {
+ createBenchContainer,
+ nextAnimationFrame,
+ noop,
+ removeBenchContainer,
+ warnClientMemoryDevMode,
+} from '#memory-client/lifecycle'
+import type { Framework, MountTestApp } from '#memory-client/lifecycle'
+
+type ItemLocation = {
+ id: string
+ q: string
+}
+
+type NavigationRouter = {
+ load: () => Promise
+ navigate: (options: {
+ to: '/items/$id'
+ params: { id: string }
+ search: { q: string }
+ replace: true
+ }) => Promise
+ subscribe: (event: 'onRendered', listener: () => void) => () => void
+}
+
+const uniqueLocationChurnIterations = 300
+// Module-level so ids stay unique across runner invocations on one mount; the
+// counter prefix removes any residual LCG birthday-collision risk.
+const benchmarkRandom = createDeterministicRandom(0xdecafbad)
+let locationCounter = 0
+
+const uninitialized = () =>
+ Promise.reject(
+ new Error('unique-location-churn benchmark is not initialized'),
+ )
+
+export function createWorkload(
+ framework: Framework,
+ mountTestApp: MountTestApp,
+) {
+ warnClientMemoryDevMode(framework)
+
+ let container: HTMLDivElement | undefined = undefined
+ let unmount = noop
+ let unsub = noop
+ let resolveRendered: () => void = noop
+ let navigateTo: (location: ItemLocation) => Promise = uninitialized
+
+ function assertRenderedId(expected: string) {
+ const actual =
+ container?.querySelector('[data-bench-id]')?.dataset.benchId
+
+ if (actual !== expected) {
+ throw new Error(`Expected rendered item id ${expected}, got ${actual}`)
+ }
+ }
+
+ async function waitForRenderedId(expected: string) {
+ for (let attempt = 0; attempt < 10; attempt++) {
+ try {
+ assertRenderedId(expected)
+ return
+ } catch {
+ await nextAnimationFrame()
+ }
+ }
+
+ assertRenderedId(expected)
+ }
+
+ function waitForNextRender() {
+ return new Promise((resolve) => {
+ resolveRendered = resolve
+ })
+ }
+
+ async function before() {
+ if (container) {
+ after()
+ }
+
+ container = createBenchContainer()
+
+ const mounted = mountTestApp(container)
+ const router = mounted.router as NavigationRouter
+ unmount = mounted.unmount
+
+ unsub = router.subscribe('onRendered', () => {
+ resolveRendered()
+ })
+
+ navigateTo = async (location) => {
+ const rendered = waitForNextRender()
+ await Promise.all([
+ router.navigate({
+ to: '/items/$id',
+ params: { id: location.id },
+ search: { q: location.q },
+ replace: true,
+ }),
+ rendered,
+ ])
+ }
+
+ await router.load()
+ await waitForRenderedId('initial')
+ }
+
+ function after() {
+ unmount()
+ removeBenchContainer(container)
+ unsub()
+
+ container = undefined
+ unmount = noop
+ unsub = noop
+ resolveRendered = noop
+ navigateTo = uninitialized
+ }
+
+ return {
+ name: `mem unique-location-churn (${framework})`,
+ before,
+ navigate: (location: ItemLocation) => navigateTo(location),
+ async run() {
+ for (let index = 0; index < uniqueLocationChurnIterations; index++) {
+ const id = `${(locationCounter++).toString(36)}-${randomSegment(benchmarkRandom)}`
+ const q = `q-${randomSegment(benchmarkRandom)}`
+
+ await navigateTo({ id, q })
+ }
+ },
+ async sanity() {
+ await before()
+
+ try {
+ await navigateTo({ id: 'sanity-one', q: 'q-sanity-one' })
+ assertRenderedId('sanity-one')
+ } finally {
+ after()
+ }
+ },
+ after,
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/memory.bench.ts b/benchmarks/memory/client/scenarios/unique-location-churn/solid/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/memory.flame.ts b/benchmarks/memory/client/scenarios/unique-location-churn/solid/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/project.json b/benchmarks/memory/client/scenarios/unique-location-churn/solid/project.json
new file mode 100644
index 0000000000..5be22de7a4
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-unique-location-churn-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/setup.ts b/benchmarks/memory/client/scenarios/unique-location-churn/solid/setup.ts
new file mode 100644
index 0000000000..493425c6a2
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'solid',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/app.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/app.tsx
new file mode 100644
index 0000000000..16000c90a1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/app.tsx
@@ -0,0 +1,29 @@
+import { RouterProvider } from '@tanstack/solid-router'
+import { render } from 'solid-js/web'
+import { getRouter } from './router'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const dispose = render(() => , container)
+ let didUnmount = false
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ dispose()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..4a451735ff
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routeTree.gen.ts
@@ -0,0 +1,59 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/items/$id'
+ id: '__root__' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/router.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/router.tsx
new file mode 100644
index 0000000000..2969b06914
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/items/initial?q=q-initial'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..cb8d5a688d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routes/items.$id.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..9724922b8d
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routes/items.$id.tsx
@@ -0,0 +1,28 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+type ItemSearch = {
+ q: string
+}
+
+export const Route = createFileRoute('/items/$id')({
+ validateSearch: (search: Record): ItemSearch => ({
+ q: typeof search.q === 'string' ? search.q : '',
+ }),
+ loaderDeps: ({ search }) => ({ q: search.q }),
+ loader: ({ params, deps }) => ({
+ id: params.id,
+ q: deps.q,
+ checksum: params.id.length + deps.q.length,
+ }),
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+ {`${data().id}:${data().q}:${data().checksum}`}
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/tsconfig.json b/benchmarks/memory/client/scenarios/unique-location-churn/solid/tsconfig.json
new file mode 100644
index 0000000000..b12dcb7ade
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/solid/vite.config.ts b/benchmarks/memory/client/scenarios/unique-location-churn/solid/vite.config.ts
new file mode 100644
index 0000000000..d4acf0019a
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client unique-location-churn (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/memory.bench.ts b/benchmarks/memory/client/scenarios/unique-location-churn/vue/memory.bench.ts
new file mode 100644
index 0000000000..e645ab38f1
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/memory.bench.ts
@@ -0,0 +1,21 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-client/bench-utils'
+import { workload } from './setup'
+
+await workload.sanity()
+
+describe('memory', () => {
+ if (workload.before && workload.after) {
+ beforeAll(workload.before)
+ afterAll(workload.after)
+
+ bench(workload.name, workload.run, {
+ ...memoryBenchOptions,
+ setup: workload.before,
+ teardown: workload.after,
+ })
+ return
+ }
+
+ bench(workload.name, workload.run, memoryBenchOptions)
+})
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/memory.flame.ts b/benchmarks/memory/client/scenarios/unique-location-churn/vue/memory.flame.ts
new file mode 100644
index 0000000000..952fd9a62c
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runClientFlameBenchmark } from '#memory-client/flame-runner'
+import { workload } from './setup.ts'
+
+await runClientFlameBenchmark(workload)
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/project.json b/benchmarks/memory/client/scenarios/unique-location-churn/vue/project.json
new file mode 100644
index 0000000000..6c05cf80b0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-client-unique-location-churn-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:client:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:client:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/setup.ts b/benchmarks/memory/client/scenarios/unique-location-churn/vue/setup.ts
new file mode 100644
index 0000000000..a38df04788
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/setup.ts
@@ -0,0 +1,13 @@
+import type { ClientMemoryWorkload } from '#memory-client/benchmark'
+import type * as App from './src/app'
+import { createWorkload } from '../shared.ts'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export const workload: ClientMemoryWorkload = createWorkload(
+ 'vue',
+ mountTestApp,
+)
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/app.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/app.tsx
new file mode 100644
index 0000000000..fad2251cc0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/app.tsx
@@ -0,0 +1,36 @@
+import { RouterProvider } from '@tanstack/vue-router'
+import { createApp } from 'vue'
+import { getRouter } from './router'
+import type {} from '@tanstack/router-core'
+
+export function mountTestApp(container: Element) {
+ const router = getRouter()
+ const vueApp = createApp({
+ setup() {
+ return () =>
+ },
+ })
+ let didUnmount = false
+
+ vueApp.mount(container)
+
+ // Full teardown mirrors the mount-unmount scenario: guard double-unmounts,
+ // release the devtools global, and detach history listeners.
+ return {
+ router,
+ unmount() {
+ if (didUnmount) {
+ return
+ }
+
+ didUnmount = true
+ vueApp.unmount()
+
+ if (typeof self !== 'undefined' && self.__TSR_ROUTER__ === router) {
+ self.__TSR_ROUTER__ = undefined
+ }
+
+ router.history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routeTree.gen.ts b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..d3e9db0345
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routeTree.gen.ts
@@ -0,0 +1,59 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/items/$id'
+ id: '__root__' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/router.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/router.tsx
new file mode 100644
index 0000000000..72f9ab565b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/router.tsx
@@ -0,0 +1,17 @@
+import { createMemoryHistory, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ history: createMemoryHistory({
+ initialEntries: ['/items/initial?q=q-initial'],
+ }),
+ routeTree,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routes/__root.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..91296e6f84
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routes/__root.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createRootRoute } from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routes/items.$id.tsx b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..1854367142
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routes/items.$id.tsx
@@ -0,0 +1,34 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+type ItemSearch = {
+ q: string
+}
+
+export const Route = createFileRoute('/items/$id')({
+ validateSearch: (search: Record): ItemSearch => ({
+ q: typeof search.q === 'string' ? search.q : '',
+ }),
+ loaderDeps: ({ search }: { search: ItemSearch }) => ({ q: search.q }),
+ loader: ({
+ params,
+ deps,
+ }: {
+ params: { id: string }
+ deps: { q: string }
+ }) => ({
+ id: params.id,
+ q: deps.q,
+ checksum: params.id.length + deps.q.length,
+ }),
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+ {`${data.value.id}:${data.value.q}:${data.value.checksum}`}
+ )
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/tsconfig.json b/benchmarks/memory/client/scenarios/unique-location-churn/vue/tsconfig.json
new file mode 100644
index 0000000000..9a5872a4c0
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "../../../vitest.setup.ts"
+ ]
+}
diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/vue/vite.config.ts b/benchmarks/memory/client/scenarios/unique-location-churn/vue/vite.config.ts
new file mode 100644
index 0000000000..423126019b
--- /dev/null
+++ b/benchmarks/memory/client/scenarios/unique-location-churn/vue/vite.config.ts
@@ -0,0 +1,36 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('production'),
+ },
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/app.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/memory-client unique-location-churn (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/tsconfig.json b/benchmarks/memory/client/tsconfig.json
new file mode 100644
index 0000000000..9280d5c42f
--- /dev/null
+++ b/benchmarks/memory/client/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": ["bench-utils.ts", "lifecycle.ts", "vitest.setup.ts"]
+}
diff --git a/benchmarks/memory/client/vitest.react.config.ts b/benchmarks/memory/client/vitest.react.config.ts
new file mode 100644
index 0000000000..44643b4d60
--- /dev/null
+++ b/benchmarks/memory/client/vitest.react.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ watch: false,
+ fileParallelism: false,
+ projects: ['./scenarios/*/react/vite.config.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/vitest.setup.ts b/benchmarks/memory/client/vitest.setup.ts
new file mode 100644
index 0000000000..ce3127275a
--- /dev/null
+++ b/benchmarks/memory/client/vitest.setup.ts
@@ -0,0 +1,9 @@
+// @ts-expect-error
+global.IS_REACT_ACT_ENVIRONMENT = true
+
+const scrollTo = () => {}
+
+window.scrollTo = scrollTo
+globalThis.scrollTo = scrollTo
+
+export {}
diff --git a/benchmarks/memory/client/vitest.solid.config.ts b/benchmarks/memory/client/vitest.solid.config.ts
new file mode 100644
index 0000000000..5c8185cdd9
--- /dev/null
+++ b/benchmarks/memory/client/vitest.solid.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ watch: false,
+ fileParallelism: false,
+ projects: ['./scenarios/*/solid/vite.config.ts'],
+ },
+})
diff --git a/benchmarks/memory/client/vitest.vue.config.ts b/benchmarks/memory/client/vitest.vue.config.ts
new file mode 100644
index 0000000000..01768185ee
--- /dev/null
+++ b/benchmarks/memory/client/vitest.vue.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ watch: false,
+ fileParallelism: false,
+ projects: ['./scenarios/*/vue/vite.config.ts'],
+ },
+})
diff --git a/benchmarks/memory/flame-control.ts b/benchmarks/memory/flame-control.ts
new file mode 100644
index 0000000000..0d6f42df33
--- /dev/null
+++ b/benchmarks/memory/flame-control.ts
@@ -0,0 +1,123 @@
+import fs from 'node:fs'
+import { createRequire } from 'node:module'
+import path from 'node:path'
+
+const flameEnabled = process.env.TSR_MEMORY_FLAME === '1'
+const heapIntervalBytes = 512 * 1024
+const heapStackDepth = 64
+
+interface HeapProfile {
+ encode: () => Uint8Array
+}
+
+interface HeapProfiler {
+ start: (intervalBytes: number, stackDepth: number) => void
+ stop: () => void
+ v8Profile: () => unknown
+ convertProfile: (
+ v8Profile: unknown,
+ ignoreSamplePath?: string,
+ sourceMapper?: unknown,
+ ) => HeapProfile
+}
+
+interface SourceMapperConstructor {
+ create: (searchDirs: Array) => Promise
+}
+
+interface PprofModule {
+ heap: HeapProfiler
+ SourceMapper: SourceMapperConstructor
+}
+
+function loadPprof() {
+ const requireFrom = process.env.TSR_MEMORY_REQUIRE_FROM
+ ? path.resolve(process.env.TSR_MEMORY_REQUIRE_FROM)
+ : import.meta.url
+ const require = createRequire(requireFrom)
+
+ return require('@datadog/pprof') as PprofModule
+}
+
+function getSourcemapDirs() {
+ return (process.env.TSR_MEMORY_SOURCEMAP_DIRS ?? '')
+ .split(path.delimiter)
+ .filter(Boolean)
+}
+
+async function createSourceMapper(pprof: PprofModule) {
+ const sourcemapDirs = getSourcemapDirs()
+
+ if (sourcemapDirs.length === 0) {
+ return undefined
+ }
+
+ return pprof.SourceMapper.create(sourcemapDirs)
+}
+
+function formatTimestamp() {
+ return new Date().toISOString().replace(/[:.]/g, '-')
+}
+
+function formatProfileFileName(profileName?: string) {
+ const profileNameSlug = profileName
+ ?.trim()
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, '-')
+ .replace(/^-+|-+$/g, '')
+ const profileNamePart = profileNameSlug ? `${profileNameSlug}-` : ''
+
+ return `heap-profile-${profileNamePart}${formatTimestamp()}.pb`
+}
+
+export async function profileFlameWorkload(
+ workload: () => Promise | void,
+ profileName?: string,
+) {
+ if (!flameEnabled) {
+ await workload()
+ return
+ }
+
+ const profileDir = process.env.TSR_MEMORY_PROFILE_DIR
+
+ if (!profileDir) {
+ throw new Error('TSR_MEMORY_PROFILE_DIR must be set for flame profiling')
+ }
+
+ fs.mkdirSync(profileDir, { recursive: true })
+
+ const pprof = loadPprof()
+ const sourceMapper = await createSourceMapper(pprof)
+
+ pprof.heap.start(heapIntervalBytes, heapStackDepth)
+
+ let workloadError: unknown = undefined
+ let v8Profile: unknown = undefined
+
+ try {
+ await workload()
+ } catch (error) {
+ workloadError = error
+ } finally {
+ try {
+ v8Profile = pprof.heap.v8Profile()
+ } finally {
+ pprof.heap.stop()
+ }
+ }
+
+ const heapProfile = pprof.heap.convertProfile(
+ v8Profile,
+ undefined,
+ sourceMapper,
+ )
+ const profilePath = path.join(profileDir, formatProfileFileName(profileName))
+
+ fs.writeFileSync(profilePath, heapProfile.encode())
+ console.log(`Heap profile written to: ${profilePath}`)
+
+ if (workloadError) {
+ throw workloadError
+ }
+}
diff --git a/benchmarks/memory/run-flame.mjs b/benchmarks/memory/run-flame.mjs
new file mode 100644
index 0000000000..cc568f3f77
--- /dev/null
+++ b/benchmarks/memory/run-flame.mjs
@@ -0,0 +1,117 @@
+#!/usr/bin/env node
+import { spawn } from 'node:child_process'
+import fs from 'node:fs'
+import { createRequire } from 'node:module'
+import path from 'node:path'
+import process from 'node:process'
+
+const [, , entrypointArg, sourcemapDirArg] = process.argv
+
+if (!entrypointArg || !sourcemapDirArg) {
+ console.error(
+ 'Usage: node benchmarks/memory/run-flame.mjs ',
+ )
+ process.exit(1)
+}
+
+const entrypointPath = path.resolve(entrypointArg)
+const sourcemapDirPath = path.resolve(sourcemapDirArg)
+
+if (!fs.existsSync(entrypointPath)) {
+ console.error(`Flame entrypoint not found: ${entrypointPath}`)
+ process.exit(1)
+}
+
+if (!fs.existsSync(sourcemapDirPath)) {
+ console.error(`Flame sourcemap directory not found: ${sourcemapDirPath}`)
+ process.exit(1)
+}
+
+const profileDir = path.join(
+ path.dirname(entrypointPath),
+ '.profiles',
+ new Date().toISOString().replace(/[:.]/g, '-'),
+)
+
+fs.mkdirSync(profileDir, { recursive: true })
+
+const entrypointRequire = createRequire(entrypointPath)
+const { generateFlamegraph, generateMarkdown } = entrypointRequire(
+ '@platformatic/flame',
+)
+
+process.env.NODE_ENV = 'production'
+
+console.log(`Flame profile directory: ${profileDir}`)
+
+const childProcess = spawn(process.execPath, [entrypointPath], {
+ cwd: profileDir,
+ env: {
+ ...process.env,
+ NODE_ENV: 'production',
+ TSR_MEMORY_FLAME: '1',
+ TSR_MEMORY_PROFILE_DIR: profileDir,
+ TSR_MEMORY_REQUIRE_FROM: entrypointPath,
+ TSR_MEMORY_SOURCEMAP_DIRS: sourcemapDirPath,
+ },
+ stdio: 'inherit',
+})
+
+console.log(`Flame profiling process: ${childProcess.pid}`)
+
+const childCode = await new Promise((resolve) => {
+ childProcess.on('error', (error) => {
+ console.error(`Failed to start Flame profiled process: ${error.message}`)
+ resolve(1)
+ })
+
+ childProcess.on('close', (code, signal) => {
+ if (signal) {
+ console.error(`Flame profiled process exited via signal ${signal}`)
+ resolve(1)
+ return
+ }
+
+ resolve(code ?? 0)
+ })
+})
+
+const heapProfilePaths = fs
+ .readdirSync(profileDir)
+ .filter((fileName) => /^heap-profile-.*\.pb$/.test(fileName))
+ .sort()
+ .map((fileName) => path.join(profileDir, fileName))
+
+for (const heapProfilePath of heapProfilePaths) {
+ const htmlPath = heapProfilePath.replace(/\.pb$/, '.html')
+ const mdPath = heapProfilePath.replace(/\.pb$/, '.md')
+
+ console.log(`Generating heap flamegraph: ${htmlPath}`)
+ try {
+ await generateFlamegraph(heapProfilePath, htmlPath)
+ } catch (error) {
+ console.warn(`Failed to generate heap flamegraph: ${error.message}`)
+ }
+
+ console.log(`Generating heap markdown: ${mdPath}`)
+ try {
+ await generateMarkdown(heapProfilePath, mdPath, { format: 'detailed' })
+ } catch (error) {
+ console.warn(`Failed to generate heap markdown: ${error.message}`)
+ }
+}
+
+const heapReportPaths = fs
+ .readdirSync(profileDir)
+ .filter((fileName) => /^heap-profile-.*\.(?:html|md)$/.test(fileName))
+ .sort()
+ .map((fileName) => path.join(profileDir, fileName))
+
+if (heapReportPaths.length > 0) {
+ console.log('Generated heap profile reports:')
+ for (const heapReportPath of heapReportPaths) {
+ console.log(` ${heapReportPath}`)
+ }
+}
+
+process.exit(childCode)
diff --git a/benchmarks/memory/server/bench-utils.ts b/benchmarks/memory/server/bench-utils.ts
new file mode 100644
index 0000000000..f1f5dadea0
--- /dev/null
+++ b/benchmarks/memory/server/bench-utils.ts
@@ -0,0 +1,90 @@
+export interface StartRequestHandler {
+ fetch: (request: Request) => Promise | Response
+}
+
+type RunSequentialRequestLoopRandomOptions =
+ | {
+ seed: number
+ random?: never
+ }
+ | {
+ random: () => number
+ seed?: never
+ }
+
+export type RunSequentialRequestLoopOptions =
+ RunSequentialRequestLoopRandomOptions & {
+ iterations?: number
+ buildRequest: (random: () => number, index: number) => Request
+ validateResponse?: (response: Response, request: Request) => void
+ }
+
+export const memoryBenchOptions = {
+ iterations: 1,
+ warmupIterations: 1,
+ time: 0,
+ warmupTime: 0,
+ throws: true,
+}
+
+export function createDeterministicRandom(seed: number) {
+ let state = seed >>> 0
+
+ return () => {
+ state = (state * 1664525 + 1013904223) >>> 0
+ return state / 0x100000000
+ }
+}
+
+export function randomSegment(random: () => number) {
+ return Math.floor(random() * 1_000_000_000).toString(36)
+}
+
+export async function drainResponse(response: Response) {
+ const reader = response.body?.getReader()
+
+ if (!reader) {
+ return
+ }
+
+ try {
+ while (true) {
+ const result = await reader.read()
+
+ if (result.done) {
+ break
+ }
+ }
+ } finally {
+ reader.releaseLock()
+ }
+}
+
+export async function runSequentialRequestLoop(
+ handler: StartRequestHandler,
+ options: RunSequentialRequestLoopOptions,
+) {
+ const { iterations = 10, buildRequest, validateResponse } = options
+ const random =
+ options.seed !== undefined
+ ? createDeterministicRandom(options.seed)
+ : options.random
+ const validate =
+ validateResponse ??
+ ((response: Response, request: Request) => {
+ if (response.status !== 200) {
+ throw new Error(
+ `Request failed with non-200 status ${response.status} (${request.url})`,
+ )
+ }
+ })
+
+ for (let index = 0; index < iterations; index++) {
+ const request = buildRequest(random, index)
+ const response = await handler.fetch(request)
+
+ validate(response, request)
+
+ await drainResponse(response)
+ }
+}
diff --git a/benchmarks/memory/server/benchmark.ts b/benchmarks/memory/server/benchmark.ts
new file mode 100644
index 0000000000..301a9f956d
--- /dev/null
+++ b/benchmarks/memory/server/benchmark.ts
@@ -0,0 +1,9 @@
+export interface ServerMemoryWorkload {
+ name: string
+ run: () => Promise | void
+}
+
+export interface ServerMemoryWorkloadGroup {
+ sanity: () => Promise | void
+ workloads: Array
+}
diff --git a/benchmarks/memory/server/flame-runner.ts b/benchmarks/memory/server/flame-runner.ts
new file mode 100644
index 0000000000..1930085aad
--- /dev/null
+++ b/benchmarks/memory/server/flame-runner.ts
@@ -0,0 +1,12 @@
+import { profileFlameWorkload } from '../flame-control.ts'
+import type { ServerMemoryWorkloadGroup } from './benchmark.ts'
+
+export async function runServerFlameBenchmark(
+ workloadGroup: ServerMemoryWorkloadGroup,
+) {
+ await workloadGroup.sanity()
+
+ for (const workload of workloadGroup.workloads) {
+ await profileFlameWorkload(workload.run, workload.name)
+ }
+}
diff --git a/benchmarks/memory/server/package.json b/benchmarks/memory/server/package.json
new file mode 100644
index 0000000000..68ba6b3c32
--- /dev/null
+++ b/benchmarks/memory/server/package.json
@@ -0,0 +1,269 @@
+{
+ "name": "@benchmarks/memory-server",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "clean:profiles": "rm -rf scenarios/*/*/.profiles"
+ },
+ "imports": {
+ "#memory-server/benchmark": "./benchmark.ts",
+ "#memory-server/bench-utils": "./bench-utils.ts",
+ "#memory-server/flame-runner": "./flame-runner.ts"
+ },
+ "dependencies": {
+ "@tanstack/react-router": "workspace:*",
+ "@tanstack/react-start": "workspace:*",
+ "@tanstack/solid-router": "workspace:*",
+ "@tanstack/solid-start": "workspace:*",
+ "@tanstack/vue-router": "workspace:*",
+ "@tanstack/vue-start": "workspace:*",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "solid-js": "^1.9.10",
+ "vue": "^3.5.16"
+ },
+ "devDependencies": {
+ "@codspeed/vitest-plugin": "^5.5.0",
+ "@datadog/pprof": "^5.13.2",
+ "@platformatic/flame": "^1.6.0",
+ "@vitejs/plugin-react": "^6.0.1",
+ "@vitejs/plugin-vue-jsx": "^5.1.5",
+ "typescript": "^6.0.2",
+ "vite": "^8.0.14",
+ "vite-plugin-solid": "^2.11.11",
+ "vitest": "^4.1.4"
+ },
+ "nx": {
+ "targets": {
+ "build:react": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-react",
+ "@benchmarks/memory-server-server-fn-churn-react",
+ "@benchmarks/memory-server-error-paths-react",
+ "@benchmarks/memory-server-aborted-requests-react",
+ "@benchmarks/memory-server-peak-large-page-react",
+ "@benchmarks/memory-server-streaming-peak-react",
+ "@benchmarks/memory-server-serialization-payload-react"
+ ],
+ "target": "build:ssr"
+ }
+ ]
+ },
+ "build:solid": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-solid",
+ "@benchmarks/memory-server-server-fn-churn-solid",
+ "@benchmarks/memory-server-error-paths-solid",
+ "@benchmarks/memory-server-aborted-requests-solid",
+ "@benchmarks/memory-server-peak-large-page-solid",
+ "@benchmarks/memory-server-streaming-peak-solid",
+ "@benchmarks/memory-server-serialization-payload-solid"
+ ],
+ "target": "build:ssr"
+ }
+ ]
+ },
+ "build:vue": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-vue",
+ "@benchmarks/memory-server-server-fn-churn-vue",
+ "@benchmarks/memory-server-error-paths-vue",
+ "@benchmarks/memory-server-aborted-requests-vue",
+ "@benchmarks/memory-server-peak-large-page-vue",
+ "@benchmarks/memory-server-streaming-peak-vue",
+ "@benchmarks/memory-server-serialization-payload-vue"
+ ],
+ "target": "build:ssr"
+ }
+ ]
+ },
+ "build:react:flame": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-react",
+ "@benchmarks/memory-server-server-fn-churn-react",
+ "@benchmarks/memory-server-error-paths-react",
+ "@benchmarks/memory-server-aborted-requests-react",
+ "@benchmarks/memory-server-peak-large-page-react",
+ "@benchmarks/memory-server-streaming-peak-react",
+ "@benchmarks/memory-server-serialization-payload-react"
+ ],
+ "target": "build:ssr:flame"
+ }
+ ]
+ },
+ "build:solid:flame": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-solid",
+ "@benchmarks/memory-server-server-fn-churn-solid",
+ "@benchmarks/memory-server-error-paths-solid",
+ "@benchmarks/memory-server-aborted-requests-solid",
+ "@benchmarks/memory-server-peak-large-page-solid",
+ "@benchmarks/memory-server-streaming-peak-solid",
+ "@benchmarks/memory-server-serialization-payload-solid"
+ ],
+ "target": "build:ssr:flame"
+ }
+ ]
+ },
+ "build:vue:flame": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-vue",
+ "@benchmarks/memory-server-server-fn-churn-vue",
+ "@benchmarks/memory-server-error-paths-vue",
+ "@benchmarks/memory-server-aborted-requests-vue",
+ "@benchmarks/memory-server-peak-large-page-vue",
+ "@benchmarks/memory-server-streaming-peak-vue",
+ "@benchmarks/memory-server-serialization-payload-vue"
+ ],
+ "target": "build:ssr:flame"
+ }
+ ]
+ },
+ "test:flame:react": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-react",
+ "@benchmarks/memory-server-server-fn-churn-react",
+ "@benchmarks/memory-server-error-paths-react",
+ "@benchmarks/memory-server-aborted-requests-react",
+ "@benchmarks/memory-server-peak-large-page-react",
+ "@benchmarks/memory-server-streaming-peak-react",
+ "@benchmarks/memory-server-serialization-payload-react"
+ ],
+ "target": "test:flame"
+ }
+ ]
+ },
+ "test:flame:solid": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-solid",
+ "@benchmarks/memory-server-server-fn-churn-solid",
+ "@benchmarks/memory-server-error-paths-solid",
+ "@benchmarks/memory-server-aborted-requests-solid",
+ "@benchmarks/memory-server-peak-large-page-solid",
+ "@benchmarks/memory-server-streaming-peak-solid",
+ "@benchmarks/memory-server-serialization-payload-solid"
+ ],
+ "target": "test:flame"
+ }
+ ]
+ },
+ "test:flame:vue": {
+ "executor": "nx:noop",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-vue",
+ "@benchmarks/memory-server-server-fn-churn-vue",
+ "@benchmarks/memory-server-error-paths-vue",
+ "@benchmarks/memory-server-aborted-requests-vue",
+ "@benchmarks/memory-server-peak-large-page-vue",
+ "@benchmarks/memory-server-streaming-peak-vue",
+ "@benchmarks/memory-server-serialization-payload-vue"
+ ],
+ "target": "test:flame"
+ }
+ ]
+ },
+ "test:perf:react": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": [
+ "build:react"
+ ],
+ "options": {
+ "command": "NODE_ENV=production vitest bench --config ./vitest.react.config.ts",
+ "cwd": "benchmarks/memory/server"
+ }
+ },
+ "test:perf:solid": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": [
+ "build:solid"
+ ],
+ "options": {
+ "command": "NODE_ENV=production vitest bench --config ./vitest.solid.config.ts",
+ "cwd": "benchmarks/memory/server"
+ }
+ },
+ "test:perf:vue": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": [
+ "build:vue"
+ ],
+ "options": {
+ "command": "NODE_ENV=production vitest bench --config ./vitest.vue.config.ts",
+ "cwd": "benchmarks/memory/server"
+ }
+ },
+ "test:types": {
+ "executor": "nx:noop",
+ "dependsOn": [
+ {
+ "projects": [
+ "@benchmarks/memory-server-request-churn-react",
+ "@benchmarks/memory-server-server-fn-churn-react",
+ "@benchmarks/memory-server-error-paths-react",
+ "@benchmarks/memory-server-aborted-requests-react",
+ "@benchmarks/memory-server-peak-large-page-react",
+ "@benchmarks/memory-server-streaming-peak-react",
+ "@benchmarks/memory-server-serialization-payload-react",
+ "@benchmarks/memory-server-request-churn-solid",
+ "@benchmarks/memory-server-server-fn-churn-solid",
+ "@benchmarks/memory-server-error-paths-solid",
+ "@benchmarks/memory-server-aborted-requests-solid",
+ "@benchmarks/memory-server-peak-large-page-solid",
+ "@benchmarks/memory-server-streaming-peak-solid",
+ "@benchmarks/memory-server-serialization-payload-solid",
+ "@benchmarks/memory-server-request-churn-vue",
+ "@benchmarks/memory-server-server-fn-churn-vue",
+ "@benchmarks/memory-server-error-paths-vue",
+ "@benchmarks/memory-server-aborted-requests-vue",
+ "@benchmarks/memory-server-peak-large-page-vue",
+ "@benchmarks/memory-server-streaming-peak-vue",
+ "@benchmarks/memory-server-serialization-payload-vue"
+ ],
+ "target": "test:types:ssr"
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/deferred-records.ts b/benchmarks/memory/server/scenarios/aborted-requests/deferred-records.ts
new file mode 100644
index 0000000000..4886e3ee80
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/deferred-records.ts
@@ -0,0 +1,18 @@
+const recordCount = 20
+
+export type RecordGroup = 'alpha' | 'beta'
+
+export interface DeferredRecord {
+ id: string
+ label: string
+}
+
+export function makeAbortedRequestRecords(
+ id: string,
+ group: RecordGroup,
+): Array {
+ return Array.from({ length: recordCount }, (_, index) => ({
+ id: `${group}-${id}-${index}`,
+ label: `deferred-${group}-${id}-${index}`,
+ }))
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/memory.bench.ts b/benchmarks/memory/server/scenarios/aborted-requests/react/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/memory.flame.ts b/benchmarks/memory/server/scenarios/aborted-requests/react/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/project.json b/benchmarks/memory/server/scenarios/aborted-requests/react/project.json
new file mode 100644
index 0000000000..bea90c8766
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-aborted-requests-react",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/setup.ts b/benchmarks/memory/server/scenarios/aborted-requests/react/setup.ts
new file mode 100644
index 0000000000..0c98f54ddd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('react', handler)
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/aborted-requests/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..fbbc25ecfb
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as StreamIdRouteImport } from './routes/stream.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const StreamIdRoute = StreamIdRouteImport.update({
+ id: '/stream/$id',
+ path: '/stream/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/stream/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/stream/$id'
+ id: '__root__' | '/' | '/stream/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ StreamIdRoute: typeof StreamIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/stream/$id': {
+ id: '/stream/$id'
+ path: '/stream/$id'
+ fullPath: '/stream/$id'
+ preLoaderRoute: typeof StreamIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ StreamIdRoute: StreamIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/src/router.tsx b/benchmarks/memory/server/scenarios/aborted-requests/react/src/router.tsx
new file mode 100644
index 0000000000..7c4eb0babe
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..c5f9de6922
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/__root.tsx
@@ -0,0 +1,27 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/index.tsx b/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/index.tsx
new file mode 100644
index 0000000000..08d534a779
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return aborted-requests-index
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/stream.$id.tsx b/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/stream.$id.tsx
new file mode 100644
index 0000000000..ecd9ef524d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/stream.$id.tsx
@@ -0,0 +1,66 @@
+import { Await, createFileRoute } from '@tanstack/react-router'
+import { Suspense } from 'react'
+import { makeAbortedRequestRecords } from '../../../deferred-records'
+
+function resolveAfterMicrotasks(microtasks: number, value: () => T) {
+ let promise = Promise.resolve()
+
+ for (let index = 0; index < microtasks; index++) {
+ promise = promise.then(() => undefined)
+ }
+
+ return promise.then(value)
+}
+
+export const Route = createFileRoute('/stream/$id')({
+ loader: ({ params }) => ({
+ eager: `eager-${params.id}`,
+ alpha: resolveAfterMicrotasks(32, () =>
+ makeAbortedRequestRecords(params.id, 'alpha'),
+ ),
+ beta: resolveAfterMicrotasks(64, () =>
+ makeAbortedRequestRecords(params.id, 'beta'),
+ ),
+ }),
+ component: StreamComponent,
+})
+
+function StreamComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {data.eager}
+ loading-alpha
+ }
+ >
+
+ {(records) => (
+
+ {records.map((record) => (
+ - {record.label}
+ ))}
+
+ )}
+
+
+ loading-beta
+ }
+ >
+
+ {(records) => (
+
+ {records.map((record) => (
+ - {record.label}
+ ))}
+
+ )}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/tsconfig.json b/benchmarks/memory/server/scenarios/aborted-requests/react/tsconfig.json
new file mode 100644
index 0000000000..11ddcce4ea
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/react/vite.config.ts b/benchmarks/memory/server/scenarios/aborted-requests/react/vite.config.ts
new file mode 100644
index 0000000000..a3f4836091
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/react/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server aborted-requests (react)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/shared.ts b/benchmarks/memory/server/scenarios/aborted-requests/shared.ts
new file mode 100644
index 0000000000..9b71c37617
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/shared.ts
@@ -0,0 +1,278 @@
+import type { StartRequestHandler } from '#memory-server/bench-utils'
+
+export type { StartRequestHandler }
+
+type Framework = 'react' | 'solid' | 'vue'
+
+type AbortedRequestReadMode = 'first-chunk' | 'shell-before-deferred'
+type AbortedRequestCancelMode = 'plain' | 'swallow-abort-error'
+type AbortedRequestDrainMode = 'microtasks' | 'tasks'
+
+type AbortedRequestMode = {
+ readMode: AbortedRequestReadMode
+ cancelMode: AbortedRequestCancelMode
+ drainMode: AbortedRequestDrainMode
+}
+
+const abortedRequestIterations = 100
+let abortedRequestCounter = 0
+const eagerMarker = 'data-bench="aborted-requests-eager"'
+const alphaFallbackMarker = 'data-bench="aborted-requests-alpha-fallback"'
+const betaFallbackMarker = 'data-bench="aborted-requests-beta-fallback"'
+const alphaFirstRecord = (id: string) => `deferred-alpha-${id}-0`
+const alphaLastRecord = (id: string) => `deferred-alpha-${id}-19`
+const betaFirstRecord = (id: string) => `deferred-beta-${id}-0`
+const betaLastRecord = (id: string) => `deferred-beta-${id}-19`
+
+const textDecoder = new TextDecoder()
+const abortedRequestModes: Record = {
+ react: {
+ readMode: 'first-chunk',
+ cancelMode: 'plain',
+ drainMode: 'microtasks',
+ },
+ solid: {
+ readMode: 'first-chunk',
+ cancelMode: 'plain',
+ drainMode: 'tasks',
+ },
+ vue: {
+ readMode: 'shell-before-deferred',
+ cancelMode: 'swallow-abort-error',
+ drainMode: 'tasks',
+ },
+}
+
+const documentRequestInit = {
+ method: 'GET',
+ headers: {
+ accept: 'text/html',
+ },
+} satisfies RequestInit
+
+function buildStreamRequest(id: string, signal?: AbortSignal) {
+ const init: RequestInit = { ...documentRequestInit }
+
+ if (signal) {
+ init.signal = signal
+ }
+
+ return new Request(`http://localhost/stream/${id}`, init)
+}
+
+function validateDocumentResponse(response: Response, request: Request) {
+ if (response.status !== 200) {
+ throw new Error(
+ `Expected status 200 for ${request.url}, got ${response.status}`,
+ )
+ }
+}
+
+async function readFirstChunk(response: Response, request: Request) {
+ if (!response.body) {
+ throw new Error(`Expected response body for ${request.url}`)
+ }
+
+ const reader = response.body.getReader()
+ const result = await reader.read()
+ const value = result.value
+
+ if (result.done || !value || value.byteLength === 0) {
+ await reader.cancel()
+ throw new Error(`Expected a non-empty first chunk for ${request.url}`)
+ }
+
+ return {
+ reader,
+ value,
+ }
+}
+
+async function readShellBeforeDeferred(
+ response: Response,
+ request: Request,
+ id: string,
+) {
+ if (!response.body) {
+ throw new Error(`Expected response body for ${request.url}`)
+ }
+
+ const reader = response.body.getReader()
+ let text = ''
+
+ while (true) {
+ const result = await reader.read()
+ const value = result.value
+
+ if (result.done || !value || value.byteLength === 0) {
+ await reader.cancel()
+ throw new Error(`Expected shell content for ${request.url}`)
+ }
+
+ text += textDecoder.decode(value, { stream: true })
+
+ for (const marker of [
+ alphaFirstRecord(id),
+ alphaLastRecord(id),
+ betaFirstRecord(id),
+ betaLastRecord(id),
+ ]) {
+ if (text.includes(marker)) {
+ await reader.cancel()
+ throw new Error(
+ `Shell chunks already included deferred content ${marker}`,
+ )
+ }
+ }
+
+ if (
+ text.includes(eagerMarker) &&
+ text.includes(alphaFallbackMarker) &&
+ text.includes(betaFallbackMarker)
+ ) {
+ return {
+ reader,
+ text,
+ }
+ }
+ }
+}
+
+async function readSanityStream(
+ response: Response,
+ request: Request,
+ mode: AbortedRequestMode,
+ id: string,
+) {
+ if (mode.readMode === 'shell-before-deferred') {
+ return readShellBeforeDeferred(response, request, id)
+ }
+
+ const { reader, value } = await readFirstChunk(response, request)
+
+ return {
+ reader,
+ text: textDecoder.decode(value),
+ }
+}
+
+function readLoopStream(
+ mode: AbortedRequestReadMode,
+ response: Response,
+ request: Request,
+ id: string,
+) {
+ if (mode === 'shell-before-deferred') {
+ return readShellBeforeDeferred(response, request, id)
+ }
+
+ return readFirstChunk(response, request)
+}
+
+async function cancelReader(
+ reader: ReadableStreamDefaultReader,
+ mode: AbortedRequestCancelMode,
+) {
+ if (mode === 'swallow-abort-error') {
+ try {
+ await reader.cancel()
+ } catch (error) {
+ if (!(error instanceof DOMException && error.name === 'AbortError')) {
+ throw error
+ }
+ }
+
+ return
+ }
+
+ await reader.cancel()
+}
+
+async function drainCancellation(mode: AbortedRequestDrainMode) {
+ await Promise.resolve()
+ await Promise.resolve()
+
+ if (mode === 'tasks') {
+ await new Promise((resolve) => setTimeout(resolve, 0))
+ }
+}
+
+async function assertAbortedRequestsSanity(
+ handler: StartRequestHandler,
+ mode: AbortedRequestMode,
+) {
+ const fullId = 'sanity-full'
+ const fullRequest = buildStreamRequest(fullId)
+ const fullResponse = await handler.fetch(fullRequest)
+ validateDocumentResponse(fullResponse, fullRequest)
+
+ const fullBody = await fullResponse.text()
+
+ if (!fullBody.includes(eagerMarker)) {
+ throw new Error('Expected full sanity response to include the eager marker')
+ }
+
+ const midStreamId = 'sanity-mid-stream'
+ const controller = new AbortController()
+ const midStreamRequest = buildStreamRequest(midStreamId, controller.signal)
+ const midStreamResponse = await handler.fetch(midStreamRequest)
+ validateDocumentResponse(midStreamResponse, midStreamRequest)
+
+ const { reader, text } = await readSanityStream(
+ midStreamResponse,
+ midStreamRequest,
+ mode,
+ midStreamId,
+ )
+
+ if (!text.includes(eagerMarker)) {
+ throw new Error('Expected sanity stream to include the eager marker')
+ }
+
+ // reader.cancel() is the response-stream cancellation path if the handler
+ // does not observe Request.signal for this in-process request.
+ controller.abort()
+ await cancelReader(reader, mode.cancelMode)
+ await drainCancellation(mode.drainMode)
+}
+
+async function runAbortedRequestLoop(
+ handler: StartRequestHandler,
+ mode: AbortedRequestMode,
+) {
+ for (let index = 0; index < abortedRequestIterations; index++) {
+ const controller = new AbortController()
+ const id = `abort-${(abortedRequestCounter++).toString(36)}`
+ const request = buildStreamRequest(id, controller.signal)
+ const response = await handler.fetch(request)
+ validateDocumentResponse(response, request)
+
+ const { reader } = await readLoopStream(
+ mode.readMode,
+ response,
+ request,
+ id,
+ )
+ controller.abort()
+ await cancelReader(reader, mode.cancelMode)
+ await drainCancellation(mode.drainMode)
+ }
+}
+
+export function createWorkloadGroup(
+ framework: Framework,
+ handler: StartRequestHandler,
+) {
+ const mode = abortedRequestModes[framework]
+ const run = () => runAbortedRequestLoop(handler, mode)
+
+ return {
+ sanity: () => assertAbortedRequestsSanity(handler, mode),
+ workloads: [
+ {
+ name: `mem aborted-requests (${framework})`,
+ run,
+ },
+ ],
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/memory.bench.ts b/benchmarks/memory/server/scenarios/aborted-requests/solid/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/memory.flame.ts b/benchmarks/memory/server/scenarios/aborted-requests/solid/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/project.json b/benchmarks/memory/server/scenarios/aborted-requests/solid/project.json
new file mode 100644
index 0000000000..345fcc0720
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-aborted-requests-solid",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/setup.ts b/benchmarks/memory/server/scenarios/aborted-requests/solid/setup.ts
new file mode 100644
index 0000000000..4fe85c3f00
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('solid', handler)
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..04ebed80f0
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as StreamIdRouteImport } from './routes/stream.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const StreamIdRoute = StreamIdRouteImport.update({
+ id: '/stream/$id',
+ path: '/stream/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/stream/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/stream/$id'
+ id: '__root__' | '/' | '/stream/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ StreamIdRoute: typeof StreamIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/stream/$id': {
+ id: '/stream/$id'
+ path: '/stream/$id'
+ fullPath: '/stream/$id'
+ preLoaderRoute: typeof StreamIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ StreamIdRoute: StreamIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/src/router.tsx b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/router.tsx
new file mode 100644
index 0000000000..038ec0ab5e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..aaf7dfdd89
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { HydrationScript } from 'solid-js/web'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charset: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/index.tsx b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..3aac56e824
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return aborted-requests-index
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/stream.$id.tsx b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/stream.$id.tsx
new file mode 100644
index 0000000000..95d69ad91d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/stream.$id.tsx
@@ -0,0 +1,126 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { Show, Suspense, createResource } from 'solid-js'
+import {
+ makeAbortedRequestRecords,
+ type DeferredRecord,
+ type RecordGroup,
+} from '../../../deferred-records'
+
+const alphaDelayMs = 50
+const betaDelayMs = 75
+const abortProbeAlphaDelayMs = 500
+const abortProbeBetaDelayMs = 750
+
+function isAbortProbeId(id: string) {
+ return id === 'sanity-mid-stream' || id.startsWith('abort-')
+}
+
+function getDelay(id: string, group: RecordGroup) {
+ if (isAbortProbeId(id)) {
+ return group === 'alpha' ? abortProbeAlphaDelayMs : abortProbeBetaDelayMs
+ }
+
+ return group === 'alpha' ? alphaDelayMs : betaDelayMs
+}
+
+function resolveAfterDelay(
+ delayMs: number,
+ signal: AbortSignal,
+ value: () => T,
+ abortedValue: () => T,
+) {
+ return new Promise((resolve) => {
+ if (signal.aborted) {
+ resolve(abortedValue())
+ return
+ }
+
+ const timeoutId = setTimeout(() => {
+ signal.removeEventListener('abort', onAbort)
+ resolve(value())
+ }, delayMs)
+
+ const onAbort = () => {
+ clearTimeout(timeoutId)
+ resolve(abortedValue())
+ }
+
+ signal.addEventListener('abort', onAbort, { once: true })
+ })
+}
+
+function makeDeferredRecords(
+ id: string,
+ group: RecordGroup,
+ signal: AbortSignal,
+) {
+ const delayMs = getDelay(id, group)
+
+ return resolveAfterDelay(
+ delayMs,
+ signal,
+ () => makeAbortedRequestRecords(id, group),
+ () => [],
+ )
+}
+
+export const Route = createFileRoute('/stream/$id')({
+ loader: ({ params, abortController }) => ({
+ eager: `eager-${params.id}`,
+ alpha: makeDeferredRecords(params.id, 'alpha', abortController.signal),
+ beta: makeDeferredRecords(params.id, 'beta', abortController.signal),
+ }),
+ component: StreamComponent,
+})
+
+function StreamComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {data().eager}
+ loading-alpha
+ }
+ >
+
+
+ loading-beta
+ }
+ >
+
+
+
+ )
+}
+
+function DeferredRecords(props: {
+ promise: Promise>
+ dataBench: string
+}) {
+ const [records] = createResource(
+ () => props.promise,
+ (promise) => promise,
+ )
+
+ return (
+
+ {(resolvedRecords) => (
+
+ {resolvedRecords().map((record) => (
+ - {record.label}
+ ))}
+
+ )}
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/tsconfig.json b/benchmarks/memory/server/scenarios/aborted-requests/solid/tsconfig.json
new file mode 100644
index 0000000000..4b61264e11
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts b/benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts
new file mode 100644
index 0000000000..d3a374cab7
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ solid({ ssr: true, hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server aborted-requests (solid)',
+ watch: false,
+ environment: 'node',
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/memory.bench.ts b/benchmarks/memory/server/scenarios/aborted-requests/vue/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/memory.flame.ts b/benchmarks/memory/server/scenarios/aborted-requests/vue/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/project.json b/benchmarks/memory/server/scenarios/aborted-requests/vue/project.json
new file mode 100644
index 0000000000..8ca7d84660
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-aborted-requests-vue",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/setup.ts b/benchmarks/memory/server/scenarios/aborted-requests/vue/setup.ts
new file mode 100644
index 0000000000..6683dafe6b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('vue', handler)
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..192f1d6dc7
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as StreamIdRouteImport } from './routes/stream.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const StreamIdRoute = StreamIdRouteImport.update({
+ id: '/stream/$id',
+ path: '/stream/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/stream/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/stream/$id'
+ id: '__root__' | '/' | '/stream/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ StreamIdRoute: typeof StreamIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/stream/$id': {
+ id: '/stream/$id'
+ path: '/stream/$id'
+ fullPath: '/stream/$id'
+ preLoaderRoute: typeof StreamIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ StreamIdRoute: StreamIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/vue-start'
+declare module '@tanstack/vue-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/src/router.tsx b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/router.tsx
new file mode 100644
index 0000000000..4290e7cdd3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..de29ee1612
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ Body,
+ HeadContent,
+ Html,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/index.tsx b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..af662b29f7
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return aborted-requests-index
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/stream.$id.tsx b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/stream.$id.tsx
new file mode 100644
index 0000000000..d064bf75e9
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/stream.$id.tsx
@@ -0,0 +1,120 @@
+import { Await, createFileRoute } from '@tanstack/vue-router'
+import { Suspense } from 'vue'
+import {
+ makeAbortedRequestRecords,
+ type DeferredRecord,
+ type RecordGroup,
+} from '../../../deferred-records'
+
+const alphaDelayMs = 50
+const betaDelayMs = 75
+const abortProbeAlphaDelayMs = 500
+const abortProbeBetaDelayMs = 750
+
+function isAbortProbeId(id: string) {
+ return id === 'sanity-mid-stream' || id.startsWith('abort-')
+}
+
+function getDelay(id: string, group: RecordGroup) {
+ if (isAbortProbeId(id)) {
+ return group === 'alpha' ? abortProbeAlphaDelayMs : abortProbeBetaDelayMs
+ }
+
+ return group === 'alpha' ? alphaDelayMs : betaDelayMs
+}
+
+function resolveAfterDelay(
+ delayMs: number,
+ signal: AbortSignal,
+ value: () => T,
+ abortedValue: () => T,
+) {
+ return new Promise((resolve) => {
+ if (signal.aborted) {
+ resolve(abortedValue())
+ return
+ }
+
+ const timeoutId = setTimeout(() => {
+ signal.removeEventListener('abort', onAbort)
+ resolve(value())
+ }, delayMs)
+
+ const onAbort = () => {
+ clearTimeout(timeoutId)
+ resolve(abortedValue())
+ }
+
+ signal.addEventListener('abort', onAbort, { once: true })
+ })
+}
+
+function makeDeferredRecords(
+ id: string,
+ group: RecordGroup,
+ signal: AbortSignal,
+) {
+ const delayMs = getDelay(id, group)
+
+ return resolveAfterDelay(
+ delayMs,
+ signal,
+ () => makeAbortedRequestRecords(id, group),
+ () => [],
+ )
+}
+
+export const Route = createFileRoute('/stream/$id')({
+ loader: ({ params, abortController }) => ({
+ eager: `eager-${params.id}`,
+ alpha: makeDeferredRecords(params.id, 'alpha', abortController.signal),
+ beta: makeDeferredRecords(params.id, 'beta', abortController.signal),
+ }),
+ component: StreamComponent,
+})
+
+function StreamComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {data.value.eager}
+ loading-alpha
+ loading-beta
+
+ {{
+ default: () => (
+ ) => (
+
+ {records.map((record) => (
+ - {record.label}
+ ))}
+
+ )}
+ />
+ ),
+ fallback: () => null,
+ }}
+
+
+ {{
+ default: () => (
+ ) => (
+
+ {records.map((record) => (
+ - {record.label}
+ ))}
+
+ )}
+ />
+ ),
+ fallback: () => null,
+ }}
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/tsconfig.json b/benchmarks/memory/server/scenarios/aborted-requests/vue/tsconfig.json
new file mode 100644
index 0000000000..9ad6481342
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/vue/vite.config.ts b/benchmarks/memory/server/scenarios/aborted-requests/vue/vite.config.ts
new file mode 100644
index 0000000000..2b42e2be39
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/aborted-requests/vue/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server aborted-requests (vue)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/memory.bench.ts b/benchmarks/memory/server/scenarios/error-paths/react/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/memory.flame.ts b/benchmarks/memory/server/scenarios/error-paths/react/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/project.json b/benchmarks/memory/server/scenarios/error-paths/react/project.json
new file mode 100644
index 0000000000..6a399fe41e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-error-paths-react",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/setup.ts b/benchmarks/memory/server/scenarios/error-paths/react/setup.ts
new file mode 100644
index 0000000000..0c98f54ddd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('react', handler)
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/error-paths/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..fd373be6b4
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/src/routeTree.gen.ts
@@ -0,0 +1,146 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as TargetIdRouteImport } from './routes/target.$id'
+import { Route as MissingIdRouteImport } from './routes/missing.$id'
+import { Route as FromIdRouteImport } from './routes/from.$id'
+import { Route as BoomIdRouteImport } from './routes/boom.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const TargetIdRoute = TargetIdRouteImport.update({
+ id: '/target/$id',
+ path: '/target/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const MissingIdRoute = MissingIdRouteImport.update({
+ id: '/missing/$id',
+ path: '/missing/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FromIdRoute = FromIdRouteImport.update({
+ id: '/from/$id',
+ path: '/from/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const BoomIdRoute = BoomIdRouteImport.update({
+ id: '/boom/$id',
+ path: '/boom/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/boom/$id' | '/from/$id' | '/missing/$id' | '/target/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/boom/$id' | '/from/$id' | '/missing/$id' | '/target/$id'
+ id:
+ | '__root__'
+ | '/'
+ | '/boom/$id'
+ | '/from/$id'
+ | '/missing/$id'
+ | '/target/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ BoomIdRoute: typeof BoomIdRoute
+ FromIdRoute: typeof FromIdRoute
+ MissingIdRoute: typeof MissingIdRoute
+ TargetIdRoute: typeof TargetIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/target/$id': {
+ id: '/target/$id'
+ path: '/target/$id'
+ fullPath: '/target/$id'
+ preLoaderRoute: typeof TargetIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/missing/$id': {
+ id: '/missing/$id'
+ path: '/missing/$id'
+ fullPath: '/missing/$id'
+ preLoaderRoute: typeof MissingIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/from/$id': {
+ id: '/from/$id'
+ path: '/from/$id'
+ fullPath: '/from/$id'
+ preLoaderRoute: typeof FromIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/boom/$id': {
+ id: '/boom/$id'
+ path: '/boom/$id'
+ fullPath: '/boom/$id'
+ preLoaderRoute: typeof BoomIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ BoomIdRoute: BoomIdRoute,
+ FromIdRoute: FromIdRoute,
+ MissingIdRoute: MissingIdRoute,
+ TargetIdRoute: TargetIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/router.tsx b/benchmarks/memory/server/scenarios/error-paths/react/src/router.tsx
new file mode 100644
index 0000000000..e31bd0abf7
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/src/router.tsx
@@ -0,0 +1,23 @@
+import { createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ defaultNotFoundComponent: DefaultNotFound,
+ })
+}
+
+// Without this, every unmatched-URL request logs a router warning in
+// non-production ad-hoc runs.
+function DefaultNotFound() {
+ return error-paths-unmatched
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..c5f9de6922
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/__root.tsx
@@ -0,0 +1,27 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/routes/boom.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/boom.$id.tsx
new file mode 100644
index 0000000000..fafa81a9bb
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/boom.$id.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/boom/$id')({
+ loader: ({ params }) => {
+ throw new Error(`boom-${params.id}`)
+ },
+ errorComponent: BoomErrorComponent,
+ component: BoomComponent,
+})
+
+function BoomErrorComponent() {
+ return error-paths-error-boundary
+}
+
+function BoomComponent() {
+ return null
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/routes/from.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/from.$id.tsx
new file mode 100644
index 0000000000..4c45e76c92
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/from.$id.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/from/$id')({
+ loader: ({ params }) => {
+ const { id } = params
+
+ throw redirect({ to: '/target/$id', params: { id }, statusCode: 302 })
+ },
+ component: FromComponent,
+})
+
+function FromComponent() {
+ return null
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/routes/index.tsx b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/index.tsx
new file mode 100644
index 0000000000..c5492486b4
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return error-paths-index
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/routes/missing.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/missing.$id.tsx
new file mode 100644
index 0000000000..84640e9712
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/missing.$id.tsx
@@ -0,0 +1,19 @@
+import { createFileRoute, notFound } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/missing/$id')({
+ loader: () => {
+ throw notFound()
+ },
+ notFoundComponent: MissingNotFoundComponent,
+ component: MissingComponent,
+})
+
+function MissingNotFoundComponent() {
+ return (
+ error-paths-not-found-boundary
+ )
+}
+
+function MissingComponent() {
+ return null
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/routes/target.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/target.$id.tsx
new file mode 100644
index 0000000000..1c13394cb2
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/src/routes/target.$id.tsx
@@ -0,0 +1,11 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/target/$id')({
+ component: TargetComponent,
+})
+
+function TargetComponent() {
+ const params = Route.useParams()
+
+ return {`target-${params.id}`}
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/tsconfig.json b/benchmarks/memory/server/scenarios/error-paths/react/tsconfig.json
new file mode 100644
index 0000000000..11ddcce4ea
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/react/vite.config.ts b/benchmarks/memory/server/scenarios/error-paths/react/vite.config.ts
new file mode 100644
index 0000000000..2a4fd9818a
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/react/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server error-paths (react)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/error-paths/shared.ts b/benchmarks/memory/server/scenarios/error-paths/shared.ts
new file mode 100644
index 0000000000..d4c1877465
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/shared.ts
@@ -0,0 +1,192 @@
+import {
+ createDeterministicRandom,
+ randomSegment,
+ runSequentialRequestLoop,
+} from '#memory-server/bench-utils'
+import type { StartRequestHandler } from '#memory-server/bench-utils'
+
+export type { StartRequestHandler }
+
+type Framework = 'react' | 'solid' | 'vue'
+
+const errorPathsIterations = 50
+const redirectSeed = 0xdecafbad
+const notFoundSeed = 0xdecafb0d
+const errorSeed = 0xdecafbed
+const unmatchedSeed = 0xdecaf00d
+const redirectStatus = 302
+const notFoundStatus = 404
+const errorStatus = 500
+// Module-level so each error-path bench keeps advancing across runner invocations.
+const redirectRandom = createDeterministicRandom(redirectSeed)
+const notFoundRandom = createDeterministicRandom(notFoundSeed)
+const errorRandom = createDeterministicRandom(errorSeed)
+const unmatchedRandom = createDeterministicRandom(unmatchedSeed)
+let redirectCounter = 0
+let notFoundCounter = 0
+let errorCounter = 0
+let unmatchedCounter = 0
+
+const requestInit = {
+ method: 'GET',
+ headers: {
+ accept: 'text/html',
+ },
+} satisfies RequestInit
+
+function buildRedirectRequest(random: () => number) {
+ const id = `${(redirectCounter++).toString(36)}-${randomSegment(random)}`
+
+ return new Request(`http://localhost/from/${id}`, requestInit)
+}
+
+function buildNotFoundRequest(random: () => number) {
+ const id = `${(notFoundCounter++).toString(36)}-${randomSegment(random)}`
+
+ return new Request(`http://localhost/missing/${id}`, requestInit)
+}
+
+function buildErrorRequest(random: () => number) {
+ const id = `${(errorCounter++).toString(36)}-${randomSegment(random)}`
+
+ return new Request(`http://localhost/boom/${id}`, requestInit)
+}
+
+function buildUnmatchedRequest(random: () => number) {
+ const id = `${(unmatchedCounter++).toString(36)}-${randomSegment(random)}`
+
+ return new Request(`http://localhost/nope/${id}`, requestInit)
+}
+
+function getRequestId(request: Request) {
+ const id = new URL(request.url).pathname.split('/').pop()
+
+ if (!id) {
+ throw new Error(`Expected request id in ${request.url}`)
+ }
+
+ return id
+}
+
+function validateRedirectResponse(response: Response, request: Request) {
+ if (response.status !== redirectStatus) {
+ throw new Error(
+ `Expected status ${redirectStatus} for ${request.url}, got ${response.status}`,
+ )
+ }
+
+ const id = getRequestId(request)
+ const location = response.headers.get('location')
+
+ if (location !== `/target/${id}`) {
+ throw new Error(`Expected redirect location /target/${id}, got ${location}`)
+ }
+}
+
+function validateNotFoundResponse(response: Response, request: Request) {
+ if (response.status !== notFoundStatus) {
+ throw new Error(
+ `Expected status ${notFoundStatus} for ${request.url}, got ${response.status}`,
+ )
+ }
+}
+
+function validateErrorResponse(response: Response, request: Request) {
+ if (response.status !== errorStatus) {
+ throw new Error(
+ `Expected status ${errorStatus} for ${request.url}, got ${response.status}`,
+ )
+ }
+}
+
+async function assertStatusSanity(
+ handler: StartRequestHandler,
+ request: Request,
+ validateResponse: (response: Response, request: Request) => void,
+) {
+ const response = await handler.fetch(request)
+ validateResponse(response, request)
+ await response.text()
+}
+
+async function assertErrorPathsSanity(handler: StartRequestHandler) {
+ await assertStatusSanity(
+ handler,
+ new Request('http://localhost/from/sanity-redirect', requestInit),
+ validateRedirectResponse,
+ )
+ await assertStatusSanity(
+ handler,
+ new Request('http://localhost/missing/sanity-missing', requestInit),
+ validateNotFoundResponse,
+ )
+ await assertStatusSanity(
+ handler,
+ new Request('http://localhost/boom/sanity-error', requestInit),
+ validateErrorResponse,
+ )
+ await assertStatusSanity(
+ handler,
+ new Request('http://localhost/nope/sanity-unmatched', requestInit),
+ validateNotFoundResponse,
+ )
+}
+
+export function createWorkloadGroup(
+ framework: Framework,
+ handler: StartRequestHandler,
+) {
+ const runRedirect = () =>
+ runSequentialRequestLoop(handler, {
+ random: redirectRandom,
+ iterations: errorPathsIterations,
+ buildRequest: buildRedirectRequest,
+ validateResponse: validateRedirectResponse,
+ })
+
+ const runNotFound = () =>
+ runSequentialRequestLoop(handler, {
+ random: notFoundRandom,
+ iterations: errorPathsIterations,
+ buildRequest: buildNotFoundRequest,
+ validateResponse: validateNotFoundResponse,
+ })
+
+ const runError = () =>
+ runSequentialRequestLoop(handler, {
+ random: errorRandom,
+ iterations: errorPathsIterations,
+ buildRequest: buildErrorRequest,
+ validateResponse: validateErrorResponse,
+ })
+
+ const runUnmatched = () =>
+ runSequentialRequestLoop(handler, {
+ random: unmatchedRandom,
+ iterations: errorPathsIterations,
+ buildRequest: buildUnmatchedRequest,
+ validateResponse: validateNotFoundResponse,
+ })
+
+ return {
+ sanity: () => assertErrorPathsSanity(handler),
+ workloads: [
+ {
+ name: `mem error-paths redirect (${framework})`,
+ run: runRedirect,
+ },
+ {
+ name: `mem error-paths not-found (${framework})`,
+ run: runNotFound,
+ },
+ {
+ name: `mem error-paths error (${framework})`,
+ run: runError,
+ },
+ {
+ name: `mem error-paths unmatched (${framework})`,
+ run: runUnmatched,
+ },
+ ],
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/memory.bench.ts b/benchmarks/memory/server/scenarios/error-paths/solid/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/memory.flame.ts b/benchmarks/memory/server/scenarios/error-paths/solid/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/project.json b/benchmarks/memory/server/scenarios/error-paths/solid/project.json
new file mode 100644
index 0000000000..77fcfaf578
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-error-paths-solid",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/setup.ts b/benchmarks/memory/server/scenarios/error-paths/solid/setup.ts
new file mode 100644
index 0000000000..4fe85c3f00
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('solid', handler)
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/error-paths/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..32a4d56d3a
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/src/routeTree.gen.ts
@@ -0,0 +1,146 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as TargetIdRouteImport } from './routes/target.$id'
+import { Route as MissingIdRouteImport } from './routes/missing.$id'
+import { Route as FromIdRouteImport } from './routes/from.$id'
+import { Route as BoomIdRouteImport } from './routes/boom.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const TargetIdRoute = TargetIdRouteImport.update({
+ id: '/target/$id',
+ path: '/target/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const MissingIdRoute = MissingIdRouteImport.update({
+ id: '/missing/$id',
+ path: '/missing/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FromIdRoute = FromIdRouteImport.update({
+ id: '/from/$id',
+ path: '/from/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const BoomIdRoute = BoomIdRouteImport.update({
+ id: '/boom/$id',
+ path: '/boom/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/boom/$id' | '/from/$id' | '/missing/$id' | '/target/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/boom/$id' | '/from/$id' | '/missing/$id' | '/target/$id'
+ id:
+ | '__root__'
+ | '/'
+ | '/boom/$id'
+ | '/from/$id'
+ | '/missing/$id'
+ | '/target/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ BoomIdRoute: typeof BoomIdRoute
+ FromIdRoute: typeof FromIdRoute
+ MissingIdRoute: typeof MissingIdRoute
+ TargetIdRoute: typeof TargetIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/target/$id': {
+ id: '/target/$id'
+ path: '/target/$id'
+ fullPath: '/target/$id'
+ preLoaderRoute: typeof TargetIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/missing/$id': {
+ id: '/missing/$id'
+ path: '/missing/$id'
+ fullPath: '/missing/$id'
+ preLoaderRoute: typeof MissingIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/from/$id': {
+ id: '/from/$id'
+ path: '/from/$id'
+ fullPath: '/from/$id'
+ preLoaderRoute: typeof FromIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/boom/$id': {
+ id: '/boom/$id'
+ path: '/boom/$id'
+ fullPath: '/boom/$id'
+ preLoaderRoute: typeof BoomIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ BoomIdRoute: BoomIdRoute,
+ FromIdRoute: FromIdRoute,
+ MissingIdRoute: MissingIdRoute,
+ TargetIdRoute: TargetIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/src/router.tsx b/benchmarks/memory/server/scenarios/error-paths/solid/src/router.tsx
new file mode 100644
index 0000000000..ae51f8b5e9
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/src/router.tsx
@@ -0,0 +1,23 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ defaultNotFoundComponent: DefaultNotFound,
+ })
+}
+
+// Without this, every unmatched-URL request logs a router warning in
+// non-production ad-hoc runs.
+function DefaultNotFound() {
+ return error-paths-unmatched
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..aaf7dfdd89
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { HydrationScript } from 'solid-js/web'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charset: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/boom.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/boom.$id.tsx
new file mode 100644
index 0000000000..bebc638caa
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/boom.$id.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/boom/$id')({
+ loader: ({ params }) => {
+ throw new Error(`boom-${params.id}`)
+ },
+ errorComponent: BoomErrorComponent,
+ component: BoomComponent,
+})
+
+function BoomErrorComponent() {
+ return error-paths-error-boundary
+}
+
+function BoomComponent() {
+ return null
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/from.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/from.$id.tsx
new file mode 100644
index 0000000000..e96b7864f2
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/from.$id.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute, redirect } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/from/$id')({
+ loader: ({ params }) => {
+ const { id } = params
+
+ throw redirect({ to: '/target/$id', params: { id }, statusCode: 302 })
+ },
+ component: FromComponent,
+})
+
+function FromComponent() {
+ return null
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/index.tsx b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..43282af801
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return error-paths-index
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/missing.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/missing.$id.tsx
new file mode 100644
index 0000000000..1b9ec5e29d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/missing.$id.tsx
@@ -0,0 +1,19 @@
+import { createFileRoute, notFound } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/missing/$id')({
+ loader: () => {
+ throw notFound()
+ },
+ notFoundComponent: MissingNotFoundComponent,
+ component: MissingComponent,
+})
+
+function MissingNotFoundComponent() {
+ return (
+ error-paths-not-found-boundary
+ )
+}
+
+function MissingComponent() {
+ return null
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/target.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/target.$id.tsx
new file mode 100644
index 0000000000..4196fc1183
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/src/routes/target.$id.tsx
@@ -0,0 +1,11 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/target/$id')({
+ component: TargetComponent,
+})
+
+function TargetComponent() {
+ const params = Route.useParams()
+
+ return {`target-${params().id}`}
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/tsconfig.json b/benchmarks/memory/server/scenarios/error-paths/solid/tsconfig.json
new file mode 100644
index 0000000000..4b61264e11
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/solid/vite.config.ts b/benchmarks/memory/server/scenarios/error-paths/solid/vite.config.ts
new file mode 100644
index 0000000000..7f89abb099
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ solid({ ssr: true, hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server error-paths (solid)',
+ watch: false,
+ environment: 'node',
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/memory.bench.ts b/benchmarks/memory/server/scenarios/error-paths/vue/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/memory.flame.ts b/benchmarks/memory/server/scenarios/error-paths/vue/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/project.json b/benchmarks/memory/server/scenarios/error-paths/vue/project.json
new file mode 100644
index 0000000000..8472470bde
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-error-paths-vue",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/setup.ts b/benchmarks/memory/server/scenarios/error-paths/vue/setup.ts
new file mode 100644
index 0000000000..6683dafe6b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('vue', handler)
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/error-paths/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..ea5bafa386
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/src/routeTree.gen.ts
@@ -0,0 +1,146 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as TargetIdRouteImport } from './routes/target.$id'
+import { Route as MissingIdRouteImport } from './routes/missing.$id'
+import { Route as FromIdRouteImport } from './routes/from.$id'
+import { Route as BoomIdRouteImport } from './routes/boom.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const TargetIdRoute = TargetIdRouteImport.update({
+ id: '/target/$id',
+ path: '/target/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const MissingIdRoute = MissingIdRouteImport.update({
+ id: '/missing/$id',
+ path: '/missing/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FromIdRoute = FromIdRouteImport.update({
+ id: '/from/$id',
+ path: '/from/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const BoomIdRoute = BoomIdRouteImport.update({
+ id: '/boom/$id',
+ path: '/boom/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/boom/$id': typeof BoomIdRoute
+ '/from/$id': typeof FromIdRoute
+ '/missing/$id': typeof MissingIdRoute
+ '/target/$id': typeof TargetIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/boom/$id' | '/from/$id' | '/missing/$id' | '/target/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/boom/$id' | '/from/$id' | '/missing/$id' | '/target/$id'
+ id:
+ | '__root__'
+ | '/'
+ | '/boom/$id'
+ | '/from/$id'
+ | '/missing/$id'
+ | '/target/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ BoomIdRoute: typeof BoomIdRoute
+ FromIdRoute: typeof FromIdRoute
+ MissingIdRoute: typeof MissingIdRoute
+ TargetIdRoute: typeof TargetIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/target/$id': {
+ id: '/target/$id'
+ path: '/target/$id'
+ fullPath: '/target/$id'
+ preLoaderRoute: typeof TargetIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/missing/$id': {
+ id: '/missing/$id'
+ path: '/missing/$id'
+ fullPath: '/missing/$id'
+ preLoaderRoute: typeof MissingIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/from/$id': {
+ id: '/from/$id'
+ path: '/from/$id'
+ fullPath: '/from/$id'
+ preLoaderRoute: typeof FromIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/boom/$id': {
+ id: '/boom/$id'
+ path: '/boom/$id'
+ fullPath: '/boom/$id'
+ preLoaderRoute: typeof BoomIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ BoomIdRoute: BoomIdRoute,
+ FromIdRoute: FromIdRoute,
+ MissingIdRoute: MissingIdRoute,
+ TargetIdRoute: TargetIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/vue-start'
+declare module '@tanstack/vue-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/src/router.tsx b/benchmarks/memory/server/scenarios/error-paths/vue/src/router.tsx
new file mode 100644
index 0000000000..4290e7cdd3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..de29ee1612
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ Body,
+ HeadContent,
+ Html,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/boom.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/boom.$id.tsx
new file mode 100644
index 0000000000..d933b93df2
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/boom.$id.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/boom/$id')({
+ loader: ({ params }) => {
+ throw new Error(`boom-${params.id}`)
+ },
+ errorComponent: BoomErrorComponent,
+ component: BoomComponent,
+})
+
+function BoomErrorComponent() {
+ return error-paths-error-boundary
+}
+
+function BoomComponent() {
+ return <>>
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/from.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/from.$id.tsx
new file mode 100644
index 0000000000..6ffba96bce
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/from.$id.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute, redirect } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/from/$id')({
+ loader: ({ params }) => {
+ const { id } = params
+
+ throw redirect({ to: '/target/$id', params: { id }, statusCode: 302 })
+ },
+ component: FromComponent,
+})
+
+function FromComponent() {
+ return <>>
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/index.tsx b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..53fb604c60
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return error-paths-index
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/missing.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/missing.$id.tsx
new file mode 100644
index 0000000000..0e4972f5c6
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/missing.$id.tsx
@@ -0,0 +1,19 @@
+import { createFileRoute, notFound } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/missing/$id')({
+ loader: () => {
+ throw notFound()
+ },
+ notFoundComponent: MissingNotFoundComponent,
+ component: MissingComponent,
+})
+
+function MissingNotFoundComponent() {
+ return (
+ error-paths-not-found-boundary
+ )
+}
+
+function MissingComponent() {
+ return <>>
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/target.$id.tsx b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/target.$id.tsx
new file mode 100644
index 0000000000..17ade6bf5b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/src/routes/target.$id.tsx
@@ -0,0 +1,11 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/target/$id')({
+ component: TargetComponent,
+})
+
+function TargetComponent() {
+ const params = Route.useParams()
+
+ return {`target-${params.value.id}`}
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/tsconfig.json b/benchmarks/memory/server/scenarios/error-paths/vue/tsconfig.json
new file mode 100644
index 0000000000..9ad6481342
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/error-paths/vue/vite.config.ts b/benchmarks/memory/server/scenarios/error-paths/vue/vite.config.ts
new file mode 100644
index 0000000000..b5cf79924a
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/error-paths/vue/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server error-paths (vue)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/large-page-data.ts b/benchmarks/memory/server/scenarios/peak-large-page/large-page-data.ts
new file mode 100644
index 0000000000..8f77c58731
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/large-page-data.ts
@@ -0,0 +1,111 @@
+export interface LargePageRecord {
+ id: string
+ name: string
+ description: string
+}
+
+export interface LargePageLevelData {
+ level: number
+ marker: string
+ records: Array
+}
+
+const recordCount = 200
+const descriptionLength = 104
+
+export function makeLargePageLevelData(level: number, seed: number) {
+ const random = createDeterministicRandom(seed)
+
+ return {
+ level,
+ marker: makeLargePageMarker(level),
+ records: Array.from({ length: recordCount }, (_, index) => {
+ const idToken = randomSegment(random)
+ const descriptionTokenA = randomSegment(random)
+ const descriptionTokenB = randomSegment(random)
+
+ return {
+ id: `l${level}-${index}-${idToken}`,
+ name: makeLargePageRecordName(level, index),
+ description: makeDescription(
+ level,
+ index,
+ descriptionTokenA,
+ descriptionTokenB,
+ ),
+ }
+ }),
+ } satisfies LargePageLevelData
+}
+
+export function makeLargePageHead(loaderData: LargePageLevelData | undefined) {
+ if (!loaderData) {
+ return {
+ meta: [{ title: 'Peak Large Page' }],
+ }
+ }
+
+ const first = loaderData.records[0]!
+ const last = loaderData.records[loaderData.records.length - 1]!
+
+ return {
+ meta: [
+ { title: `Peak Large Page L${loaderData.level} ${first.name}` },
+ {
+ name: `peak-large-page-l${loaderData.level}-count`,
+ content: String(loaderData.records.length),
+ },
+ {
+ name: `peak-large-page-l${loaderData.level}-first-id`,
+ content: first.id,
+ },
+ {
+ name: `peak-large-page-l${loaderData.level}-first-name`,
+ content: first.name,
+ },
+ {
+ name: `peak-large-page-l${loaderData.level}-last-id`,
+ content: last.id,
+ },
+ {
+ name: `peak-large-page-l${loaderData.level}-description`,
+ content: first.description,
+ },
+ ],
+ }
+}
+
+function makeLargePageRecordName(level: number, index: number) {
+ return `peak-large-page-l${level}-record-${index}`
+}
+
+function makeLargePageMarker(level: number) {
+ return `peak-large-page-level-${level}`
+}
+
+function makeDescription(
+ level: number,
+ index: number,
+ tokenA: string,
+ tokenB: string,
+) {
+ return `Level ${level} record ${index} uses seeded fragments ${tokenA} and ${tokenB} to keep a fresh deterministic loader payload.`.padEnd(
+ descriptionLength,
+ 'x',
+ )
+}
+
+// Local copy of the LCG from benchmarks/memory/server/bench-utils.ts - app
+// source cannot import from outside the app root. Keep in sync.
+function createDeterministicRandom(seed: number) {
+ let state = seed >>> 0
+
+ return () => {
+ state = (state * 1664525 + 1013904223) >>> 0
+ return state / 0x100000000
+ }
+}
+
+function randomSegment(random: () => number) {
+ return Math.floor(random() * 1_000_000_000).toString(36)
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/memory.bench.ts b/benchmarks/memory/server/scenarios/peak-large-page/react/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/memory.flame.ts b/benchmarks/memory/server/scenarios/peak-large-page/react/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/project.json b/benchmarks/memory/server/scenarios/peak-large-page/react/project.json
new file mode 100644
index 0000000000..699a243a03
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-peak-large-page-react",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/setup.ts b/benchmarks/memory/server/scenarios/peak-large-page/react/setup.ts
new file mode 100644
index 0000000000..0c98f54ddd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('react', handler)
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..60a1bd1fbc
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routeTree.gen.ts
@@ -0,0 +1,305 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as L1RouteImport } from './routes/l1'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as L1L2RouteImport } from './routes/l1.l2'
+import { Route as L1L2L3RouteImport } from './routes/l1.l2.l3'
+import { Route as L1L2L3L4RouteImport } from './routes/l1.l2.l3.l4'
+import { Route as L1L2L3L4L5RouteImport } from './routes/l1.l2.l3.l4.l5'
+import { Route as L1L2L3L4L5L6RouteImport } from './routes/l1.l2.l3.l4.l5.l6'
+import { Route as L1L2L3L4L5L6L7RouteImport } from './routes/l1.l2.l3.l4.l5.l6.l7'
+import { Route as L1L2L3L4L5L6L7L8RouteImport } from './routes/l1.l2.l3.l4.l5.l6.l7.l8'
+
+const L1Route = L1RouteImport.update({
+ id: '/l1',
+ path: '/l1',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const L1L2Route = L1L2RouteImport.update({
+ id: '/l2',
+ path: '/l2',
+ getParentRoute: () => L1Route,
+} as any)
+const L1L2L3Route = L1L2L3RouteImport.update({
+ id: '/l3',
+ path: '/l3',
+ getParentRoute: () => L1L2Route,
+} as any)
+const L1L2L3L4Route = L1L2L3L4RouteImport.update({
+ id: '/l4',
+ path: '/l4',
+ getParentRoute: () => L1L2L3Route,
+} as any)
+const L1L2L3L4L5Route = L1L2L3L4L5RouteImport.update({
+ id: '/l5',
+ path: '/l5',
+ getParentRoute: () => L1L2L3L4Route,
+} as any)
+const L1L2L3L4L5L6Route = L1L2L3L4L5L6RouteImport.update({
+ id: '/l6',
+ path: '/l6',
+ getParentRoute: () => L1L2L3L4L5Route,
+} as any)
+const L1L2L3L4L5L6L7Route = L1L2L3L4L5L6L7RouteImport.update({
+ id: '/l7',
+ path: '/l7',
+ getParentRoute: () => L1L2L3L4L5L6Route,
+} as any)
+const L1L2L3L4L5L6L7L8Route = L1L2L3L4L5L6L7L8RouteImport.update({
+ id: '/l8',
+ path: '/l8',
+ getParentRoute: () => L1L2L3L4L5L6L7Route,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ fileRoutesByTo: FileRoutesByTo
+ to:
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ id:
+ | '__root__'
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ L1Route: typeof L1RouteWithChildren
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/l1': {
+ id: '/l1'
+ path: '/l1'
+ fullPath: '/l1'
+ preLoaderRoute: typeof L1RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/l1/l2': {
+ id: '/l1/l2'
+ path: '/l2'
+ fullPath: '/l1/l2'
+ preLoaderRoute: typeof L1L2RouteImport
+ parentRoute: typeof L1Route
+ }
+ '/l1/l2/l3': {
+ id: '/l1/l2/l3'
+ path: '/l3'
+ fullPath: '/l1/l2/l3'
+ preLoaderRoute: typeof L1L2L3RouteImport
+ parentRoute: typeof L1L2Route
+ }
+ '/l1/l2/l3/l4': {
+ id: '/l1/l2/l3/l4'
+ path: '/l4'
+ fullPath: '/l1/l2/l3/l4'
+ preLoaderRoute: typeof L1L2L3L4RouteImport
+ parentRoute: typeof L1L2L3Route
+ }
+ '/l1/l2/l3/l4/l5': {
+ id: '/l1/l2/l3/l4/l5'
+ path: '/l5'
+ fullPath: '/l1/l2/l3/l4/l5'
+ preLoaderRoute: typeof L1L2L3L4L5RouteImport
+ parentRoute: typeof L1L2L3L4Route
+ }
+ '/l1/l2/l3/l4/l5/l6': {
+ id: '/l1/l2/l3/l4/l5/l6'
+ path: '/l6'
+ fullPath: '/l1/l2/l3/l4/l5/l6'
+ preLoaderRoute: typeof L1L2L3L4L5L6RouteImport
+ parentRoute: typeof L1L2L3L4L5Route
+ }
+ '/l1/l2/l3/l4/l5/l6/l7': {
+ id: '/l1/l2/l3/l4/l5/l6/l7'
+ path: '/l7'
+ fullPath: '/l1/l2/l3/l4/l5/l6/l7'
+ preLoaderRoute: typeof L1L2L3L4L5L6L7RouteImport
+ parentRoute: typeof L1L2L3L4L5L6Route
+ }
+ '/l1/l2/l3/l4/l5/l6/l7/l8': {
+ id: '/l1/l2/l3/l4/l5/l6/l7/l8'
+ path: '/l8'
+ fullPath: '/l1/l2/l3/l4/l5/l6/l7/l8'
+ preLoaderRoute: typeof L1L2L3L4L5L6L7L8RouteImport
+ parentRoute: typeof L1L2L3L4L5L6L7Route
+ }
+ }
+}
+
+interface L1L2L3L4L5L6L7RouteChildren {
+ L1L2L3L4L5L6L7L8Route: typeof L1L2L3L4L5L6L7L8Route
+}
+
+const L1L2L3L4L5L6L7RouteChildren: L1L2L3L4L5L6L7RouteChildren = {
+ L1L2L3L4L5L6L7L8Route: L1L2L3L4L5L6L7L8Route,
+}
+
+const L1L2L3L4L5L6L7RouteWithChildren = L1L2L3L4L5L6L7Route._addFileChildren(
+ L1L2L3L4L5L6L7RouteChildren,
+)
+
+interface L1L2L3L4L5L6RouteChildren {
+ L1L2L3L4L5L6L7Route: typeof L1L2L3L4L5L6L7RouteWithChildren
+}
+
+const L1L2L3L4L5L6RouteChildren: L1L2L3L4L5L6RouteChildren = {
+ L1L2L3L4L5L6L7Route: L1L2L3L4L5L6L7RouteWithChildren,
+}
+
+const L1L2L3L4L5L6RouteWithChildren = L1L2L3L4L5L6Route._addFileChildren(
+ L1L2L3L4L5L6RouteChildren,
+)
+
+interface L1L2L3L4L5RouteChildren {
+ L1L2L3L4L5L6Route: typeof L1L2L3L4L5L6RouteWithChildren
+}
+
+const L1L2L3L4L5RouteChildren: L1L2L3L4L5RouteChildren = {
+ L1L2L3L4L5L6Route: L1L2L3L4L5L6RouteWithChildren,
+}
+
+const L1L2L3L4L5RouteWithChildren = L1L2L3L4L5Route._addFileChildren(
+ L1L2L3L4L5RouteChildren,
+)
+
+interface L1L2L3L4RouteChildren {
+ L1L2L3L4L5Route: typeof L1L2L3L4L5RouteWithChildren
+}
+
+const L1L2L3L4RouteChildren: L1L2L3L4RouteChildren = {
+ L1L2L3L4L5Route: L1L2L3L4L5RouteWithChildren,
+}
+
+const L1L2L3L4RouteWithChildren = L1L2L3L4Route._addFileChildren(
+ L1L2L3L4RouteChildren,
+)
+
+interface L1L2L3RouteChildren {
+ L1L2L3L4Route: typeof L1L2L3L4RouteWithChildren
+}
+
+const L1L2L3RouteChildren: L1L2L3RouteChildren = {
+ L1L2L3L4Route: L1L2L3L4RouteWithChildren,
+}
+
+const L1L2L3RouteWithChildren =
+ L1L2L3Route._addFileChildren(L1L2L3RouteChildren)
+
+interface L1L2RouteChildren {
+ L1L2L3Route: typeof L1L2L3RouteWithChildren
+}
+
+const L1L2RouteChildren: L1L2RouteChildren = {
+ L1L2L3Route: L1L2L3RouteWithChildren,
+}
+
+const L1L2RouteWithChildren = L1L2Route._addFileChildren(L1L2RouteChildren)
+
+interface L1RouteChildren {
+ L1L2Route: typeof L1L2RouteWithChildren
+}
+
+const L1RouteChildren: L1RouteChildren = {
+ L1L2Route: L1L2RouteWithChildren,
+}
+
+const L1RouteWithChildren = L1Route._addFileChildren(L1RouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ L1Route: L1RouteWithChildren,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/router.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/router.tsx
new file mode 100644
index 0000000000..7c4eb0babe
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..c5f9de6922
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/__root.tsx
@@ -0,0 +1,27 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/index.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/index.tsx
new file mode 100644
index 0000000000..25099720e4
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return peak-large-page-index
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
new file mode 100644
index 0000000000..0651ba886d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
@@ -0,0 +1,28 @@
+import { createFileRoute } from '@tanstack/react-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6/l7/l8')({
+ loader: () => makeLargePageLevelData(8, 0x5eed_1008),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelEightComponent,
+})
+
+function LevelEightComponent() {
+ const data = Route.useLoaderData()
+ const first = data.records[0]!
+
+ return (
+
+ {data.marker}
+ records: {data.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
new file mode 100644
index 0000000000..1f89e6f454
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6/l7')({
+ loader: () => makeLargePageLevelData(7, 0x5eed_1007),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelSevenComponent,
+})
+
+function LevelSevenComponent() {
+ const data = Route.useLoaderData()
+ const first = data.records[0]!
+
+ return (
+
+ {data.marker}
+ records: {data.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.tsx
new file mode 100644
index 0000000000..1a8c98ceb2
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6')({
+ loader: () => makeLargePageLevelData(6, 0x5eed_1006),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelSixComponent,
+})
+
+function LevelSixComponent() {
+ const data = Route.useLoaderData()
+ const first = data.records[0]!
+
+ return (
+
+ {data.marker}
+ records: {data.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.tsx
new file mode 100644
index 0000000000..3e9afad0b9
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5')({
+ loader: () => makeLargePageLevelData(5, 0x5eed_1005),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelFiveComponent,
+})
+
+function LevelFiveComponent() {
+ const data = Route.useLoaderData()
+ const first = data.records[0]!
+
+ return (
+
+ {data.marker}
+ records: {data.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.tsx
new file mode 100644
index 0000000000..73c29bc402
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4')({
+ loader: () => makeLargePageLevelData(4, 0x5eed_1004),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelFourComponent,
+})
+
+function LevelFourComponent() {
+ const data = Route.useLoaderData()
+ const first = data.records[0]!
+
+ return (
+
+ {data.marker}
+ records: {data.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.tsx
new file mode 100644
index 0000000000..e5b57463ad
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3')({
+ loader: () => makeLargePageLevelData(3, 0x5eed_1003),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelThreeComponent,
+})
+
+function LevelThreeComponent() {
+ const data = Route.useLoaderData()
+ const first = data.records[0]!
+
+ return (
+
+ {data.marker}
+ records: {data.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.tsx
new file mode 100644
index 0000000000..18999233cc
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2')({
+ loader: () => makeLargePageLevelData(2, 0x5eed_1002),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelTwoComponent,
+})
+
+function LevelTwoComponent() {
+ const data = Route.useLoaderData()
+ const first = data.records[0]!
+
+ return (
+
+ {data.marker}
+ records: {data.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.tsx b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.tsx
new file mode 100644
index 0000000000..8fa7a5e0dc
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1')({
+ loader: () => makeLargePageLevelData(1, 0x5eed_1001),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelOneComponent,
+})
+
+function LevelOneComponent() {
+ const data = Route.useLoaderData()
+ const first = data.records[0]!
+
+ return (
+
+ {data.marker}
+ records: {data.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/tsconfig.json b/benchmarks/memory/server/scenarios/peak-large-page/react/tsconfig.json
new file mode 100644
index 0000000000..11ddcce4ea
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts b/benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts
new file mode 100644
index 0000000000..7d4fa70196
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server peak-large-page (react)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/shared.ts b/benchmarks/memory/server/scenarios/peak-large-page/shared.ts
new file mode 100644
index 0000000000..36a91fa49d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/shared.ts
@@ -0,0 +1,68 @@
+import { runSequentialRequestLoop } from '#memory-server/bench-utils'
+import type { StartRequestHandler } from '#memory-server/bench-utils'
+
+export type { StartRequestHandler }
+
+type Framework = 'react' | 'solid' | 'vue'
+
+const benchmarkSeed = 0x5eed_0005
+const peakLargePageIterations = 20
+const peakLargePageUrl = 'http://localhost/l1/l2/l3/l4/l5/l6/l7/l8'
+const levelEightMarker = 'data-bench="peak-large-page-level-8"'
+
+const requestInit = {
+ method: 'GET',
+ headers: {
+ accept: 'text/html',
+ },
+} satisfies RequestInit
+
+function buildPeakLargePageRequest() {
+ return new Request(peakLargePageUrl, requestInit)
+}
+
+function validatePeakLargePageResponse(response: Response, request: Request) {
+ if (response.status !== 200) {
+ throw new Error(
+ `Expected status 200 for ${request.url}, got ${response.status}`,
+ )
+ }
+}
+
+function validatePeakLargePageBody(body: string) {
+ if (!body.includes(levelEightMarker)) {
+ throw new Error('Expected peak-large-page level-8 marker in response body')
+ }
+}
+
+async function assertPeakLargePageSanity(handler: StartRequestHandler) {
+ const request = new Request(peakLargePageUrl, requestInit)
+ const response = await handler.fetch(request)
+ const body = await response.text()
+
+ validatePeakLargePageResponse(response, request)
+ validatePeakLargePageBody(body)
+}
+
+export function createWorkloadGroup(
+ framework: Framework,
+ handler: StartRequestHandler,
+) {
+ const run = () =>
+ runSequentialRequestLoop(handler, {
+ seed: benchmarkSeed,
+ iterations: peakLargePageIterations,
+ buildRequest: buildPeakLargePageRequest,
+ validateResponse: validatePeakLargePageResponse,
+ })
+
+ return {
+ sanity: () => assertPeakLargePageSanity(handler),
+ workloads: [
+ {
+ name: `mem peak-large-page (${framework})`,
+ run,
+ },
+ ],
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/memory.bench.ts b/benchmarks/memory/server/scenarios/peak-large-page/solid/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/memory.flame.ts b/benchmarks/memory/server/scenarios/peak-large-page/solid/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/project.json b/benchmarks/memory/server/scenarios/peak-large-page/solid/project.json
new file mode 100644
index 0000000000..85e8f83224
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-peak-large-page-solid",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/setup.ts b/benchmarks/memory/server/scenarios/peak-large-page/solid/setup.ts
new file mode 100644
index 0000000000..4fe85c3f00
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('solid', handler)
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..c3ccdaaa0e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routeTree.gen.ts
@@ -0,0 +1,305 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as L1RouteImport } from './routes/l1'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as L1L2RouteImport } from './routes/l1.l2'
+import { Route as L1L2L3RouteImport } from './routes/l1.l2.l3'
+import { Route as L1L2L3L4RouteImport } from './routes/l1.l2.l3.l4'
+import { Route as L1L2L3L4L5RouteImport } from './routes/l1.l2.l3.l4.l5'
+import { Route as L1L2L3L4L5L6RouteImport } from './routes/l1.l2.l3.l4.l5.l6'
+import { Route as L1L2L3L4L5L6L7RouteImport } from './routes/l1.l2.l3.l4.l5.l6.l7'
+import { Route as L1L2L3L4L5L6L7L8RouteImport } from './routes/l1.l2.l3.l4.l5.l6.l7.l8'
+
+const L1Route = L1RouteImport.update({
+ id: '/l1',
+ path: '/l1',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const L1L2Route = L1L2RouteImport.update({
+ id: '/l2',
+ path: '/l2',
+ getParentRoute: () => L1Route,
+} as any)
+const L1L2L3Route = L1L2L3RouteImport.update({
+ id: '/l3',
+ path: '/l3',
+ getParentRoute: () => L1L2Route,
+} as any)
+const L1L2L3L4Route = L1L2L3L4RouteImport.update({
+ id: '/l4',
+ path: '/l4',
+ getParentRoute: () => L1L2L3Route,
+} as any)
+const L1L2L3L4L5Route = L1L2L3L4L5RouteImport.update({
+ id: '/l5',
+ path: '/l5',
+ getParentRoute: () => L1L2L3L4Route,
+} as any)
+const L1L2L3L4L5L6Route = L1L2L3L4L5L6RouteImport.update({
+ id: '/l6',
+ path: '/l6',
+ getParentRoute: () => L1L2L3L4L5Route,
+} as any)
+const L1L2L3L4L5L6L7Route = L1L2L3L4L5L6L7RouteImport.update({
+ id: '/l7',
+ path: '/l7',
+ getParentRoute: () => L1L2L3L4L5L6Route,
+} as any)
+const L1L2L3L4L5L6L7L8Route = L1L2L3L4L5L6L7L8RouteImport.update({
+ id: '/l8',
+ path: '/l8',
+ getParentRoute: () => L1L2L3L4L5L6L7Route,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ fileRoutesByTo: FileRoutesByTo
+ to:
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ id:
+ | '__root__'
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ L1Route: typeof L1RouteWithChildren
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/l1': {
+ id: '/l1'
+ path: '/l1'
+ fullPath: '/l1'
+ preLoaderRoute: typeof L1RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/l1/l2': {
+ id: '/l1/l2'
+ path: '/l2'
+ fullPath: '/l1/l2'
+ preLoaderRoute: typeof L1L2RouteImport
+ parentRoute: typeof L1Route
+ }
+ '/l1/l2/l3': {
+ id: '/l1/l2/l3'
+ path: '/l3'
+ fullPath: '/l1/l2/l3'
+ preLoaderRoute: typeof L1L2L3RouteImport
+ parentRoute: typeof L1L2Route
+ }
+ '/l1/l2/l3/l4': {
+ id: '/l1/l2/l3/l4'
+ path: '/l4'
+ fullPath: '/l1/l2/l3/l4'
+ preLoaderRoute: typeof L1L2L3L4RouteImport
+ parentRoute: typeof L1L2L3Route
+ }
+ '/l1/l2/l3/l4/l5': {
+ id: '/l1/l2/l3/l4/l5'
+ path: '/l5'
+ fullPath: '/l1/l2/l3/l4/l5'
+ preLoaderRoute: typeof L1L2L3L4L5RouteImport
+ parentRoute: typeof L1L2L3L4Route
+ }
+ '/l1/l2/l3/l4/l5/l6': {
+ id: '/l1/l2/l3/l4/l5/l6'
+ path: '/l6'
+ fullPath: '/l1/l2/l3/l4/l5/l6'
+ preLoaderRoute: typeof L1L2L3L4L5L6RouteImport
+ parentRoute: typeof L1L2L3L4L5Route
+ }
+ '/l1/l2/l3/l4/l5/l6/l7': {
+ id: '/l1/l2/l3/l4/l5/l6/l7'
+ path: '/l7'
+ fullPath: '/l1/l2/l3/l4/l5/l6/l7'
+ preLoaderRoute: typeof L1L2L3L4L5L6L7RouteImport
+ parentRoute: typeof L1L2L3L4L5L6Route
+ }
+ '/l1/l2/l3/l4/l5/l6/l7/l8': {
+ id: '/l1/l2/l3/l4/l5/l6/l7/l8'
+ path: '/l8'
+ fullPath: '/l1/l2/l3/l4/l5/l6/l7/l8'
+ preLoaderRoute: typeof L1L2L3L4L5L6L7L8RouteImport
+ parentRoute: typeof L1L2L3L4L5L6L7Route
+ }
+ }
+}
+
+interface L1L2L3L4L5L6L7RouteChildren {
+ L1L2L3L4L5L6L7L8Route: typeof L1L2L3L4L5L6L7L8Route
+}
+
+const L1L2L3L4L5L6L7RouteChildren: L1L2L3L4L5L6L7RouteChildren = {
+ L1L2L3L4L5L6L7L8Route: L1L2L3L4L5L6L7L8Route,
+}
+
+const L1L2L3L4L5L6L7RouteWithChildren = L1L2L3L4L5L6L7Route._addFileChildren(
+ L1L2L3L4L5L6L7RouteChildren,
+)
+
+interface L1L2L3L4L5L6RouteChildren {
+ L1L2L3L4L5L6L7Route: typeof L1L2L3L4L5L6L7RouteWithChildren
+}
+
+const L1L2L3L4L5L6RouteChildren: L1L2L3L4L5L6RouteChildren = {
+ L1L2L3L4L5L6L7Route: L1L2L3L4L5L6L7RouteWithChildren,
+}
+
+const L1L2L3L4L5L6RouteWithChildren = L1L2L3L4L5L6Route._addFileChildren(
+ L1L2L3L4L5L6RouteChildren,
+)
+
+interface L1L2L3L4L5RouteChildren {
+ L1L2L3L4L5L6Route: typeof L1L2L3L4L5L6RouteWithChildren
+}
+
+const L1L2L3L4L5RouteChildren: L1L2L3L4L5RouteChildren = {
+ L1L2L3L4L5L6Route: L1L2L3L4L5L6RouteWithChildren,
+}
+
+const L1L2L3L4L5RouteWithChildren = L1L2L3L4L5Route._addFileChildren(
+ L1L2L3L4L5RouteChildren,
+)
+
+interface L1L2L3L4RouteChildren {
+ L1L2L3L4L5Route: typeof L1L2L3L4L5RouteWithChildren
+}
+
+const L1L2L3L4RouteChildren: L1L2L3L4RouteChildren = {
+ L1L2L3L4L5Route: L1L2L3L4L5RouteWithChildren,
+}
+
+const L1L2L3L4RouteWithChildren = L1L2L3L4Route._addFileChildren(
+ L1L2L3L4RouteChildren,
+)
+
+interface L1L2L3RouteChildren {
+ L1L2L3L4Route: typeof L1L2L3L4RouteWithChildren
+}
+
+const L1L2L3RouteChildren: L1L2L3RouteChildren = {
+ L1L2L3L4Route: L1L2L3L4RouteWithChildren,
+}
+
+const L1L2L3RouteWithChildren =
+ L1L2L3Route._addFileChildren(L1L2L3RouteChildren)
+
+interface L1L2RouteChildren {
+ L1L2L3Route: typeof L1L2L3RouteWithChildren
+}
+
+const L1L2RouteChildren: L1L2RouteChildren = {
+ L1L2L3Route: L1L2L3RouteWithChildren,
+}
+
+const L1L2RouteWithChildren = L1L2Route._addFileChildren(L1L2RouteChildren)
+
+interface L1RouteChildren {
+ L1L2Route: typeof L1L2RouteWithChildren
+}
+
+const L1RouteChildren: L1RouteChildren = {
+ L1L2Route: L1L2RouteWithChildren,
+}
+
+const L1RouteWithChildren = L1Route._addFileChildren(L1RouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ L1Route: L1RouteWithChildren,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/router.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/router.tsx
new file mode 100644
index 0000000000..038ec0ab5e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..aaf7dfdd89
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { HydrationScript } from 'solid-js/web'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charset: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/index.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..111ffaa36a
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return peak-large-page-index
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
new file mode 100644
index 0000000000..b0f94893fb
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
@@ -0,0 +1,28 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6/l7/l8')({
+ loader: () => makeLargePageLevelData(8, 0x5eed_1008),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelEightComponent,
+})
+
+function LevelEightComponent() {
+ const data = Route.useLoaderData()
+ const first = () => data().records[0]!
+
+ return (
+
+ {data().marker}
+ records: {data().records.length}
+
+ {first().name}
+ {first().id}
+ {first().description}
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
new file mode 100644
index 0000000000..f8a3819c85
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6/l7')({
+ loader: () => makeLargePageLevelData(7, 0x5eed_1007),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelSevenComponent,
+})
+
+function LevelSevenComponent() {
+ const data = Route.useLoaderData()
+ const first = () => data().records[0]!
+
+ return (
+
+ {data().marker}
+ records: {data().records.length}
+
+ {first().name}
+ {first().id}
+ {first().description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.tsx
new file mode 100644
index 0000000000..7a438ddbd2
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6')({
+ loader: () => makeLargePageLevelData(6, 0x5eed_1006),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelSixComponent,
+})
+
+function LevelSixComponent() {
+ const data = Route.useLoaderData()
+ const first = () => data().records[0]!
+
+ return (
+
+ {data().marker}
+ records: {data().records.length}
+
+ {first().name}
+ {first().id}
+ {first().description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.tsx
new file mode 100644
index 0000000000..2e05985ac5
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5')({
+ loader: () => makeLargePageLevelData(5, 0x5eed_1005),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelFiveComponent,
+})
+
+function LevelFiveComponent() {
+ const data = Route.useLoaderData()
+ const first = () => data().records[0]!
+
+ return (
+
+ {data().marker}
+ records: {data().records.length}
+
+ {first().name}
+ {first().id}
+ {first().description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.tsx
new file mode 100644
index 0000000000..4f38f0cb76
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4')({
+ loader: () => makeLargePageLevelData(4, 0x5eed_1004),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelFourComponent,
+})
+
+function LevelFourComponent() {
+ const data = Route.useLoaderData()
+ const first = () => data().records[0]!
+
+ return (
+
+ {data().marker}
+ records: {data().records.length}
+
+ {first().name}
+ {first().id}
+ {first().description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.tsx
new file mode 100644
index 0000000000..3f1f113fe3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3')({
+ loader: () => makeLargePageLevelData(3, 0x5eed_1003),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelThreeComponent,
+})
+
+function LevelThreeComponent() {
+ const data = Route.useLoaderData()
+ const first = () => data().records[0]!
+
+ return (
+
+ {data().marker}
+ records: {data().records.length}
+
+ {first().name}
+ {first().id}
+ {first().description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.tsx
new file mode 100644
index 0000000000..bd338fd53f
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2')({
+ loader: () => makeLargePageLevelData(2, 0x5eed_1002),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelTwoComponent,
+})
+
+function LevelTwoComponent() {
+ const data = Route.useLoaderData()
+ const first = () => data().records[0]!
+
+ return (
+
+ {data().marker}
+ records: {data().records.length}
+
+ {first().name}
+ {first().id}
+ {first().description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.tsx b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.tsx
new file mode 100644
index 0000000000..9dcfd7448a
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1')({
+ loader: () => makeLargePageLevelData(1, 0x5eed_1001),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelOneComponent,
+})
+
+function LevelOneComponent() {
+ const data = Route.useLoaderData()
+ const first = () => data().records[0]!
+
+ return (
+
+ {data().marker}
+ records: {data().records.length}
+
+ {first().name}
+ {first().id}
+ {first().description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/tsconfig.json b/benchmarks/memory/server/scenarios/peak-large-page/solid/tsconfig.json
new file mode 100644
index 0000000000..4b61264e11
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/solid/vite.config.ts b/benchmarks/memory/server/scenarios/peak-large-page/solid/vite.config.ts
new file mode 100644
index 0000000000..ffc80b43aa
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ solid({ ssr: true, hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server peak-large-page (solid)',
+ watch: false,
+ environment: 'node',
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/memory.bench.ts b/benchmarks/memory/server/scenarios/peak-large-page/vue/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/memory.flame.ts b/benchmarks/memory/server/scenarios/peak-large-page/vue/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/project.json b/benchmarks/memory/server/scenarios/peak-large-page/vue/project.json
new file mode 100644
index 0000000000..f424a2134f
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-peak-large-page-vue",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/setup.ts b/benchmarks/memory/server/scenarios/peak-large-page/vue/setup.ts
new file mode 100644
index 0000000000..6683dafe6b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('vue', handler)
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..26cc9f6058
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routeTree.gen.ts
@@ -0,0 +1,305 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as L1RouteImport } from './routes/l1'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as L1L2RouteImport } from './routes/l1.l2'
+import { Route as L1L2L3RouteImport } from './routes/l1.l2.l3'
+import { Route as L1L2L3L4RouteImport } from './routes/l1.l2.l3.l4'
+import { Route as L1L2L3L4L5RouteImport } from './routes/l1.l2.l3.l4.l5'
+import { Route as L1L2L3L4L5L6RouteImport } from './routes/l1.l2.l3.l4.l5.l6'
+import { Route as L1L2L3L4L5L6L7RouteImport } from './routes/l1.l2.l3.l4.l5.l6.l7'
+import { Route as L1L2L3L4L5L6L7L8RouteImport } from './routes/l1.l2.l3.l4.l5.l6.l7.l8'
+
+const L1Route = L1RouteImport.update({
+ id: '/l1',
+ path: '/l1',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const L1L2Route = L1L2RouteImport.update({
+ id: '/l2',
+ path: '/l2',
+ getParentRoute: () => L1Route,
+} as any)
+const L1L2L3Route = L1L2L3RouteImport.update({
+ id: '/l3',
+ path: '/l3',
+ getParentRoute: () => L1L2Route,
+} as any)
+const L1L2L3L4Route = L1L2L3L4RouteImport.update({
+ id: '/l4',
+ path: '/l4',
+ getParentRoute: () => L1L2L3Route,
+} as any)
+const L1L2L3L4L5Route = L1L2L3L4L5RouteImport.update({
+ id: '/l5',
+ path: '/l5',
+ getParentRoute: () => L1L2L3L4Route,
+} as any)
+const L1L2L3L4L5L6Route = L1L2L3L4L5L6RouteImport.update({
+ id: '/l6',
+ path: '/l6',
+ getParentRoute: () => L1L2L3L4L5Route,
+} as any)
+const L1L2L3L4L5L6L7Route = L1L2L3L4L5L6L7RouteImport.update({
+ id: '/l7',
+ path: '/l7',
+ getParentRoute: () => L1L2L3L4L5L6Route,
+} as any)
+const L1L2L3L4L5L6L7L8Route = L1L2L3L4L5L6L7L8RouteImport.update({
+ id: '/l8',
+ path: '/l8',
+ getParentRoute: () => L1L2L3L4L5L6L7Route,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/l1': typeof L1RouteWithChildren
+ '/l1/l2': typeof L1L2RouteWithChildren
+ '/l1/l2/l3': typeof L1L2L3RouteWithChildren
+ '/l1/l2/l3/l4': typeof L1L2L3L4RouteWithChildren
+ '/l1/l2/l3/l4/l5': typeof L1L2L3L4L5RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6': typeof L1L2L3L4L5L6RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7': typeof L1L2L3L4L5L6L7RouteWithChildren
+ '/l1/l2/l3/l4/l5/l6/l7/l8': typeof L1L2L3L4L5L6L7L8Route
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ fileRoutesByTo: FileRoutesByTo
+ to:
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ id:
+ | '__root__'
+ | '/'
+ | '/l1'
+ | '/l1/l2'
+ | '/l1/l2/l3'
+ | '/l1/l2/l3/l4'
+ | '/l1/l2/l3/l4/l5'
+ | '/l1/l2/l3/l4/l5/l6'
+ | '/l1/l2/l3/l4/l5/l6/l7'
+ | '/l1/l2/l3/l4/l5/l6/l7/l8'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ L1Route: typeof L1RouteWithChildren
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/l1': {
+ id: '/l1'
+ path: '/l1'
+ fullPath: '/l1'
+ preLoaderRoute: typeof L1RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/l1/l2': {
+ id: '/l1/l2'
+ path: '/l2'
+ fullPath: '/l1/l2'
+ preLoaderRoute: typeof L1L2RouteImport
+ parentRoute: typeof L1Route
+ }
+ '/l1/l2/l3': {
+ id: '/l1/l2/l3'
+ path: '/l3'
+ fullPath: '/l1/l2/l3'
+ preLoaderRoute: typeof L1L2L3RouteImport
+ parentRoute: typeof L1L2Route
+ }
+ '/l1/l2/l3/l4': {
+ id: '/l1/l2/l3/l4'
+ path: '/l4'
+ fullPath: '/l1/l2/l3/l4'
+ preLoaderRoute: typeof L1L2L3L4RouteImport
+ parentRoute: typeof L1L2L3Route
+ }
+ '/l1/l2/l3/l4/l5': {
+ id: '/l1/l2/l3/l4/l5'
+ path: '/l5'
+ fullPath: '/l1/l2/l3/l4/l5'
+ preLoaderRoute: typeof L1L2L3L4L5RouteImport
+ parentRoute: typeof L1L2L3L4Route
+ }
+ '/l1/l2/l3/l4/l5/l6': {
+ id: '/l1/l2/l3/l4/l5/l6'
+ path: '/l6'
+ fullPath: '/l1/l2/l3/l4/l5/l6'
+ preLoaderRoute: typeof L1L2L3L4L5L6RouteImport
+ parentRoute: typeof L1L2L3L4L5Route
+ }
+ '/l1/l2/l3/l4/l5/l6/l7': {
+ id: '/l1/l2/l3/l4/l5/l6/l7'
+ path: '/l7'
+ fullPath: '/l1/l2/l3/l4/l5/l6/l7'
+ preLoaderRoute: typeof L1L2L3L4L5L6L7RouteImport
+ parentRoute: typeof L1L2L3L4L5L6Route
+ }
+ '/l1/l2/l3/l4/l5/l6/l7/l8': {
+ id: '/l1/l2/l3/l4/l5/l6/l7/l8'
+ path: '/l8'
+ fullPath: '/l1/l2/l3/l4/l5/l6/l7/l8'
+ preLoaderRoute: typeof L1L2L3L4L5L6L7L8RouteImport
+ parentRoute: typeof L1L2L3L4L5L6L7Route
+ }
+ }
+}
+
+interface L1L2L3L4L5L6L7RouteChildren {
+ L1L2L3L4L5L6L7L8Route: typeof L1L2L3L4L5L6L7L8Route
+}
+
+const L1L2L3L4L5L6L7RouteChildren: L1L2L3L4L5L6L7RouteChildren = {
+ L1L2L3L4L5L6L7L8Route: L1L2L3L4L5L6L7L8Route,
+}
+
+const L1L2L3L4L5L6L7RouteWithChildren = L1L2L3L4L5L6L7Route._addFileChildren(
+ L1L2L3L4L5L6L7RouteChildren,
+)
+
+interface L1L2L3L4L5L6RouteChildren {
+ L1L2L3L4L5L6L7Route: typeof L1L2L3L4L5L6L7RouteWithChildren
+}
+
+const L1L2L3L4L5L6RouteChildren: L1L2L3L4L5L6RouteChildren = {
+ L1L2L3L4L5L6L7Route: L1L2L3L4L5L6L7RouteWithChildren,
+}
+
+const L1L2L3L4L5L6RouteWithChildren = L1L2L3L4L5L6Route._addFileChildren(
+ L1L2L3L4L5L6RouteChildren,
+)
+
+interface L1L2L3L4L5RouteChildren {
+ L1L2L3L4L5L6Route: typeof L1L2L3L4L5L6RouteWithChildren
+}
+
+const L1L2L3L4L5RouteChildren: L1L2L3L4L5RouteChildren = {
+ L1L2L3L4L5L6Route: L1L2L3L4L5L6RouteWithChildren,
+}
+
+const L1L2L3L4L5RouteWithChildren = L1L2L3L4L5Route._addFileChildren(
+ L1L2L3L4L5RouteChildren,
+)
+
+interface L1L2L3L4RouteChildren {
+ L1L2L3L4L5Route: typeof L1L2L3L4L5RouteWithChildren
+}
+
+const L1L2L3L4RouteChildren: L1L2L3L4RouteChildren = {
+ L1L2L3L4L5Route: L1L2L3L4L5RouteWithChildren,
+}
+
+const L1L2L3L4RouteWithChildren = L1L2L3L4Route._addFileChildren(
+ L1L2L3L4RouteChildren,
+)
+
+interface L1L2L3RouteChildren {
+ L1L2L3L4Route: typeof L1L2L3L4RouteWithChildren
+}
+
+const L1L2L3RouteChildren: L1L2L3RouteChildren = {
+ L1L2L3L4Route: L1L2L3L4RouteWithChildren,
+}
+
+const L1L2L3RouteWithChildren =
+ L1L2L3Route._addFileChildren(L1L2L3RouteChildren)
+
+interface L1L2RouteChildren {
+ L1L2L3Route: typeof L1L2L3RouteWithChildren
+}
+
+const L1L2RouteChildren: L1L2RouteChildren = {
+ L1L2L3Route: L1L2L3RouteWithChildren,
+}
+
+const L1L2RouteWithChildren = L1L2Route._addFileChildren(L1L2RouteChildren)
+
+interface L1RouteChildren {
+ L1L2Route: typeof L1L2RouteWithChildren
+}
+
+const L1RouteChildren: L1RouteChildren = {
+ L1L2Route: L1L2RouteWithChildren,
+}
+
+const L1RouteWithChildren = L1Route._addFileChildren(L1RouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ L1Route: L1RouteWithChildren,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/vue-start'
+declare module '@tanstack/vue-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/router.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/router.tsx
new file mode 100644
index 0000000000..4290e7cdd3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..de29ee1612
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ Body,
+ HeadContent,
+ Html,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/index.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..e9a57d46fd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return peak-large-page-index
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
new file mode 100644
index 0000000000..bd079aa1a0
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
@@ -0,0 +1,28 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6/l7/l8')({
+ loader: () => makeLargePageLevelData(8, 0x5eed_1008),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelEightComponent,
+})
+
+function LevelEightComponent() {
+ const data = Route.useLoaderData()
+ const first = data.value.records[0]!
+
+ return (
+
+ {data.value.marker}
+ records: {data.value.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
new file mode 100644
index 0000000000..7f69f6f438
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6/l7')({
+ loader: () => makeLargePageLevelData(7, 0x5eed_1007),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelSevenComponent,
+})
+
+function LevelSevenComponent() {
+ const data = Route.useLoaderData()
+ const first = data.value.records[0]!
+
+ return (
+
+ {data.value.marker}
+ records: {data.value.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.tsx
new file mode 100644
index 0000000000..75a7c717e3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5/l6')({
+ loader: () => makeLargePageLevelData(6, 0x5eed_1006),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelSixComponent,
+})
+
+function LevelSixComponent() {
+ const data = Route.useLoaderData()
+ const first = data.value.records[0]!
+
+ return (
+
+ {data.value.marker}
+ records: {data.value.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.tsx
new file mode 100644
index 0000000000..f390ae3db3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4/l5')({
+ loader: () => makeLargePageLevelData(5, 0x5eed_1005),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelFiveComponent,
+})
+
+function LevelFiveComponent() {
+ const data = Route.useLoaderData()
+ const first = data.value.records[0]!
+
+ return (
+
+ {data.value.marker}
+ records: {data.value.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.tsx
new file mode 100644
index 0000000000..6b30bd50dc
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3/l4')({
+ loader: () => makeLargePageLevelData(4, 0x5eed_1004),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelFourComponent,
+})
+
+function LevelFourComponent() {
+ const data = Route.useLoaderData()
+ const first = data.value.records[0]!
+
+ return (
+
+ {data.value.marker}
+ records: {data.value.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.tsx
new file mode 100644
index 0000000000..6ec6f32fea
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2/l3')({
+ loader: () => makeLargePageLevelData(3, 0x5eed_1003),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelThreeComponent,
+})
+
+function LevelThreeComponent() {
+ const data = Route.useLoaderData()
+ const first = data.value.records[0]!
+
+ return (
+
+ {data.value.marker}
+ records: {data.value.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.tsx
new file mode 100644
index 0000000000..e5b4cf0de1
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1/l2')({
+ loader: () => makeLargePageLevelData(2, 0x5eed_1002),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelTwoComponent,
+})
+
+function LevelTwoComponent() {
+ const data = Route.useLoaderData()
+ const first = data.value.records[0]!
+
+ return (
+
+ {data.value.marker}
+ records: {data.value.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.tsx b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.tsx
new file mode 100644
index 0000000000..a29e465cd4
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.tsx
@@ -0,0 +1,29 @@
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import {
+ makeLargePageHead,
+ makeLargePageLevelData,
+} from '../../../large-page-data'
+
+export const Route = createFileRoute('/l1')({
+ loader: () => makeLargePageLevelData(1, 0x5eed_1001),
+ head: ({ loaderData }) => makeLargePageHead(loaderData),
+ component: LevelOneComponent,
+})
+
+function LevelOneComponent() {
+ const data = Route.useLoaderData()
+ const first = data.value.records[0]!
+
+ return (
+
+ {data.value.marker}
+ records: {data.value.records.length}
+
+ {first.name}
+ {first.id}
+ {first.description}
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/tsconfig.json b/benchmarks/memory/server/scenarios/peak-large-page/vue/tsconfig.json
new file mode 100644
index 0000000000..9ad6481342
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/vue/vite.config.ts b/benchmarks/memory/server/scenarios/peak-large-page/vue/vite.config.ts
new file mode 100644
index 0000000000..67bdf9b79d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/peak-large-page/vue/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server peak-large-page (vue)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/memory.bench.ts b/benchmarks/memory/server/scenarios/request-churn/react/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/memory.flame.ts b/benchmarks/memory/server/scenarios/request-churn/react/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/project.json b/benchmarks/memory/server/scenarios/request-churn/react/project.json
new file mode 100644
index 0000000000..d997054b95
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-request-churn-react",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/setup.ts b/benchmarks/memory/server/scenarios/request-churn/react/setup.ts
new file mode 100644
index 0000000000..0c98f54ddd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('react', handler)
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/request-churn/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..d22f1bee18
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/items/$id'
+ id: '__root__' | '/' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/src/router.tsx b/benchmarks/memory/server/scenarios/request-churn/react/src/router.tsx
new file mode 100644
index 0000000000..7c4eb0babe
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/request-churn/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..c5f9de6922
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/src/routes/__root.tsx
@@ -0,0 +1,27 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/src/routes/index.tsx b/benchmarks/memory/server/scenarios/request-churn/react/src/routes/index.tsx
new file mode 100644
index 0000000000..e6335b0d83
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return request-churn-index
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/src/routes/items.$id.tsx b/benchmarks/memory/server/scenarios/request-churn/react/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..5fd86ee5fb
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/src/routes/items.$id.tsx
@@ -0,0 +1,40 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+const itemIndexes = Array.from({ length: 5 }, (_, index) => index)
+
+type ItemSearch = {
+ q: string
+}
+
+export const Route = createFileRoute('/items/$id')({
+ validateSearch: (search: Record): ItemSearch => ({
+ q: typeof search.q === 'string' ? search.q : '',
+ }),
+ loaderDeps: ({ search }) => ({ q: search.q }),
+ loader: ({ params, deps }) => ({
+ id: params.id,
+ title: `Item ${params.id}`,
+ q: deps.q,
+ items: itemIndexes.map((index) => ({
+ id: `${params.id}-${index}`,
+ label: `${deps.q}-${index}`,
+ })),
+ }),
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {data.title}
+ {data.q}
+
+ {data.items.map((item) => (
+ - {item.label}
+ ))}
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/tsconfig.json b/benchmarks/memory/server/scenarios/request-churn/react/tsconfig.json
new file mode 100644
index 0000000000..11ddcce4ea
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/react/vite.config.ts b/benchmarks/memory/server/scenarios/request-churn/react/vite.config.ts
new file mode 100644
index 0000000000..4b75255eb1
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/react/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server request-churn (react)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/request-churn/shared.ts b/benchmarks/memory/server/scenarios/request-churn/shared.ts
new file mode 100644
index 0000000000..1739e4cc59
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/shared.ts
@@ -0,0 +1,82 @@
+import {
+ createDeterministicRandom,
+ randomSegment,
+ runSequentialRequestLoop,
+} from '#memory-server/bench-utils'
+import type { StartRequestHandler } from '#memory-server/bench-utils'
+
+export type { StartRequestHandler }
+
+type Framework = 'react' | 'solid' | 'vue'
+
+const benchmarkSeed = 0xdecafbad
+const requestChurnIterations = 200
+const itemPageMarker = 'data-bench="request-churn-item"'
+// Module-level so CodSpeed warmups and measurement never replay URLs.
+const benchmarkRandom = createDeterministicRandom(benchmarkSeed)
+let requestCounter = 0
+
+const requestInit = {
+ method: 'GET',
+ headers: {
+ accept: 'text/html',
+ },
+} satisfies RequestInit
+
+function validateItemResponse(response: Response, request: Request) {
+ if (response.status !== 200) {
+ throw new Error(
+ `Expected status 200 for ${request.url}, got ${response.status}`,
+ )
+ }
+}
+
+function validateItemBody(body: string) {
+ if (!body.includes(itemPageMarker)) {
+ throw new Error('Expected request-churn item marker in response body')
+ }
+}
+
+async function assertRequestChurnSanity(handler: StartRequestHandler) {
+ const response = await handler.fetch(
+ new Request('http://localhost/items/sanity-item?q=q-sanity', requestInit),
+ )
+ const body = await response.text()
+
+ if (response.status !== 200) {
+ throw new Error(`Expected sanity status 200, got ${response.status}`)
+ }
+
+ validateItemBody(body)
+}
+
+export function createWorkloadGroup(
+ framework: Framework,
+ handler: StartRequestHandler,
+) {
+ function buildItemRequest(random: () => number) {
+ const counter = (requestCounter++).toString(36)
+ const id = `${counter}-${randomSegment(random)}`
+ const q = `q-${randomSegment(random)}`
+
+ return new Request(`http://localhost/items/${id}?q=${q}`, requestInit)
+ }
+
+ const run = () =>
+ runSequentialRequestLoop(handler, {
+ random: benchmarkRandom,
+ iterations: requestChurnIterations,
+ buildRequest: buildItemRequest,
+ validateResponse: validateItemResponse,
+ })
+
+ return {
+ sanity: () => assertRequestChurnSanity(handler),
+ workloads: [
+ {
+ name: `mem request-churn (${framework})`,
+ run,
+ },
+ ],
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/memory.bench.ts b/benchmarks/memory/server/scenarios/request-churn/solid/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/memory.flame.ts b/benchmarks/memory/server/scenarios/request-churn/solid/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/project.json b/benchmarks/memory/server/scenarios/request-churn/solid/project.json
new file mode 100644
index 0000000000..3f3434ee24
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-request-churn-solid",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/setup.ts b/benchmarks/memory/server/scenarios/request-churn/solid/setup.ts
new file mode 100644
index 0000000000..4fe85c3f00
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('solid', handler)
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/request-churn/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..8f49ec5d1f
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/items/$id'
+ id: '__root__' | '/' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/src/router.tsx b/benchmarks/memory/server/scenarios/request-churn/solid/src/router.tsx
new file mode 100644
index 0000000000..038ec0ab5e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..aaf7dfdd89
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { HydrationScript } from 'solid-js/web'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charset: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/index.tsx b/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..d32d31f301
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return request-churn-index
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/items.$id.tsx b/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..e687e22741
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/src/routes/items.$id.tsx
@@ -0,0 +1,40 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+const itemIndexes = Array.from({ length: 5 }, (_, index) => index)
+
+type ItemSearch = {
+ q: string
+}
+
+export const Route = createFileRoute('/items/$id')({
+ validateSearch: (search: Record): ItemSearch => ({
+ q: typeof search.q === 'string' ? search.q : '',
+ }),
+ loaderDeps: ({ search }) => ({ q: search.q }),
+ loader: ({ params, deps }) => ({
+ id: params.id,
+ title: `Item ${params.id}`,
+ q: deps.q,
+ items: itemIndexes.map((index) => ({
+ id: `${params.id}-${index}`,
+ label: `${deps.q}-${index}`,
+ })),
+ }),
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {data().title}
+ {data().q}
+
+ {data().items.map((item) => (
+ - {item.label}
+ ))}
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/tsconfig.json b/benchmarks/memory/server/scenarios/request-churn/solid/tsconfig.json
new file mode 100644
index 0000000000..4b61264e11
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/solid/vite.config.ts b/benchmarks/memory/server/scenarios/request-churn/solid/vite.config.ts
new file mode 100644
index 0000000000..8ff879e9e0
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ solid({ ssr: true, hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server request-churn (solid)',
+ watch: false,
+ environment: 'node',
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/memory.bench.ts b/benchmarks/memory/server/scenarios/request-churn/vue/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/memory.flame.ts b/benchmarks/memory/server/scenarios/request-churn/vue/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/project.json b/benchmarks/memory/server/scenarios/request-churn/vue/project.json
new file mode 100644
index 0000000000..11eb1d868b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-request-churn-vue",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/setup.ts b/benchmarks/memory/server/scenarios/request-churn/vue/setup.ts
new file mode 100644
index 0000000000..6683dafe6b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('vue', handler)
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/request-churn/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..86ccc14a0c
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ItemsIdRoute = ItemsIdRouteImport.update({
+ id: '/items/$id',
+ path: '/items/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/items/$id'
+ id: '__root__' | '/' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/items/$id': {
+ id: '/items/$id'
+ path: '/items/$id'
+ fullPath: '/items/$id'
+ preLoaderRoute: typeof ItemsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/vue-start'
+declare module '@tanstack/vue-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/src/router.tsx b/benchmarks/memory/server/scenarios/request-churn/vue/src/router.tsx
new file mode 100644
index 0000000000..4290e7cdd3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..de29ee1612
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ Body,
+ HeadContent,
+ Html,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/index.tsx b/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..30751100a4
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return request-churn-index
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/items.$id.tsx b/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..a98d036565
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/src/routes/items.$id.tsx
@@ -0,0 +1,40 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+const itemIndexes = Array.from({ length: 5 }, (_, index) => index)
+
+type ItemSearch = {
+ q: string
+}
+
+export const Route = createFileRoute('/items/$id')({
+ validateSearch: (search: Record): ItemSearch => ({
+ q: typeof search.q === 'string' ? search.q : '',
+ }),
+ loaderDeps: ({ search }) => ({ q: search.q }),
+ loader: ({ params, deps }) => ({
+ id: params.id,
+ title: `Item ${params.id}`,
+ q: deps.q,
+ items: itemIndexes.map((index) => ({
+ id: `${params.id}-${index}`,
+ label: `${deps.q}-${index}`,
+ })),
+ }),
+ component: ItemComponent,
+})
+
+function ItemComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ {data.value.title}
+ {data.value.q}
+
+ {data.value.items.map((item) => (
+ - {item.label}
+ ))}
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/tsconfig.json b/benchmarks/memory/server/scenarios/request-churn/vue/tsconfig.json
new file mode 100644
index 0000000000..9ad6481342
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/request-churn/vue/vite.config.ts b/benchmarks/memory/server/scenarios/request-churn/vue/vite.config.ts
new file mode 100644
index 0000000000..bcfa82b0ca
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/request-churn/vue/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server request-churn (vue)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/memory.bench.ts b/benchmarks/memory/server/scenarios/serialization-payload/react/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/memory.flame.ts b/benchmarks/memory/server/scenarios/serialization-payload/react/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/project.json b/benchmarks/memory/server/scenarios/serialization-payload/react/project.json
new file mode 100644
index 0000000000..467b40d734
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-serialization-payload-react",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/setup.ts b/benchmarks/memory/server/scenarios/serialization-payload/react/setup.ts
new file mode 100644
index 0000000000..0c98f54ddd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('react', handler)
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/serialization-payload/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..7e370fd84c
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/src/routeTree.gen.ts
@@ -0,0 +1,68 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as DataIdRouteImport } from './routes/data.$id'
+
+const DataIdRoute = DataIdRouteImport.update({
+ id: '/data/$id',
+ path: '/data/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRoutesByTo {
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/data/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/data/$id'
+ id: '__root__' | '/data/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ DataIdRoute: typeof DataIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/data/$id': {
+ id: '/data/$id'
+ path: '/data/$id'
+ fullPath: '/data/$id'
+ preLoaderRoute: typeof DataIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ DataIdRoute: DataIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/src/router.tsx b/benchmarks/memory/server/scenarios/serialization-payload/react/src/router.tsx
new file mode 100644
index 0000000000..7c4eb0babe
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/serialization-payload/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..c5f9de6922
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/src/routes/__root.tsx
@@ -0,0 +1,27 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/src/routes/data.$id.tsx b/benchmarks/memory/server/scenarios/serialization-payload/react/src/routes/data.$id.tsx
new file mode 100644
index 0000000000..cfba0dbd62
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/src/routes/data.$id.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { makeSerializationPayload } from '../../../serialization-payload'
+
+export const Route = createFileRoute('/data/$id')({
+ loader: ({ params }) => makeSerializationPayload(params.id),
+ component: DataComponent,
+})
+
+function DataComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+ Map size: {data.lookup.size}
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/tsconfig.json b/benchmarks/memory/server/scenarios/serialization-payload/react/tsconfig.json
new file mode 100644
index 0000000000..11ddcce4ea
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/react/vite.config.ts b/benchmarks/memory/server/scenarios/serialization-payload/react/vite.config.ts
new file mode 100644
index 0000000000..cfcc305c1a
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/react/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server serialization-payload (react)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/serialization-payload.ts b/benchmarks/memory/server/scenarios/serialization-payload/serialization-payload.ts
new file mode 100644
index 0000000000..b6f401640e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/serialization-payload.ts
@@ -0,0 +1,115 @@
+const mapEntryCount = 500
+const setEntryCount = 500
+const temporalEntryCount = 500
+const nestedTreeDepth = 5
+const nestedTreeBreadth = 6
+const payloadTextLength = 150
+
+export interface MapPayloadValue {
+ index: number
+ label: string
+ createdAt: Date
+ count: bigint
+ text: string
+}
+
+export interface NestedPayloadNode {
+ id: string
+ depth: number
+ text: string
+ values: Array
+ children: Array
+}
+
+export interface SerializationPayload {
+ id: string
+ lookup: Map
+ tags: Set
+ dates: Array
+ bigints: Array
+ tree: NestedPayloadNode
+}
+
+export function makeSerializationPayload(id: string): SerializationPayload {
+ const hash = hashId(id)
+ const baseTimestamp = Date.UTC(2024, 0, 1) + hash
+
+ return {
+ id,
+ lookup: new Map(
+ Array.from(
+ { length: mapEntryCount },
+ (_, index): [string, MapPayloadValue] => [
+ makeMapKey(id, index),
+ {
+ index,
+ label: `${id}-map-${index}`,
+ createdAt: new Date(baseTimestamp + index * 1_000),
+ count: BigInt(hash) * 10_000n + BigInt(index),
+ text: makePayloadText(id, `map-${index}`),
+ },
+ ],
+ ),
+ ),
+ tags: new Set(
+ Array.from({ length: setEntryCount }, (_, index) =>
+ makePayloadText(id, `set-${index}`),
+ ),
+ ),
+ dates: Array.from(
+ { length: temporalEntryCount },
+ (_, index) => new Date(baseTimestamp + index * 60_000),
+ ),
+ bigints: Array.from(
+ { length: temporalEntryCount },
+ (_, index) => BigInt(hash) * 1_000_000n + BigInt(index),
+ ),
+ tree: makeNestedTree(id, 0, 'root', hash),
+ }
+}
+
+function makeMapKey(id: string, index: number) {
+ return `map-${id}-${index.toString().padStart(3, '0')}`
+}
+
+function makeNestedTree(
+ id: string,
+ depth: number,
+ path: string,
+ hash: number,
+): NestedPayloadNode {
+ return {
+ id: `${id}-node-${path}`,
+ depth,
+ text: makePayloadText(id, `tree-${depth}-${path}`),
+ values: [hash, depth, path.length],
+ children:
+ depth === nestedTreeDepth
+ ? []
+ : Array.from({ length: nestedTreeBreadth }, (_, index) =>
+ makeNestedTree(id, depth + 1, `${path}-${index}`, hash),
+ ),
+ }
+}
+
+function makePayloadText(id: string, segment: string) {
+ const filler = 'abcdefghijklmnopqrstuvwxyz0123456789'
+ let value = `${id}|${segment}|`
+
+ while (value.length < payloadTextLength) {
+ value += filler
+ }
+
+ return value.slice(0, payloadTextLength)
+}
+
+function hashId(id: string) {
+ let hash = 2_166_136_261
+
+ for (let index = 0; index < id.length; index++) {
+ hash ^= id.charCodeAt(index)
+ hash = Math.imul(hash, 16_777_619)
+ }
+
+ return hash >>> 0
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/shared.ts b/benchmarks/memory/server/scenarios/serialization-payload/shared.ts
new file mode 100644
index 0000000000..c02990bbf9
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/shared.ts
@@ -0,0 +1,75 @@
+import {
+ randomSegment,
+ runSequentialRequestLoop,
+} from '#memory-server/bench-utils'
+import type { StartRequestHandler } from '#memory-server/bench-utils'
+
+export type { StartRequestHandler }
+
+type Framework = 'react' | 'solid' | 'vue'
+
+const benchmarkSeed = 0x51eaa11
+const serializationPayloadIterations = 20
+const payloadPageMarker = 'data-bench="serialization-payload"'
+
+const requestInit = {
+ method: 'GET',
+ headers: {
+ accept: 'text/html',
+ },
+} satisfies RequestInit
+
+function buildPayloadRequest(random: () => number, index: number) {
+ const id = `payload-${index}-${randomSegment(random)}`
+
+ return new Request(`http://localhost/data/${id}`, requestInit)
+}
+
+function validatePayloadResponse(response: Response, request: Request) {
+ if (response.status !== 200) {
+ throw new Error(
+ `Expected status 200 for ${request.url}, got ${response.status}`,
+ )
+ }
+}
+
+function validatePayloadBody(body: string) {
+ if (!body.includes(payloadPageMarker)) {
+ throw new Error('Expected serialization-payload marker in response body')
+ }
+}
+
+async function assertSerializationPayloadSanity(handler: StartRequestHandler) {
+ const request = new Request(
+ 'http://localhost/data/sanity-payload',
+ requestInit,
+ )
+ const response = await handler.fetch(request)
+ const body = await response.text()
+
+ validatePayloadResponse(response, request)
+ validatePayloadBody(body)
+}
+
+export function createWorkloadGroup(
+ framework: Framework,
+ handler: StartRequestHandler,
+) {
+ const run = () =>
+ runSequentialRequestLoop(handler, {
+ seed: benchmarkSeed,
+ iterations: serializationPayloadIterations,
+ buildRequest: buildPayloadRequest,
+ validateResponse: validatePayloadResponse,
+ })
+
+ return {
+ sanity: () => assertSerializationPayloadSanity(handler),
+ workloads: [
+ {
+ name: `mem serialization-payload (${framework})`,
+ run,
+ },
+ ],
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/memory.bench.ts b/benchmarks/memory/server/scenarios/serialization-payload/solid/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/memory.flame.ts b/benchmarks/memory/server/scenarios/serialization-payload/solid/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/project.json b/benchmarks/memory/server/scenarios/serialization-payload/solid/project.json
new file mode 100644
index 0000000000..6f381ef389
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-serialization-payload-solid",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/setup.ts b/benchmarks/memory/server/scenarios/serialization-payload/solid/setup.ts
new file mode 100644
index 0000000000..4fe85c3f00
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('solid', handler)
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..082cbb290c
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routeTree.gen.ts
@@ -0,0 +1,68 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as DataIdRouteImport } from './routes/data.$id'
+
+const DataIdRoute = DataIdRouteImport.update({
+ id: '/data/$id',
+ path: '/data/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRoutesByTo {
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/data/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/data/$id'
+ id: '__root__' | '/data/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ DataIdRoute: typeof DataIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/data/$id': {
+ id: '/data/$id'
+ path: '/data/$id'
+ fullPath: '/data/$id'
+ preLoaderRoute: typeof DataIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ DataIdRoute: DataIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/src/router.tsx b/benchmarks/memory/server/scenarios/serialization-payload/solid/src/router.tsx
new file mode 100644
index 0000000000..038ec0ab5e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..aaf7dfdd89
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { HydrationScript } from 'solid-js/web'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charset: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routes/data.$id.tsx b/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routes/data.$id.tsx
new file mode 100644
index 0000000000..8cdbdb9046
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/src/routes/data.$id.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { makeSerializationPayload } from '../../../serialization-payload'
+
+export const Route = createFileRoute('/data/$id')({
+ loader: ({ params }) => makeSerializationPayload(params.id),
+ component: DataComponent,
+})
+
+function DataComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ Map size: {data().lookup.size}
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/tsconfig.json b/benchmarks/memory/server/scenarios/serialization-payload/solid/tsconfig.json
new file mode 100644
index 0000000000..4b61264e11
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/solid/vite.config.ts b/benchmarks/memory/server/scenarios/serialization-payload/solid/vite.config.ts
new file mode 100644
index 0000000000..26f57c9baa
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ solid({ ssr: true, hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server serialization-payload (solid)',
+ watch: false,
+ environment: 'node',
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/memory.bench.ts b/benchmarks/memory/server/scenarios/serialization-payload/vue/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/memory.flame.ts b/benchmarks/memory/server/scenarios/serialization-payload/vue/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/project.json b/benchmarks/memory/server/scenarios/serialization-payload/vue/project.json
new file mode 100644
index 0000000000..425b50a0d5
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-serialization-payload-vue",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/setup.ts b/benchmarks/memory/server/scenarios/serialization-payload/vue/setup.ts
new file mode 100644
index 0000000000..6683dafe6b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('vue', handler)
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..8adce01d75
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routeTree.gen.ts
@@ -0,0 +1,68 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as DataIdRouteImport } from './routes/data.$id'
+
+const DataIdRoute = DataIdRouteImport.update({
+ id: '/data/$id',
+ path: '/data/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRoutesByTo {
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/data/$id': typeof DataIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/data/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/data/$id'
+ id: '__root__' | '/data/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ DataIdRoute: typeof DataIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/data/$id': {
+ id: '/data/$id'
+ path: '/data/$id'
+ fullPath: '/data/$id'
+ preLoaderRoute: typeof DataIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ DataIdRoute: DataIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/vue-start'
+declare module '@tanstack/vue-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/src/router.tsx b/benchmarks/memory/server/scenarios/serialization-payload/vue/src/router.tsx
new file mode 100644
index 0000000000..4290e7cdd3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..de29ee1612
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ Body,
+ HeadContent,
+ Html,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routes/data.$id.tsx b/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routes/data.$id.tsx
new file mode 100644
index 0000000000..e6b4cfadfb
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/src/routes/data.$id.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { makeSerializationPayload } from '../../../serialization-payload'
+
+export const Route = createFileRoute('/data/$id')({
+ loader: ({ params }) => makeSerializationPayload(params.id),
+ component: DataComponent,
+})
+
+function DataComponent() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ Map size: {data.value.lookup.size}
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/tsconfig.json b/benchmarks/memory/server/scenarios/serialization-payload/vue/tsconfig.json
new file mode 100644
index 0000000000..9ad6481342
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/vue/vite.config.ts b/benchmarks/memory/server/scenarios/serialization-payload/vue/vite.config.ts
new file mode 100644
index 0000000000..b67693e559
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/serialization-payload/vue/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server serialization-payload (vue)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/memory.bench.ts b/benchmarks/memory/server/scenarios/server-fn-churn/react/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/memory.flame.ts b/benchmarks/memory/server/scenarios/server-fn-churn/react/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/project.json b/benchmarks/memory/server/scenarios/server-fn-churn/react/project.json
new file mode 100644
index 0000000000..841bbedca0
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-server-fn-churn-react",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/setup.ts b/benchmarks/memory/server/scenarios/server-fn-churn/react/setup.ts
new file mode 100644
index 0000000000..0c98f54ddd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('react', handler)
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/src/fns.ts b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/fns.ts
new file mode 100644
index 0000000000..22770278cd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/fns.ts
@@ -0,0 +1,24 @@
+import { createMiddleware, createServerFn } from '@tanstack/react-start'
+import {
+ makeServerFnChurnPayload,
+ validateServerFnInput,
+} from '../../server-fn-payload'
+
+const contextMiddleware = createMiddleware({ type: 'function' }).server(
+ ({ next }) =>
+ next({
+ context: {
+ ctx: 'ctx-server-fn-churn',
+ },
+ }),
+)
+
+export const churnGet = createServerFn({ method: 'GET' })
+ .middleware([contextMiddleware])
+ .validator(validateServerFnInput)
+ .handler(({ data, context }) => makeServerFnChurnPayload(data, context))
+
+export const churnPost = createServerFn({ method: 'POST' })
+ .middleware([contextMiddleware])
+ .validator(validateServerFnInput)
+ .handler(({ data, context }) => makeServerFnChurnPayload(data, context))
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..10e933da3d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ApiFnUrlsRouteImport } from './routes/api.fn-urls'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ApiFnUrlsRoute = ApiFnUrlsRouteImport.update({
+ id: '/api/fn-urls',
+ path: '/api/fn-urls',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/api/fn-urls'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/api/fn-urls'
+ id: '__root__' | '/' | '/api/fn-urls'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ApiFnUrlsRoute: typeof ApiFnUrlsRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/api/fn-urls': {
+ id: '/api/fn-urls'
+ path: '/api/fn-urls'
+ fullPath: '/api/fn-urls'
+ preLoaderRoute: typeof ApiFnUrlsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ApiFnUrlsRoute: ApiFnUrlsRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/src/router.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/router.tsx
new file mode 100644
index 0000000000..7c4eb0babe
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..c5f9de6922
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/__root.tsx
@@ -0,0 +1,27 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/api.fn-urls.ts b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/api.fn-urls.ts
new file mode 100644
index 0000000000..da74a20f62
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/api.fn-urls.ts
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { churnGet, churnPost } from '../fns'
+
+export const Route = createFileRoute('/api/fn-urls')({
+ server: {
+ handlers: {
+ GET: () =>
+ Response.json({
+ get: churnGet.url,
+ post: churnPost.url,
+ }),
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/index.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/index.tsx
new file mode 100644
index 0000000000..de0acfc2b3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/index.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { churnGet, churnPost } from '../fns'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return (
+
+ memory-server-fn-churn-index
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/tsconfig.json b/benchmarks/memory/server/scenarios/server-fn-churn/react/tsconfig.json
new file mode 100644
index 0000000000..11ddcce4ea
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/react/vite.config.ts b/benchmarks/memory/server/scenarios/server-fn-churn/react/vite.config.ts
new file mode 100644
index 0000000000..8a3eea0d36
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/react/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server server-fn-churn (react)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/server-fn-payload.ts b/benchmarks/memory/server/scenarios/server-fn-churn/server-fn-payload.ts
new file mode 100644
index 0000000000..0f23390a6c
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/server-fn-payload.ts
@@ -0,0 +1,33 @@
+export type ServerFnInput = {
+ id: string
+}
+
+export type ServerFnChurnContext = {
+ ctx: string
+}
+
+const recordIndexes = Array.from({ length: 5 }, (_, index) => index)
+
+export function validateServerFnInput(input: unknown): ServerFnInput {
+ const payload = input as Partial | null
+
+ if (typeof payload?.id !== 'string') {
+ throw new Error('invalid server-fn churn input')
+ }
+
+ return { id: payload.id }
+}
+
+export function makeServerFnChurnPayload(
+ data: ServerFnInput,
+ context: ServerFnChurnContext,
+) {
+ return {
+ id: data.id,
+ ctx: context.ctx,
+ payload: recordIndexes.map((index) => ({
+ id: `${data.id}-${index}`,
+ label: `record-${index}`,
+ })),
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/shared.ts b/benchmarks/memory/server/scenarios/server-fn-churn/shared.ts
new file mode 100644
index 0000000000..ad1022c836
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/shared.ts
@@ -0,0 +1,232 @@
+import {
+ createDeterministicRandom,
+ randomSegment,
+ runSequentialRequestLoop,
+} from '#memory-server/bench-utils'
+import type { StartRequestHandler } from '#memory-server/bench-utils'
+
+export type { StartRequestHandler }
+
+type Framework = 'react' | 'solid' | 'vue'
+
+type FnUrls = {
+ get: string
+ post: string
+}
+
+type PayloadFixture = {
+ id: string
+ body: string
+ query: string
+}
+
+type SerovalNode =
+ | {
+ t: 1
+ s: string
+ }
+ | {
+ t: 10
+ i: number
+ p: {
+ k: Array
+ v: Array
+ }
+ o: number
+ }
+
+const benchmarkSeed = 0xdecafbad
+const payloadSeed = 0x51f0cafe
+const fixtureCount = 16
+const serverFnChurnIterations = 150
+const origin = 'http://localhost'
+const tssContentTypeFramed = 'application/x-tss-framed'
+const acceptHeader = `${tssContentTypeFramed}, application/x-ndjson, application/json`
+const xTssSerialized = 'x-tss-serialized'
+const contextMarker = 'ctx-server-fn-churn'
+
+const commonHeaders = {
+ 'x-tsr-serverFn': 'true',
+ 'sec-fetch-site': 'same-origin',
+ accept: acceptHeader,
+} satisfies HeadersInit
+
+const postHeaders = {
+ ...commonHeaders,
+ 'content-type': 'application/json',
+} satisfies HeadersInit
+
+// Hand-rolled copy of Start's seroval RPC wire format so POST bodies can be
+// precomputed at module level. Coupled to the internal protocol on purpose;
+// the module-load sanity check below throws loudly if the protocol drifts.
+function stringNode(value: string): SerovalNode {
+ return { t: 1, s: value }
+}
+
+function objectNode(
+ id: number,
+ entries: Array,
+): SerovalNode {
+ return {
+ t: 10,
+ i: id,
+ p: {
+ k: entries.map(([key]) => key),
+ v: entries.map(([, value]) => value),
+ },
+ o: 0,
+ }
+}
+
+function serializePayload(id: string) {
+ return JSON.stringify({
+ t: objectNode(0, [['data', objectNode(1, [['id', stringNode(id)]])]]),
+ f: 63,
+ m: [],
+ })
+}
+
+function createFixtures(kind: 'get' | 'post') {
+ const random = createDeterministicRandom(payloadSeed ^ kind.length)
+
+ return Array.from({ length: fixtureCount }, (_, index): PayloadFixture => {
+ const id = [kind, index, randomSegment(random), randomSegment(random)].join(
+ '-',
+ )
+ const body = serializePayload(id)
+
+ return {
+ id,
+ body,
+ query: `?${new URLSearchParams({ payload: body })}`,
+ }
+ })
+}
+
+const getFixtures = createFixtures('get')
+const postFixtures = createFixtures('post')
+
+async function discoverUrls(handler: StartRequestHandler) {
+ const response = await handler.fetch(new Request(`${origin}/api/fn-urls`))
+ const text = await response.text()
+
+ if (response.status !== 200) {
+ throw new Error(
+ `URL discovery failed with status ${response.status}: ${text}`,
+ )
+ }
+
+ let urls: Partial
+
+ try {
+ urls = JSON.parse(text) as Partial
+ } catch (error) {
+ throw new Error(`URL discovery returned invalid JSON: ${text}`, {
+ cause: error,
+ })
+ }
+
+ if (typeof urls.get !== 'string' || typeof urls.post !== 'string') {
+ throw new Error(`URL discovery returned invalid payload: ${text}`)
+ }
+
+ return urls as FnUrls
+}
+
+function buildGetRequest(url: string, fixture: PayloadFixture) {
+ return new Request(`${origin}${url}${fixture.query}`, {
+ method: 'GET',
+ headers: commonHeaders,
+ })
+}
+
+function buildPostRequest(url: string, fixture: PayloadFixture) {
+ return new Request(`${origin}${url}`, {
+ method: 'POST',
+ headers: postHeaders,
+ body: fixture.body,
+ })
+}
+
+function validateServerFnResponse(response: Response, request: Request) {
+ if (response.status !== 200) {
+ throw new Error(
+ `Expected status 200 for ${request.url}, got ${response.status}`,
+ )
+ }
+
+ if (!response.headers.get(xTssSerialized)) {
+ throw new Error(`Expected ${xTssSerialized} header for ${request.url}`)
+ }
+}
+
+function validateEchoedBody(
+ body: string,
+ request: Request,
+ expectedId: string,
+) {
+ if (!body.includes(expectedId)) {
+ throw new Error(`Expected echoed id ${expectedId} in ${request.url}`)
+ }
+
+ if (!body.includes(contextMarker)) {
+ throw new Error(
+ `Expected context marker ${contextMarker} in ${request.url}`,
+ )
+ }
+}
+
+async function assertServerFnChurnSanity(
+ handler: StartRequestHandler,
+ urls: FnUrls,
+) {
+ const getFixture = getFixtures[0]!
+ const getRequest = buildGetRequest(urls.get, getFixture)
+ const getResponse = await handler.fetch(getRequest)
+ const getBody = await getResponse.text()
+
+ validateServerFnResponse(getResponse, getRequest)
+ validateEchoedBody(getBody, getRequest, getFixture.id)
+
+ const postFixture = postFixtures[0]!
+ const postRequest = buildPostRequest(urls.post, postFixture)
+ const postResponse = await handler.fetch(postRequest)
+ const postBody = await postResponse.text()
+
+ validateServerFnResponse(postResponse, postRequest)
+ validateEchoedBody(postBody, postRequest, postFixture.id)
+}
+
+export async function createWorkloadGroup(
+ framework: Framework,
+ handler: StartRequestHandler,
+) {
+ const urls = await discoverUrls(handler)
+ const run = () =>
+ runSequentialRequestLoop(handler, {
+ seed: benchmarkSeed,
+ iterations: serverFnChurnIterations,
+ buildRequest: (_random, index) => {
+ const fixtureIndex = Math.floor(index / 2) % fixtureCount
+
+ if (index % 2 === 0) {
+ const fixture = getFixtures[fixtureIndex]!
+ return buildGetRequest(urls.get, fixture)
+ } else {
+ const fixture = postFixtures[fixtureIndex]!
+ return buildPostRequest(urls.post, fixture)
+ }
+ },
+ validateResponse: validateServerFnResponse,
+ })
+
+ return {
+ sanity: () => assertServerFnChurnSanity(handler, urls),
+ workloads: [
+ {
+ name: `mem server-fn-churn (${framework})`,
+ run,
+ },
+ ],
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/memory.bench.ts b/benchmarks/memory/server/scenarios/server-fn-churn/solid/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/memory.flame.ts b/benchmarks/memory/server/scenarios/server-fn-churn/solid/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/project.json b/benchmarks/memory/server/scenarios/server-fn-churn/solid/project.json
new file mode 100644
index 0000000000..53aca0cea6
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-server-fn-churn-solid",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/setup.ts b/benchmarks/memory/server/scenarios/server-fn-churn/solid/setup.ts
new file mode 100644
index 0000000000..4fe85c3f00
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('solid', handler)
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/fns.ts b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/fns.ts
new file mode 100644
index 0000000000..31669dff50
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/fns.ts
@@ -0,0 +1,24 @@
+import { createMiddleware, createServerFn } from '@tanstack/solid-start'
+import {
+ makeServerFnChurnPayload,
+ validateServerFnInput,
+} from '../../server-fn-payload'
+
+const contextMiddleware = createMiddleware({ type: 'function' }).server(
+ ({ next }) =>
+ next({
+ context: {
+ ctx: 'ctx-server-fn-churn',
+ },
+ }),
+)
+
+export const churnGet = createServerFn({ method: 'GET' })
+ .middleware([contextMiddleware])
+ .validator(validateServerFnInput)
+ .handler(({ data, context }) => makeServerFnChurnPayload(data, context))
+
+export const churnPost = createServerFn({ method: 'POST' })
+ .middleware([contextMiddleware])
+ .validator(validateServerFnInput)
+ .handler(({ data, context }) => makeServerFnChurnPayload(data, context))
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..298ac3168d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ApiFnUrlsRouteImport } from './routes/api.fn-urls'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ApiFnUrlsRoute = ApiFnUrlsRouteImport.update({
+ id: '/api/fn-urls',
+ path: '/api/fn-urls',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/api/fn-urls'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/api/fn-urls'
+ id: '__root__' | '/' | '/api/fn-urls'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ApiFnUrlsRoute: typeof ApiFnUrlsRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/api/fn-urls': {
+ id: '/api/fn-urls'
+ path: '/api/fn-urls'
+ fullPath: '/api/fn-urls'
+ preLoaderRoute: typeof ApiFnUrlsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ApiFnUrlsRoute: ApiFnUrlsRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/router.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/router.tsx
new file mode 100644
index 0000000000..038ec0ab5e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..aaf7dfdd89
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { HydrationScript } from 'solid-js/web'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charset: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/api.fn-urls.ts b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/api.fn-urls.ts
new file mode 100644
index 0000000000..2063684034
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/api.fn-urls.ts
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { churnGet, churnPost } from '../fns'
+
+export const Route = createFileRoute('/api/fn-urls')({
+ server: {
+ handlers: {
+ GET: () =>
+ Response.json({
+ get: churnGet.url,
+ post: churnPost.url,
+ }),
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/index.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..99fea371f8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/index.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { churnGet, churnPost } from '../fns'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return (
+
+ memory-server-fn-churn-index
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/tsconfig.json b/benchmarks/memory/server/scenarios/server-fn-churn/solid/tsconfig.json
new file mode 100644
index 0000000000..4b61264e11
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/solid/vite.config.ts b/benchmarks/memory/server/scenarios/server-fn-churn/solid/vite.config.ts
new file mode 100644
index 0000000000..8ffdc48227
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ solid({ ssr: true, hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server server-fn-churn (solid)',
+ watch: false,
+ environment: 'node',
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/memory.bench.ts b/benchmarks/memory/server/scenarios/server-fn-churn/vue/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/memory.flame.ts b/benchmarks/memory/server/scenarios/server-fn-churn/vue/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/project.json b/benchmarks/memory/server/scenarios/server-fn-churn/vue/project.json
new file mode 100644
index 0000000000..c5809a259b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-server-fn-churn-vue",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/setup.ts b/benchmarks/memory/server/scenarios/server-fn-churn/vue/setup.ts
new file mode 100644
index 0000000000..6683dafe6b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('vue', handler)
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/fns.ts b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/fns.ts
new file mode 100644
index 0000000000..0fa679f76f
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/fns.ts
@@ -0,0 +1,24 @@
+import { createMiddleware, createServerFn } from '@tanstack/vue-start'
+import {
+ makeServerFnChurnPayload,
+ validateServerFnInput,
+} from '../../server-fn-payload'
+
+const contextMiddleware = createMiddleware({ type: 'function' }).server(
+ ({ next }) =>
+ next({
+ context: {
+ ctx: 'ctx-server-fn-churn',
+ },
+ }),
+)
+
+export const churnGet = createServerFn({ method: 'GET' })
+ .middleware([contextMiddleware])
+ .validator(validateServerFnInput)
+ .handler(({ data, context }) => makeServerFnChurnPayload(data, context))
+
+export const churnPost = createServerFn({ method: 'POST' })
+ .middleware([contextMiddleware])
+ .validator(validateServerFnInput)
+ .handler(({ data, context }) => makeServerFnChurnPayload(data, context))
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..b226f0a25a
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ApiFnUrlsRouteImport } from './routes/api.fn-urls'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ApiFnUrlsRoute = ApiFnUrlsRouteImport.update({
+ id: '/api/fn-urls',
+ path: '/api/fn-urls',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/api/fn-urls': typeof ApiFnUrlsRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/api/fn-urls'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/api/fn-urls'
+ id: '__root__' | '/' | '/api/fn-urls'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ ApiFnUrlsRoute: typeof ApiFnUrlsRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/api/fn-urls': {
+ id: '/api/fn-urls'
+ path: '/api/fn-urls'
+ fullPath: '/api/fn-urls'
+ preLoaderRoute: typeof ApiFnUrlsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ ApiFnUrlsRoute: ApiFnUrlsRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/vue-start'
+declare module '@tanstack/vue-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/router.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/router.tsx
new file mode 100644
index 0000000000..4290e7cdd3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..de29ee1612
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ Body,
+ HeadContent,
+ Html,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/api.fn-urls.ts b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/api.fn-urls.ts
new file mode 100644
index 0000000000..ff99e89e4c
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/api.fn-urls.ts
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { churnGet, churnPost } from '../fns'
+
+export const Route = createFileRoute('/api/fn-urls')({
+ server: {
+ handlers: {
+ GET: () =>
+ Response.json({
+ get: churnGet.url,
+ post: churnPost.url,
+ }),
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/index.tsx b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..0a39b9301c
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/index.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/vue-router'
+import { churnGet, churnPost } from '../fns'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return (
+
+ memory-server-fn-churn-index
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/tsconfig.json b/benchmarks/memory/server/scenarios/server-fn-churn/vue/tsconfig.json
new file mode 100644
index 0000000000..9ad6481342
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/server-fn-churn/vue/vite.config.ts b/benchmarks/memory/server/scenarios/server-fn-churn/vue/vite.config.ts
new file mode 100644
index 0000000000..02b404808e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/server-fn-churn/vue/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server server-fn-churn (vue)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/deferred-section-data.ts b/benchmarks/memory/server/scenarios/streaming-peak/deferred-section-data.ts
new file mode 100644
index 0000000000..b30352ef39
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/deferred-section-data.ts
@@ -0,0 +1,37 @@
+const deferredRecordCount = 250
+const recordValueLength = 128
+
+interface DeferredRecord {
+ id: string
+ value: string
+}
+
+export interface DeferredSectionPayload {
+ index: number
+ records: Array
+}
+
+export function makeDeferredSectionPayload(
+ id: string,
+ sectionIndex: number,
+): DeferredSectionPayload {
+ return {
+ index: sectionIndex,
+ records: Array.from({ length: deferredRecordCount }, (_, recordIndex) => ({
+ id: `${id}-${sectionIndex}-${recordIndex}`,
+ value: makeRecordValue(id, sectionIndex, recordIndex),
+ })),
+ }
+}
+
+function makeRecordValue(
+ id: string,
+ sectionIndex: number,
+ recordIndex: number,
+) {
+ const token = `${id}:${sectionIndex}:${recordIndex}:streaming-peak-record;`
+
+ return token
+ .repeat(Math.ceil(recordValueLength / token.length))
+ .slice(0, recordValueLength)
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/memory.bench.ts b/benchmarks/memory/server/scenarios/streaming-peak/react/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/memory.flame.ts b/benchmarks/memory/server/scenarios/streaming-peak/react/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/project.json b/benchmarks/memory/server/scenarios/streaming-peak/react/project.json
new file mode 100644
index 0000000000..6fc1dcdda0
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-streaming-peak-react",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/setup.ts b/benchmarks/memory/server/scenarios/streaming-peak/react/setup.ts
new file mode 100644
index 0000000000..0c98f54ddd
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('react', handler)
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/streaming-peak/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..fbbc25ecfb
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as StreamIdRouteImport } from './routes/stream.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const StreamIdRoute = StreamIdRouteImport.update({
+ id: '/stream/$id',
+ path: '/stream/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/stream/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/stream/$id'
+ id: '__root__' | '/' | '/stream/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ StreamIdRoute: typeof StreamIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/stream/$id': {
+ id: '/stream/$id'
+ path: '/stream/$id'
+ fullPath: '/stream/$id'
+ preLoaderRoute: typeof StreamIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ StreamIdRoute: StreamIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/src/router.tsx b/benchmarks/memory/server/scenarios/streaming-peak/react/src/router.tsx
new file mode 100644
index 0000000000..7c4eb0babe
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..c5f9de6922
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/__root.tsx
@@ -0,0 +1,27 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/index.tsx b/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/index.tsx
new file mode 100644
index 0000000000..669fdef2e1
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return streaming-peak-index
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/stream.$id.tsx b/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/stream.$id.tsx
new file mode 100644
index 0000000000..aa8ba5e865
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/stream.$id.tsx
@@ -0,0 +1,83 @@
+import { Await, createFileRoute } from '@tanstack/react-router'
+import { Suspense } from 'react'
+import {
+ makeDeferredSectionPayload,
+ type DeferredSectionPayload,
+} from '../../../deferred-section-data'
+
+const fallbackFlushDelayMs = 1
+
+export const Route = createFileRoute('/stream/$id')({
+ loader: ({ params }) => ({
+ eager: `streaming-peak-eager-${params.id}`,
+ deferred0: makeDeferredSection(params.id, 0),
+ deferred1: makeDeferredSection(params.id, 1),
+ deferred2: makeDeferredSection(params.id, 2),
+ deferred3: makeDeferredSection(params.id, 3),
+ }),
+ component: StreamComponent,
+})
+
+// Deferred sections must settle strictly AFTER React's shell flush, which
+// React schedules via setImmediate internally. Microtask chains drain during
+// router load (sections resolve before the Suspense boundaries are even
+// reached) and setImmediate chains registered at loader time win the race
+// against React's flush — either way no fallback ever streams and the bench
+// stops exercising multi-flush streaming. Timer-phase callbacks reliably lose
+// that race, so this is the documented exception to the no-timers convention:
+// the few ms of wall-clock are irrelevant to memory metrics, and distinct
+// delays keep section ordering deterministic.
+function afterFallbackFlush(sectionIndex: number) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, fallbackFlushDelayMs + sectionIndex)
+ })
+}
+
+function makeDeferredSection(id: string, sectionIndex: number) {
+ return afterFallbackFlush(sectionIndex).then(() =>
+ makeDeferredSectionPayload(id, sectionIndex),
+ )
+}
+
+function StreamComponent() {
+ const data = Route.useLoaderData()
+ const deferredSections = [
+ { index: 0, promise: data.deferred0 },
+ { index: 1, promise: data.deferred1 },
+ { index: 2, promise: data.deferred2 },
+ { index: 3, promise: data.deferred3 },
+ ] as const
+
+ return (
+
+ {data.eager}
+ {deferredSections.map(({ index, promise }) => (
+
+ streaming-peak-fallback-{index}
+
+ }
+ >
+
+ {(section) => }
+
+
+ ))}
+
+ )
+}
+
+function DeferredSection({ section }: { section: DeferredSectionPayload }) {
+ const marker = `streaming-peak-deferred-${section.index}`
+
+ return (
+
+ {marker}
+ {section.records.map((record) => (
+ {record.value}
+ ))}
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/tsconfig.json b/benchmarks/memory/server/scenarios/streaming-peak/react/tsconfig.json
new file mode 100644
index 0000000000..11ddcce4ea
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/react/vite.config.ts b/benchmarks/memory/server/scenarios/streaming-peak/react/vite.config.ts
new file mode 100644
index 0000000000..733aabb76d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/react/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import react from '@vitejs/plugin-react'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server streaming-peak (react)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/shared.ts b/benchmarks/memory/server/scenarios/streaming-peak/shared.ts
new file mode 100644
index 0000000000..a82c43b5b4
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/shared.ts
@@ -0,0 +1,112 @@
+import {
+ randomSegment,
+ runSequentialRequestLoop,
+} from '#memory-server/bench-utils'
+import type { StartRequestHandler } from '#memory-server/bench-utils'
+
+export type { StartRequestHandler }
+
+type Framework = 'react' | 'solid' | 'vue'
+
+const benchmarkSeed = 0xdecafbad
+const streamingPeakIterations = 20
+const fallbackMarker = 'streaming-peak-fallback-0'
+
+const requestInit = {
+ method: 'GET',
+ headers: {
+ accept: 'text/html',
+ },
+} satisfies RequestInit
+
+function buildStreamingRequest(random: () => number, index: number) {
+ return new Request(
+ `http://localhost/stream/${index}-${randomSegment(random)}`,
+ requestInit,
+ )
+}
+
+function validateStreamingResponse(response: Response, request: Request) {
+ if (response.status !== 200) {
+ throw new Error(
+ `Expected status 200 for ${request.url}, got ${response.status}`,
+ )
+ }
+}
+
+function getResponseReader(response: Response) {
+ const reader = response.body?.getReader()
+
+ if (!reader) {
+ throw new Error('Expected streaming response body')
+ }
+
+ return reader
+}
+
+async function readStreamingBody(response: Response) {
+ const reader = getResponseReader(response)
+ const decoder = new TextDecoder()
+ let body = ''
+ let chunkCount = 0
+
+ while (true) {
+ const result = await reader.read()
+
+ if (result.done) {
+ break
+ }
+
+ chunkCount++
+ body += decoder.decode(result.value, { stream: true })
+ }
+
+ body += decoder.decode()
+
+ return { body, chunkCount }
+}
+
+async function assertStreamingPeakSanity(handler: StartRequestHandler) {
+ const chunkedRequest = new Request(
+ 'http://localhost/stream/sanity-chunked',
+ requestInit,
+ )
+ const chunkedResponse = await handler.fetch(chunkedRequest)
+
+ validateStreamingResponse(chunkedResponse, chunkedRequest)
+
+ const chunked = await readStreamingBody(chunkedResponse)
+
+ if (chunked.chunkCount <= 1) {
+ throw new Error(
+ `Expected chunked sanity response to produce multiple chunks, got ${chunked.chunkCount}`,
+ )
+ }
+
+ if (!chunked.body.includes(fallbackMarker)) {
+ throw new Error('Expected streaming-peak fallback marker in response body')
+ }
+}
+
+export function createWorkloadGroup(
+ framework: Framework,
+ handler: StartRequestHandler,
+) {
+ const run = () =>
+ runSequentialRequestLoop(handler, {
+ seed: benchmarkSeed,
+ iterations: streamingPeakIterations,
+ buildRequest: buildStreamingRequest,
+ validateResponse: validateStreamingResponse,
+ })
+
+ return {
+ sanity: () => assertStreamingPeakSanity(handler),
+ workloads: [
+ {
+ name: `mem streaming-peak chunked (${framework})`,
+ run,
+ },
+ ],
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/memory.bench.ts b/benchmarks/memory/server/scenarios/streaming-peak/solid/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/memory.flame.ts b/benchmarks/memory/server/scenarios/streaming-peak/solid/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/project.json b/benchmarks/memory/server/scenarios/streaming-peak/solid/project.json
new file mode 100644
index 0000000000..80f44d1063
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-streaming-peak-solid",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/setup.ts b/benchmarks/memory/server/scenarios/streaming-peak/solid/setup.ts
new file mode 100644
index 0000000000..4fe85c3f00
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('solid', handler)
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..04ebed80f0
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as StreamIdRouteImport } from './routes/stream.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const StreamIdRoute = StreamIdRouteImport.update({
+ id: '/stream/$id',
+ path: '/stream/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/stream/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/stream/$id'
+ id: '__root__' | '/' | '/stream/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ StreamIdRoute: typeof StreamIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/stream/$id': {
+ id: '/stream/$id'
+ path: '/stream/$id'
+ fullPath: '/stream/$id'
+ preLoaderRoute: typeof StreamIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ StreamIdRoute: StreamIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/src/router.tsx b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/router.tsx
new file mode 100644
index 0000000000..038ec0ab5e
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..aaf7dfdd89
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ HeadContent,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { HydrationScript } from 'solid-js/web'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charset: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/index.tsx b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..5815e36b3d
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return streaming-peak-index
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/stream.$id.tsx b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/stream.$id.tsx
new file mode 100644
index 0000000000..0a3f64ffd4
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/stream.$id.tsx
@@ -0,0 +1,75 @@
+import { Await, createFileRoute } from '@tanstack/solid-router'
+import { Suspense } from 'solid-js'
+import {
+ makeDeferredSectionPayload,
+ type DeferredSectionPayload,
+} from '../../../deferred-section-data'
+
+const fallbackFlushDelayMs = 25
+
+export const Route = createFileRoute('/stream/$id')({
+ loader: ({ params }) => ({
+ eager: `streaming-peak-eager-${params.id}`,
+ deferred0: makeDeferredSection(params.id, 0),
+ deferred1: makeDeferredSection(params.id, 1),
+ deferred2: makeDeferredSection(params.id, 2),
+ deferred3: makeDeferredSection(params.id, 3),
+ }),
+ component: StreamComponent,
+})
+
+// Deferred sections must settle after the framework shell flush so the bench
+// continues exercising multi-flush streaming with visible fallback chunks.
+function afterFallbackFlush(sectionIndex: number) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, fallbackFlushDelayMs + sectionIndex)
+ })
+}
+
+function makeDeferredSection(id: string, sectionIndex: number) {
+ return afterFallbackFlush(sectionIndex).then(() =>
+ makeDeferredSectionPayload(id, sectionIndex),
+ )
+}
+
+function StreamComponent() {
+ const data = Route.useLoaderData()
+ const deferredSections = () =>
+ [
+ { index: 0, promise: data().deferred0 },
+ { index: 1, promise: data().deferred1 },
+ { index: 2, promise: data().deferred2 },
+ { index: 3, promise: data().deferred3 },
+ ] as const
+
+ return (
+
+ {data().eager}
+ {deferredSections().map(({ index, promise }) => (
+ <>
+
+ streaming-peak-fallback-{index}
+
+
+
+ {(section) => }
+
+
+ >
+ ))}
+
+ )
+}
+
+function DeferredSection(props: { section: DeferredSectionPayload }) {
+ const marker = () => `streaming-peak-deferred-${props.section.index}`
+
+ return (
+
+ {marker()}
+ {props.section.records.map((record) => (
+ {record.value}
+ ))}
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/tsconfig.json b/benchmarks/memory/server/scenarios/streaming-peak/solid/tsconfig.json
new file mode 100644
index 0000000000..4b61264e11
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/solid/vite.config.ts b/benchmarks/memory/server/scenarios/streaming-peak/solid/vite.config.ts
new file mode 100644
index 0000000000..b7d98e709c
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/solid/vite.config.ts
@@ -0,0 +1,34 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import solid from 'vite-plugin-solid'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ solid({ ssr: true, hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server streaming-peak (solid)',
+ watch: false,
+ environment: 'node',
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/memory.bench.ts b/benchmarks/memory/server/scenarios/streaming-peak/vue/memory.bench.ts
new file mode 100644
index 0000000000..9a5c211fee
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/memory.bench.ts
@@ -0,0 +1,11 @@
+import { bench, describe } from 'vitest'
+import { memoryBenchOptions } from '#memory-server/bench-utils'
+import { workloadGroup } from './setup'
+
+await workloadGroup.sanity()
+
+describe('memory', () => {
+ for (const workload of workloadGroup.workloads) {
+ bench(workload.name, workload.run, memoryBenchOptions)
+ }
+})
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/memory.flame.ts b/benchmarks/memory/server/scenarios/streaming-peak/vue/memory.flame.ts
new file mode 100644
index 0000000000..0182c472a8
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/memory.flame.ts
@@ -0,0 +1,4 @@
+import { runServerFlameBenchmark } from '#memory-server/flame-runner'
+import { workloadGroup } from './setup.ts'
+
+await runServerFlameBenchmark(workloadGroup)
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/project.json b/benchmarks/memory/server/scenarios/streaming-peak/vue/project.json
new file mode 100644
index 0000000000..55771aebe3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/project.json
@@ -0,0 +1,54 @@
+{
+ "name": "@benchmarks/memory-server-streaming-peak-vue",
+ "projectType": "application",
+ "targets": {
+ "build:ssr": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "build:ssr:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts --sourcemap true"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "parallelism": false,
+ "cache": false,
+ "dependsOn": ["build:ssr:flame"],
+ "options": {
+ "command": "NODE_ENV=production node benchmarks/memory/run-flame.mjs {projectRoot}/memory.flame.ts {projectRoot}/dist",
+ "cwd": "."
+ }
+ },
+ "test:types:ssr": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-start"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ }
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/setup.ts b/benchmarks/memory/server/scenarios/streaming-peak/vue/setup.ts
new file mode 100644
index 0000000000..6683dafe6b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/setup.ts
@@ -0,0 +1,14 @@
+import type { ServerMemoryWorkloadGroup } from '#memory-server/benchmark'
+import { createWorkloadGroup } from '../shared.ts'
+import type { StartRequestHandler } from '../shared.ts'
+
+const appModuleUrl = new URL('./dist/server/server.js', import.meta.url).href
+
+const { default: handler } = (await import(
+ /* @vite-ignore */ appModuleUrl
+)) as {
+ default: StartRequestHandler
+}
+
+export const workloadGroup: ServerMemoryWorkloadGroup =
+ await createWorkloadGroup('vue', handler)
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..192f1d6dc7
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routeTree.gen.ts
@@ -0,0 +1,86 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as StreamIdRouteImport } from './routes/stream.$id'
+
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const StreamIdRoute = StreamIdRouteImport.update({
+ id: '/stream/$id',
+ path: '/stream/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/stream/$id': typeof StreamIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/stream/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/stream/$id'
+ id: '__root__' | '/' | '/stream/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ StreamIdRoute: typeof StreamIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/stream/$id': {
+ id: '/stream/$id'
+ path: '/stream/$id'
+ fullPath: '/stream/$id'
+ preLoaderRoute: typeof StreamIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ StreamIdRoute: StreamIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/vue-start'
+declare module '@tanstack/vue-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/src/router.tsx b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/router.tsx
new file mode 100644
index 0000000000..4290e7cdd3
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: false,
+ scrollRestoration: false,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/__root.tsx b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..de29ee1612
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import {
+ Body,
+ HeadContent,
+ Html,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/vue-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [{ charSet: 'utf-8' }],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/index.tsx b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..bc55b49adc
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/')({
+ component: IndexComponent,
+})
+
+function IndexComponent() {
+ return streaming-peak-index
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/stream.$id.tsx b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/stream.$id.tsx
new file mode 100644
index 0000000000..963782403b
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/stream.$id.tsx
@@ -0,0 +1,82 @@
+import { Await, createFileRoute } from '@tanstack/vue-router'
+import { Suspense } from 'vue'
+import {
+ makeDeferredSectionPayload,
+ type DeferredSectionPayload,
+} from '../../../deferred-section-data'
+
+const fallbackFlushDelayMs = 1
+
+export const Route = createFileRoute('/stream/$id')({
+ loader: ({ params }) => ({
+ eager: `streaming-peak-eager-${params.id}`,
+ deferred0: makeDeferredSection(params.id, 0),
+ deferred1: makeDeferredSection(params.id, 1),
+ deferred2: makeDeferredSection(params.id, 2),
+ deferred3: makeDeferredSection(params.id, 3),
+ }),
+ component: StreamComponent,
+})
+
+// Deferred sections must settle strictly AFTER Vue's shell has a chance to
+// flush, so fallbacks are emitted before deferred section content.
+function afterFallbackFlush(sectionIndex: number) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, fallbackFlushDelayMs + sectionIndex)
+ })
+}
+
+function makeDeferredSection(id: string, sectionIndex: number) {
+ return afterFallbackFlush(sectionIndex).then(() =>
+ makeDeferredSectionPayload(id, sectionIndex),
+ )
+}
+
+function StreamComponent() {
+ const data = Route.useLoaderData()
+ const deferredSections = [
+ { index: 0, promise: data.value.deferred0 },
+ { index: 1, promise: data.value.deferred1 },
+ { index: 2, promise: data.value.deferred2 },
+ { index: 3, promise: data.value.deferred3 },
+ ] as const
+
+ return (
+
+ {data.value.eager}
+ {deferredSections.map(({ index, promise }) => (
+ <>
+
+ streaming-peak-fallback-{index}
+
+
+ {{
+ default: () => (
+ (
+
+ )}
+ />
+ ),
+ fallback: () => null,
+ }}
+
+ >
+ ))}
+
+ )
+}
+
+function DeferredSection({ section }: { section: DeferredSectionPayload }) {
+ const marker = `streaming-peak-deferred-${section.index}`
+
+ return (
+
+ {marker}
+ {section.records.map((record) => (
+ {record.value}
+ ))}
+
+ )
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/tsconfig.json b/benchmarks/memory/server/scenarios/streaming-peak/vue/tsconfig.json
new file mode 100644
index 0000000000..9ad6481342
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "memory.bench.ts",
+ "memory.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../../../bench-utils.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/vue/vite.config.ts b/benchmarks/memory/server/scenarios/streaming-peak/vue/vite.config.ts
new file mode 100644
index 0000000000..9634031b31
--- /dev/null
+++ b/benchmarks/memory/server/scenarios/streaming-peak/vue/vite.config.ts
@@ -0,0 +1,29 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+const rootDir = fileURLToPath(new URL('.', import.meta.url))
+
+export default defineConfig({
+ root: rootDir,
+ plugins: [
+ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) &&
+ codspeedPlugin(),
+ tanstackStart({
+ srcDirectory: 'src',
+ }),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ },
+ test: {
+ name: '@benchmarks/memory-server streaming-peak (vue)',
+ watch: false,
+ environment: 'node',
+ },
+})
diff --git a/benchmarks/memory/server/tsconfig.json b/benchmarks/memory/server/tsconfig.json
new file mode 100644
index 0000000000..5d5dcbf825
--- /dev/null
+++ b/benchmarks/memory/server/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": ["bench-utils.ts"]
+}
diff --git a/benchmarks/memory/server/vitest.react.config.ts b/benchmarks/memory/server/vitest.react.config.ts
new file mode 100644
index 0000000000..44643b4d60
--- /dev/null
+++ b/benchmarks/memory/server/vitest.react.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ watch: false,
+ fileParallelism: false,
+ projects: ['./scenarios/*/react/vite.config.ts'],
+ },
+})
diff --git a/benchmarks/memory/server/vitest.solid.config.ts b/benchmarks/memory/server/vitest.solid.config.ts
new file mode 100644
index 0000000000..5c8185cdd9
--- /dev/null
+++ b/benchmarks/memory/server/vitest.solid.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ watch: false,
+ fileParallelism: false,
+ projects: ['./scenarios/*/solid/vite.config.ts'],
+ },
+})
diff --git a/benchmarks/memory/server/vitest.vue.config.ts b/benchmarks/memory/server/vitest.vue.config.ts
new file mode 100644
index 0000000000..01768185ee
--- /dev/null
+++ b/benchmarks/memory/server/vitest.vue.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ watch: false,
+ fileParallelism: false,
+ projects: ['./scenarios/*/vue/vite.config.ts'],
+ },
+})
diff --git a/package.json b/package.json
index 75bc153169..f544549330 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,14 @@
"benchmark:bundle-size:analyze": "node scripts/benchmarks/bundle-size/analyze.mjs",
"benchmark:client-nav": "nx run @benchmarks/client-nav:test:perf",
"benchmark:ssr": "nx run @benchmarks/ssr:test:perf",
+ "benchmark:memory:client:flame": "nx run @benchmarks/memory-client:test:flame:react --parallel=1",
+ "benchmark:memory:client:flame:react": "nx run @benchmarks/memory-client:test:flame:react --parallel=1",
+ "benchmark:memory:client:flame:solid": "nx run @benchmarks/memory-client:test:flame:solid --parallel=1",
+ "benchmark:memory:client:flame:vue": "nx run @benchmarks/memory-client:test:flame:vue --parallel=1",
+ "benchmark:memory:server:flame": "nx run @benchmarks/memory-server:test:flame:react --parallel=1",
+ "benchmark:memory:server:flame:react": "nx run @benchmarks/memory-server:test:flame:react --parallel=1",
+ "benchmark:memory:server:flame:solid": "nx run @benchmarks/memory-server:test:flame:solid --parallel=1",
+ "benchmark:memory:server:flame:vue": "nx run @benchmarks/memory-server:test:flame:vue --parallel=1",
"build": "nx affected --target=build --exclude=e2e/** --exclude=examples/**",
"build:all": "nx run-many --target=build --exclude=examples/** --exclude=e2e/**",
"watch": "pnpm run build:all && nx watch --all -- pnpm run build:all",
diff --git a/packages/vue-router/src/ssr/renderRouterToStream.tsx b/packages/vue-router/src/ssr/renderRouterToStream.tsx
index 3b122fda6b..efad4fc2eb 100644
--- a/packages/vue-router/src/ssr/renderRouterToStream.tsx
+++ b/packages/vue-router/src/ssr/renderRouterToStream.tsx
@@ -9,6 +9,11 @@ import {
import type { AnyRouter } from '@tanstack/router-core'
import type { Component } from 'vue'
+const isAbortError = (request: Request, error: unknown) =>
+ (request.signal.aborted && error === request.signal.reason) ||
+ (error instanceof Error && error.name === 'AbortError') ||
+ (error as any)?.code === 'ABORT_ERR'
+
function prependDoctype(
readable: globalThis.ReadableStream,
): NodeReadableStream {
@@ -126,25 +131,57 @@ export const renderRouterToStream = async ({
}
}
const abortVuePipe = (reason?: unknown) => {
- if (writerDone) return
+ if (writerDone) {
+ return
+ }
+
writerDone = true
void innerWriter
.abort(reason)
.catch(() => {})
.finally(releaseWriter)
}
+ const handleWriterError = (err: unknown) => {
+ if (isAbortError(request, err)) {
+ return
+ }
+
+ throw err
+ }
+ const handleWriteError = (err: unknown) => {
+ if (writerDone || isAbortError(request, err)) {
+ return
+ }
+
+ throw err
+ }
const vueWritable = new WritableStream({
write(chunk) {
- return innerWriter.write(chunk)
+ if (writerDone) {
+ return
+ }
+
+ return innerWriter.write(chunk).catch(handleWriteError)
},
close() {
+ if (writerDone) {
+ return
+ }
+
writerDone = true
- return innerWriter.close().finally(releaseWriter)
+ return innerWriter.close().catch(handleWriterError).finally(releaseWriter)
},
abort(reason) {
+ if (writerDone) {
+ return
+ }
+
writerDone = true
- return innerWriter.abort(reason).finally(releaseWriter)
+ return innerWriter
+ .abort(reason)
+ .catch(handleWriterError)
+ .finally(releaseWriter)
},
})
diff --git a/packages/vue-router/tests/renderRouterToStream.test.tsx b/packages/vue-router/tests/renderRouterToStream.test.tsx
index 781096185f..527bfa0e03 100644
--- a/packages/vue-router/tests/renderRouterToStream.test.tsx
+++ b/packages/vue-router/tests/renderRouterToStream.test.tsx
@@ -117,7 +117,7 @@ describe('renderRouterToStream - sync setup failures', () => {
}
})
- test('request abort aborts Vue writer and terminates response stream', async () => {
+ test('request abort drops later Vue writes and terminates response stream', async () => {
let vueWriter: WritableStreamDefaultWriter | undefined
rendererMocks.pipeToWebWritable.mockImplementationOnce(
(
@@ -149,7 +149,7 @@ describe('renderRouterToStream - sync setup failures', () => {
await expect(
vueWriter!.write(new TextEncoder().encode('')),
- ).rejects.toBeTruthy()
+ ).resolves.toBeUndefined()
const terminated = await Promise.race([
drainBody(response),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index efb2ac9b5f..4917d0514f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -311,7 +311,135 @@ importers:
version: 2.11.11(@testing-library/jest-dom@6.6.3)(solid-js@1.9.12)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
vitest:
specifier: ^4.1.4
- version: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@27.0.0(postcss@8.5.15))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+
+ benchmarks/memory/client:
+ dependencies:
+ '@tanstack/react-router':
+ specifier: workspace:*
+ version: link:../../../packages/react-router
+ '@tanstack/router-core':
+ specifier: workspace:*
+ version: link:../../../packages/router-core
+ '@tanstack/solid-router':
+ specifier: workspace:*
+ version: link:../../../packages/solid-router
+ '@tanstack/vue-router':
+ specifier: workspace:*
+ version: link:../../../packages/vue-router
+ react:
+ specifier: ^19.2.3
+ version: 19.2.3
+ react-dom:
+ specifier: ^19.2.3
+ version: 19.2.3(react@19.2.3)
+ solid-js:
+ specifier: 1.9.12
+ version: 1.9.12
+ vue:
+ specifier: ^3.5.16
+ version: 3.5.25(typescript@6.0.2)
+ devDependencies:
+ '@codspeed/vitest-plugin':
+ specifier: ^5.5.0
+ version: 5.5.0(tinybench@2.9.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vitest@4.1.4)
+ '@datadog/pprof':
+ specifier: ^5.13.2
+ version: 5.13.2
+ '@platformatic/flame':
+ specifier: ^1.6.0
+ version: 1.6.0
+ '@testing-library/react':
+ specifier: ^16.2.0
+ version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@types/jsdom':
+ specifier: 28.0.0
+ version: 28.0.0
+ '@vitejs/plugin-react':
+ specifier: ^6.0.1
+ version: 6.0.1(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.5
+ version: 6.0.5(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(typescript@6.0.2))
+ '@vitejs/plugin-vue-jsx':
+ specifier: ^5.1.5
+ version: 5.1.5(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(typescript@6.0.2))
+ jsdom:
+ specifier: 29.1.1
+ version: 29.1.1(@noble/hashes@2.0.1)
+ typescript:
+ specifier: ^6.0.2
+ version: 6.0.2
+ vite:
+ specifier: ^8.0.14
+ version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)
+ vite-plugin-solid:
+ specifier: ^2.11.11
+ version: 2.11.11(@testing-library/jest-dom@6.6.3)(solid-js@1.9.12)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ vitest:
+ specifier: ^4.1.4
+ version: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+
+ benchmarks/memory/server:
+ dependencies:
+ '@tanstack/react-router':
+ specifier: workspace:*
+ version: link:../../../packages/react-router
+ '@tanstack/react-start':
+ specifier: workspace:*
+ version: link:../../../packages/react-start
+ '@tanstack/solid-router':
+ specifier: workspace:*
+ version: link:../../../packages/solid-router
+ '@tanstack/solid-start':
+ specifier: workspace:*
+ version: link:../../../packages/solid-start
+ '@tanstack/vue-router':
+ specifier: workspace:*
+ version: link:../../../packages/vue-router
+ '@tanstack/vue-start':
+ specifier: workspace:*
+ version: link:../../../packages/vue-start
+ react:
+ specifier: ^19.2.3
+ version: 19.2.3
+ react-dom:
+ specifier: ^19.2.3
+ version: 19.2.3(react@19.2.3)
+ solid-js:
+ specifier: 1.9.12
+ version: 1.9.12
+ vue:
+ specifier: ^3.5.16
+ version: 3.5.25(typescript@6.0.2)
+ devDependencies:
+ '@codspeed/vitest-plugin':
+ specifier: ^5.5.0
+ version: 5.5.0(tinybench@2.9.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vitest@4.1.4)
+ '@datadog/pprof':
+ specifier: ^5.13.2
+ version: 5.13.2
+ '@platformatic/flame':
+ specifier: ^1.6.0
+ version: 1.6.0
+ '@vitejs/plugin-react':
+ specifier: ^6.0.1
+ version: 6.0.1(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ '@vitejs/plugin-vue-jsx':
+ specifier: ^5.1.5
+ version: 5.1.5(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(typescript@6.0.2))
+ typescript:
+ specifier: ^6.0.2
+ version: 6.0.2
+ vite:
+ specifier: ^8.0.14
+ version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)
+ vite-plugin-solid:
+ specifier: ^2.11.11
+ version: 2.11.11(@testing-library/jest-dom@6.6.3)(solid-js@1.9.12)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ vitest:
+ specifier: ^4.1.4
+ version: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
benchmarks/ssr:
dependencies:
@@ -369,7 +497,7 @@ importers:
version: 2.11.11(@testing-library/jest-dom@6.6.3)(solid-js@1.9.12)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
vitest:
specifier: ^4.1.4
- version: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@27.0.0(postcss@8.5.15))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
e2e/e2e-utils:
devDependencies:
@@ -414,7 +542,7 @@ importers:
version: 5.9.2
vitest:
specifier: ^4.1.4
- version: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@27.0.0(postcss@8.5.15))(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
e2e/react-router/basepath-file-based:
dependencies:
@@ -1859,7 +1987,7 @@ importers:
version: 6.0.1(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
nitro:
specifier: ^3.0.260311-beta
- version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
sass:
specifier: ^1.97.2
version: 1.97.2
@@ -2024,7 +2152,7 @@ importers:
version: 2.0.1
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.1)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.1)(@rspack/core@2.0.5(@swc/helpers@0.5.21))
'@tanstack/router-e2e-utils':
specifier: workspace:^
version: link:../../e2e-utils
@@ -2088,7 +2216,7 @@ importers:
version: 6.0.1(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
nitro:
specifier: ^3.0.260311-beta
- version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
srvx:
specifier: ^0.11.9
version: 0.11.12
@@ -2186,7 +2314,7 @@ importers:
version: 9.2.1
nitro:
specifier: ^3.0.260311-beta
- version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
typescript:
specifier: ^6.0.2
version: 6.0.2
@@ -3222,7 +3350,7 @@ importers:
version: 6.0.1(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
nitro:
specifier: ^3.0.260311-beta
- version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
typescript:
specifier: ^6.0.2
version: 6.0.2
@@ -3243,7 +3371,7 @@ importers:
version: link:../../../packages/start-static-server-functions
nitro:
specifier: ^3.0.1-alpha.2
- version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rolldown@1.0.2)(rollup@4.56.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.0.2)(rollup@4.56.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
react:
specifier: ^19.2.3
version: 19.2.3
@@ -8937,7 +9065,7 @@ importers:
version: 6.0.1(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
nitro:
specifier: ^3.0.260311-beta
- version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
tailwindcss:
specifier: ^4.2.2
version: 4.2.2
@@ -9679,7 +9807,7 @@ importers:
version: 0.5.20(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
nitro:
specifier: npm:nitro-nightly@latest
- version: nitro-nightly@3.0.260522-beta(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: nitro-nightly@3.0.260522-beta(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
tailwindcss:
specifier: ^4.1.18
version: 4.2.2
@@ -11428,7 +11556,7 @@ importers:
version: 25.0.9
nitro:
specifier: ^3.0.260311-beta
- version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
tailwindcss:
specifier: ^4.2.2
version: 4.2.2
@@ -11640,7 +11768,7 @@ importers:
version: 25.0.9
nitro:
specifier: ^3.0.260311-beta
- version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
tailwindcss:
specifier: ^4.2.2
version: 4.2.2
@@ -13600,9 +13728,21 @@ packages:
'@asamuzakjp/css-color@4.0.5':
resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==}
+ '@asamuzakjp/css-color@5.1.11':
+ resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
'@asamuzakjp/dom-selector@6.5.6':
resolution: {integrity: sha512-Mj3Hu9ymlsERd7WOsUKNUZnJYL4IZ/I9wVVYgtvOsWYiEFbkQ4G7VRIh2USxTVW4BBDIsLG+gBUgqOqf2Kvqow==}
+ '@asamuzakjp/dom-selector@7.1.1':
+ resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ '@asamuzakjp/generational-cache@1.0.1':
+ resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
'@asamuzakjp/nwsapi@2.3.9':
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
@@ -13940,6 +14080,10 @@ packages:
'@braidai/lang@1.1.2':
resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==}
+ '@bramus/specificity@2.4.2':
+ resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
+ hasBin: true
+
'@bundled-es-modules/cookie@2.0.1':
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
@@ -14191,6 +14335,10 @@ packages:
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
engines: {node: '>=18'}
+ '@csstools/color-helpers@6.0.2':
+ resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==}
+ engines: {node: '>=20.19.0'}
+
'@csstools/css-calc@2.1.1':
resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==}
engines: {node: '>=18'}
@@ -14205,6 +14353,13 @@ packages:
'@csstools/css-parser-algorithms': ^3.0.5
'@csstools/css-tokenizer': ^3.0.4
+ '@csstools/css-calc@3.2.1':
+ resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^4.0.0
+ '@csstools/css-tokenizer': ^4.0.0
+
'@csstools/css-color-parser@3.0.7':
resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==}
engines: {node: '>=18'}
@@ -14219,6 +14374,13 @@ packages:
'@csstools/css-parser-algorithms': ^3.0.5
'@csstools/css-tokenizer': ^3.0.4
+ '@csstools/css-color-parser@4.1.3':
+ resolution: {integrity: sha512-DOgvIPkikIOixQRlD4YF31VN6fLLUTdrzhfRbis8vm0kMTgIbEPX0Ip/YX9fOeV9iywAS4sUUbTclpan7yYP8Q==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^4.0.0
+ '@csstools/css-tokenizer': ^4.0.0
+
'@csstools/css-parser-algorithms@3.0.4':
resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==}
engines: {node: '>=18'}
@@ -14231,12 +14393,26 @@ packages:
peerDependencies:
'@csstools/css-tokenizer': ^3.0.4
+ '@csstools/css-parser-algorithms@4.0.0':
+ resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^4.0.0
+
'@csstools/css-syntax-patches-for-csstree@1.0.14':
resolution: {integrity: sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
+ '@csstools/css-syntax-patches-for-csstree@1.1.5':
+ resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==}
+ peerDependencies:
+ css-tree: ^3.2.1
+ peerDependenciesMeta:
+ css-tree:
+ optional: true
+
'@csstools/css-tokenizer@3.0.3':
resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
engines: {node: '>=18'}
@@ -14245,6 +14421,10 @@ packages:
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
+ '@csstools/css-tokenizer@4.0.0':
+ resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
+ engines: {node: '>=20.19.0'}
+
'@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
@@ -15216,6 +15396,15 @@ packages:
resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@exodus/bytes@1.15.1':
+ resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ peerDependencies:
+ '@noble/hashes': ^1.8.0 || ^2.0.0
+ peerDependenciesMeta:
+ '@noble/hashes':
+ optional: true
+
'@fastify/accept-negotiator@2.0.1':
resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
@@ -20996,6 +21185,10 @@ packages:
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+ css-tree@3.2.1:
+ resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
css-what@6.1.0:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
engines: {node: '>= 6'}
@@ -21041,6 +21234,10 @@ packages:
resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==}
engines: {node: '>=20'}
+ data-urls@7.0.0:
+ resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
dataloader@1.4.0:
resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
@@ -21097,6 +21294,9 @@ packages:
decimal.js@10.5.0:
resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
dedent@1.5.1:
resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==}
peerDependencies:
@@ -21439,6 +21639,10 @@ packages:
resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==}
engines: {node: '>=0.12'}
+ entities@8.0.0:
+ resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
+ engines: {node: '>=20.19.0'}
+
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@@ -22350,6 +22554,10 @@ packages:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
+ html-encoding-sniffer@6.0.0:
+ resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
html-entities@2.3.3:
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
@@ -22892,6 +23100,15 @@ packages:
canvas:
optional: true
+ jsdom@29.1.1:
+ resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@@ -23216,6 +23433,10 @@ packages:
resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==}
engines: {node: 20 || >=22}
+ lru-cache@11.5.1:
+ resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==}
+ engines: {node: 20 || >=22}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -23285,6 +23506,9 @@ packages:
mdn-data@2.12.2:
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
+ mdn-data@2.27.1:
+ resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
+
media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
@@ -23948,6 +24172,9 @@ packages:
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+ parse5@8.0.1:
+ resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
+
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
@@ -25423,6 +25650,10 @@ packages:
resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==}
engines: {node: '>=16'}
+ tough-cookie@6.0.1:
+ resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
+ engines: {node: '>=16'}
+
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -25634,6 +25865,10 @@ packages:
resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==}
engines: {node: '>=20.18.1'}
+ undici@7.27.2:
+ resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==}
+ engines: {node: '>=20.18.1'}
+
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
@@ -26318,6 +26553,10 @@ packages:
resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==}
engines: {node: '>=20'}
+ webidl-conversions@8.0.1:
+ resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
+ engines: {node: '>=20'}
+
webpack-cli@5.1.4:
resolution: {integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==}
engines: {node: '>=14.15.0'}
@@ -26412,6 +26651,10 @@ packages:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
+ whatwg-mimetype@5.0.0:
+ resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
+ engines: {node: '>=20'}
+
whatwg-url@14.1.0:
resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==}
engines: {node: '>=18'}
@@ -26420,6 +26663,10 @@ packages:
resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==}
engines: {node: '>=20'}
+ whatwg-url@16.0.1:
+ resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
@@ -26701,6 +26948,14 @@ snapshots:
'@csstools/css-tokenizer': 3.0.4
lru-cache: 11.2.2
+ '@asamuzakjp/css-color@5.1.11':
+ dependencies:
+ '@asamuzakjp/generational-cache': 1.0.1
+ '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.3(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
'@asamuzakjp/dom-selector@6.5.6':
dependencies:
'@asamuzakjp/nwsapi': 2.3.9
@@ -26709,6 +26964,16 @@ snapshots:
is-potential-custom-element-name: 1.0.1
lru-cache: 11.2.2
+ '@asamuzakjp/dom-selector@7.1.1':
+ dependencies:
+ '@asamuzakjp/generational-cache': 1.0.1
+ '@asamuzakjp/nwsapi': 2.3.9
+ bidi-js: 1.0.3
+ css-tree: 3.2.1
+ is-potential-custom-element-name: 1.0.1
+
+ '@asamuzakjp/generational-cache@1.0.1': {}
+
'@asamuzakjp/nwsapi@2.3.9': {}
'@assemblyscript/loader@0.19.23': {}
@@ -27252,6 +27517,10 @@ snapshots:
'@braidai/lang@1.1.2': {}
+ '@bramus/specificity@2.4.2':
+ dependencies:
+ css-tree: 3.1.0
+
'@bundled-es-modules/cookie@2.0.1':
dependencies:
cookie: 0.7.2
@@ -27585,7 +27854,7 @@ snapshots:
'@codspeed/core': 5.5.0
tinybench: 2.9.0
vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)
- vitest: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@27.0.0(postcss@8.5.15))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ vitest: 4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
transitivePeerDependencies:
- debug
- supports-color
@@ -27626,6 +27895,8 @@ snapshots:
'@csstools/color-helpers@5.1.0': {}
+ '@csstools/color-helpers@6.0.2': {}
+
'@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
dependencies:
'@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
@@ -27636,6 +27907,11 @@ snapshots:
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
+ '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
'@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
dependencies:
'@csstools/color-helpers': 5.0.1
@@ -27650,6 +27926,13 @@ snapshots:
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
+ '@csstools/css-color-parser@4.1.3(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/color-helpers': 6.0.2
+ '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
'@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)':
dependencies:
'@csstools/css-tokenizer': 3.0.3
@@ -27658,14 +27941,24 @@ snapshots:
dependencies:
'@csstools/css-tokenizer': 3.0.4
+ '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/css-tokenizer': 4.0.0
+
'@csstools/css-syntax-patches-for-csstree@1.0.14(postcss@8.5.15)':
dependencies:
postcss: 8.5.15
+ '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)':
+ optionalDependencies:
+ css-tree: 3.2.1
+
'@csstools/css-tokenizer@3.0.3': {}
'@csstools/css-tokenizer@3.0.4': {}
+ '@csstools/css-tokenizer@4.0.0': {}
+
'@dabh/diagnostics@2.0.8':
dependencies:
'@so-ric/colorspace': 1.1.6
@@ -28352,6 +28645,10 @@ snapshots:
'@eslint/core': 0.12.0
levn: 0.4.1
+ '@exodus/bytes@1.15.1(@noble/hashes@2.0.1)':
+ optionalDependencies:
+ '@noble/hashes': 2.0.1
+
'@fastify/accept-negotiator@2.0.1': {}
'@fastify/ajv-compiler@4.0.5':
@@ -31449,9 +31746,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@rsbuild/plugin-react@2.0.0(@rsbuild/core@2.0.1)(@rspack/core@2.0.5(@swc/helpers@0.5.23))':
+ '@rsbuild/plugin-react@2.0.0(@rsbuild/core@2.0.1)(@rspack/core@2.0.5(@swc/helpers@0.5.21))':
dependencies:
- '@rspack/plugin-react-refresh': 2.0.0(@rspack/core@2.0.5(@swc/helpers@0.5.23))(react-refresh@0.18.0)
+ '@rspack/plugin-react-refresh': 2.0.0(@rspack/core@2.0.5(@swc/helpers@0.5.21))(react-refresh@0.18.0)
react-refresh: 0.18.0
optionalDependencies:
'@rsbuild/core': 2.0.1
@@ -31642,6 +31939,13 @@ snapshots:
optionalDependencies:
'@swc/helpers': 0.5.23
+ '@rspack/core@2.0.5(@swc/helpers@0.5.21)':
+ dependencies:
+ '@rspack/binding': 2.0.5
+ optionalDependencies:
+ '@swc/helpers': 0.5.21
+ optional: true
+
'@rspack/core@2.0.5(@swc/helpers@0.5.23)':
dependencies:
'@rspack/binding': 2.0.5
@@ -31650,6 +31954,12 @@ snapshots:
'@rspack/lite-tapable@1.1.0': {}
+ '@rspack/plugin-react-refresh@2.0.0(@rspack/core@2.0.5(@swc/helpers@0.5.21))(react-refresh@0.18.0)':
+ dependencies:
+ react-refresh: 0.18.0
+ optionalDependencies:
+ '@rspack/core': 2.0.5(@swc/helpers@0.5.21)
+
'@rspack/plugin-react-refresh@2.0.0(@rspack/core@2.0.5(@swc/helpers@0.5.23))(react-refresh@0.18.0)':
dependencies:
react-refresh: 0.18.0
@@ -34995,6 +35305,11 @@ snapshots:
mdn-data: 2.12.2
source-map-js: 1.2.1
+ css-tree@3.2.1:
+ dependencies:
+ mdn-data: 2.27.1
+ source-map-js: 1.2.1
+
css-what@6.1.0: {}
css.escape@1.5.1: {}
@@ -35036,6 +35351,13 @@ snapshots:
whatwg-mimetype: 4.0.0
whatwg-url: 15.1.0
+ data-urls@7.0.0(@noble/hashes@2.0.1):
+ dependencies:
+ whatwg-mimetype: 5.0.0
+ whatwg-url: 16.0.1(@noble/hashes@2.0.1)
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
dataloader@1.4.0: {}
date-fns@2.30.0:
@@ -35064,6 +35386,8 @@ snapshots:
decimal.js@10.5.0: {}
+ decimal.js@10.6.0: {}
+
dedent@1.5.1(babel-plugin-macros@3.1.0):
optionalDependencies:
babel-plugin-macros: 3.1.0
@@ -35387,6 +35711,8 @@ snapshots:
entities@6.0.0: {}
+ entities@8.0.0: {}
+
env-paths@2.2.1: {}
env-paths@3.0.0: {}
@@ -36602,6 +36928,12 @@ snapshots:
dependencies:
whatwg-encoding: 3.1.1
+ html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1):
+ dependencies:
+ '@exodus/bytes': 1.15.1(@noble/hashes@2.0.1)
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
html-entities@2.3.3: {}
html-link-extractor@1.0.5:
@@ -37178,6 +37510,32 @@ snapshots:
- supports-color
- utf-8-validate
+ jsdom@29.1.1(@noble/hashes@2.0.1):
+ dependencies:
+ '@asamuzakjp/css-color': 5.1.11
+ '@asamuzakjp/dom-selector': 7.1.1
+ '@bramus/specificity': 2.4.2
+ '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1)
+ '@exodus/bytes': 1.15.1(@noble/hashes@2.0.1)
+ css-tree: 3.2.1
+ data-urls: 7.0.0(@noble/hashes@2.0.1)
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 6.0.0(@noble/hashes@2.0.1)
+ is-potential-custom-element-name: 1.0.1
+ lru-cache: 11.5.1
+ parse5: 8.0.1
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 6.0.1
+ undici: 7.27.2
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 8.0.1
+ whatwg-mimetype: 5.0.0
+ whatwg-url: 16.0.1(@noble/hashes@2.0.1)
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
jsesc@3.1.0: {}
json-buffer@3.0.1: {}
@@ -37479,6 +37837,8 @@ snapshots:
lru-cache@11.2.2: {}
+ lru-cache@11.5.1: {}
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -37539,6 +37899,8 @@ snapshots:
mdn-data@2.12.2: {}
+ mdn-data@2.27.1: {}
+
media-typer@0.3.0: {}
media-typer@1.1.0: {}
@@ -37782,7 +38144,7 @@ snapshots:
nf3@0.3.6: {}
- nitro-nightly@3.0.260522-beta(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
+ nitro-nightly@3.0.260522-beta(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
dependencies:
consola: 3.4.2
crossws: 0.4.5(srvx@0.11.15)
@@ -37797,7 +38159,7 @@ snapshots:
rolldown: 1.0.2
srvx: 0.11.15
unenv: 2.0.0-rc.24
- unstorage: 2.0.0-alpha.7(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ofetch@2.0.0-alpha.3)
+ unstorage: 2.0.0-alpha.7(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3)
optionalDependencies:
dotenv: 17.4.2
giget: 2.0.0
@@ -37834,7 +38196,7 @@ snapshots:
- sqlite3
- uploadthing
- nitro@3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rolldown@1.0.2)(rollup@4.56.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
+ nitro@3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.0.2)(rollup@4.56.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
dependencies:
consola: 3.4.2
crossws: 0.4.3(srvx@0.10.1)
@@ -37849,7 +38211,7 @@ snapshots:
srvx: 0.10.1
undici: 7.24.4
unenv: 2.0.0-rc.24
- unstorage: 2.0.0-alpha.5(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2)(lru-cache@11.2.2)(ofetch@2.0.0-alpha.3)
+ unstorage: 2.0.0-alpha.5(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2)(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3)
optionalDependencies:
rolldown: 1.0.2
rollup: 4.56.0
@@ -37885,7 +38247,7 @@ snapshots:
- sqlite3
- uploadthing
- nitro@3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
+ nitro@3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
dependencies:
consola: 3.4.2
crossws: 0.4.4(srvx@0.11.15)
@@ -37900,7 +38262,7 @@ snapshots:
rolldown: 1.0.0-rc.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
srvx: 0.11.15
unenv: 2.0.0-rc.24
- unstorage: 2.0.0-alpha.6(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ofetch@2.0.0-alpha.3)
+ unstorage: 2.0.0-alpha.6(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3)
optionalDependencies:
dotenv: 17.4.2
giget: 2.0.0
@@ -38555,6 +38917,10 @@ snapshots:
dependencies:
entities: 6.0.0
+ parse5@8.0.1:
+ dependencies:
+ entities: 8.0.0
+
parseurl@1.3.3: {}
pascal-case@3.1.2:
@@ -40200,6 +40566,10 @@ snapshots:
dependencies:
tldts: 7.0.16
+ tough-cookie@6.0.1:
+ dependencies:
+ tldts: 7.0.16
+
tr46@0.0.3: {}
tr46@5.0.0:
@@ -40375,6 +40745,8 @@ snapshots:
undici@7.24.4: {}
+ undici@7.27.2: {}
+
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
@@ -40476,27 +40848,29 @@ snapshots:
db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3)
ioredis: 5.9.2
- unstorage@2.0.0-alpha.5(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2)(lru-cache@11.2.2)(ofetch@2.0.0-alpha.3):
+ unstorage@2.0.0-alpha.5(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2)(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3):
optionalDependencies:
'@netlify/blobs': 10.1.0
chokidar: 5.0.0
db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3)
ioredis: 5.9.2
- lru-cache: 11.2.2
+ lru-cache: 11.5.1
ofetch: 2.0.0-alpha.3
- unstorage@2.0.0-alpha.6(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ofetch@2.0.0-alpha.3):
+ unstorage@2.0.0-alpha.6(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3):
optionalDependencies:
'@netlify/blobs': 10.1.0
chokidar: 5.0.0
db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3)
+ lru-cache: 11.5.1
ofetch: 2.0.0-alpha.3
- unstorage@2.0.0-alpha.7(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ofetch@2.0.0-alpha.3):
+ unstorage@2.0.0-alpha.7(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3):
optionalDependencies:
'@netlify/blobs': 10.1.0
chokidar: 5.0.0
db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3)
+ lru-cache: 11.5.1
ofetch: 2.0.0-alpha.3
untun@0.1.3:
@@ -40714,10 +41088,10 @@ snapshots:
transitivePeerDependencies:
- msw
- vitest@4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@27.0.0(postcss@8.5.15))(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
+ vitest@4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@27.0.0(postcss@8.5.15))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
dependencies:
'@vitest/expect': 4.1.4
- '@vitest/mocker': 4.1.4(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ '@vitest/mocker': 4.1.4(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
'@vitest/pretty-format': 4.1.4
'@vitest/runner': 4.1.4
'@vitest/snapshot': 4.1.4
@@ -40743,7 +41117,36 @@ snapshots:
transitivePeerDependencies:
- msw
- vitest@4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@27.0.0(postcss@8.5.15))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
+ vitest@4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
+ dependencies:
+ '@vitest/expect': 4.1.4
+ '@vitest/mocker': 4.1.4(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
+ '@vitest/pretty-format': 4.1.4
+ '@vitest/runner': 4.1.4
+ '@vitest/snapshot': 4.1.4
+ '@vitest/spy': 4.1.4
+ '@vitest/utils': 4.1.4
+ es-module-lexer: 2.0.0
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ obug: 2.1.1
+ pathe: 2.0.3
+ picomatch: 4.0.3
+ std-env: 4.1.0
+ tinybench: 2.9.0
+ tinyexec: 1.0.2
+ tinyglobby: 0.2.15
+ tinyrainbow: 3.1.0
+ vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 25.0.9
+ '@vitest/ui': 4.1.4(vitest@4.1.4)
+ jsdom: 29.1.1(@noble/hashes@2.0.1)
+ transitivePeerDependencies:
+ - msw
+
+ vitest@4.1.4(@types/node@25.0.9)(@vitest/ui@4.1.4)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)):
dependencies:
'@vitest/expect': 4.1.4
'@vitest/mocker': 4.1.4(msw@2.7.0(@types/node@25.0.9)(typescript@6.0.2))(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))
@@ -40768,7 +41171,7 @@ snapshots:
optionalDependencies:
'@types/node': 25.0.9
'@vitest/ui': 4.1.4(vitest@4.1.4)
- jsdom: 27.0.0(postcss@8.5.15)
+ jsdom: 29.1.1(@noble/hashes@2.0.1)
transitivePeerDependencies:
- msw
@@ -40909,6 +41312,8 @@ snapshots:
webidl-conversions@8.0.0: {}
+ webidl-conversions@8.0.1: {}
+
webpack-cli@5.1.4(webpack-dev-server@5.2.4)(webpack@5.97.1):
dependencies:
'@discoveryjs/json-ext': 0.5.7
@@ -41100,6 +41505,8 @@ snapshots:
whatwg-mimetype@4.0.0: {}
+ whatwg-mimetype@5.0.0: {}
+
whatwg-url@14.1.0:
dependencies:
tr46: 5.0.0
@@ -41110,6 +41517,14 @@ snapshots:
tr46: 6.0.0
webidl-conversions: 8.0.0
+ whatwg-url@16.0.1(@noble/hashes@2.0.1):
+ dependencies:
+ '@exodus/bytes': 1.15.1(@noble/hashes@2.0.1)
+ tr46: 6.0.0
+ webidl-conversions: 8.0.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 25cba27b56..2c130509f2 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -7,6 +7,7 @@ trustPolicy: 'no-downgrade'
packages:
- 'packages/*'
- 'benchmarks/*'
+ - 'benchmarks/memory/*'
- 'examples/react/*'
- 'examples/solid/*'
- 'examples/vue/*'