From 854ac95b0dc1cd116c1985f1e51f68c62c59d080 Mon Sep 17 00:00:00 2001
From: Flo
Date: Sun, 14 Jun 2026 12:51:10 +0200
Subject: [PATCH 01/25] benchmark: memory (codspeed) (#7623)
---
.github/workflows/client-nav-benchmarks.yml | 26 +-
.gitignore | 3 +
benchmarks/memory/README.md | 186 +++++++
benchmarks/memory/client/bench-utils.ts | 20 +
benchmarks/memory/client/benchmark.ts | 7 +
benchmarks/memory/client/flame-runner.ts | 14 +
benchmarks/memory/client/jsdom.ts | 51 ++
benchmarks/memory/client/lifecycle.ts | 46 ++
benchmarks/memory/client/package.json | 260 ++++++++++
.../react/memory.bench.ts | 21 +
.../react/memory.flame.ts | 4 +
.../react/project.json | 54 ++
.../interrupted-navigations/react/setup.ts | 19 +
.../interrupted-navigations/react/src/app.tsx | 37 ++
.../react/src/routeTree.gen.ts | 95 ++++
.../react/src/router.tsx | 17 +
.../react/src/routes/__root.tsx | 9 +
.../react/src/routes/fast.$id.tsx | 21 +
.../react/src/routes/index.tsx | 9 +
.../react/src/routes/slow.$id.tsx | 21 +
.../react/tsconfig.json | 19 +
.../react/vite.config.ts | 34 ++
.../interrupted-navigations/shared.ts | 320 ++++++++++++
.../interrupted-navigations/slow-loaders.ts | 54 ++
.../solid/memory.bench.ts | 21 +
.../solid/memory.flame.ts | 4 +
.../solid/project.json | 54 ++
.../interrupted-navigations/solid/setup.ts | 19 +
.../interrupted-navigations/solid/src/app.tsx | 35 ++
.../solid/src/routeTree.gen.ts | 95 ++++
.../solid/src/router.tsx | 17 +
.../solid/src/routes/__root.tsx | 9 +
.../solid/src/routes/fast.$id.tsx | 21 +
.../solid/src/routes/index.tsx | 9 +
.../solid/src/routes/slow.$id.tsx | 21 +
.../solid/tsconfig.json | 19 +
.../solid/vite.config.ts | 34 ++
.../vue/memory.bench.ts | 21 +
.../vue/memory.flame.ts | 4 +
.../interrupted-navigations/vue/project.json | 54 ++
.../interrupted-navigations/vue/setup.ts | 19 +
.../interrupted-navigations/vue/src/app.tsx | 42 ++
.../vue/src/routeTree.gen.ts | 95 ++++
.../vue/src/router.tsx | 17 +
.../vue/src/routes/__root.tsx | 9 +
.../vue/src/routes/fast.$id.tsx | 21 +
.../vue/src/routes/index.tsx | 9 +
.../vue/src/routes/slow.$id.tsx | 21 +
.../interrupted-navigations/vue/tsconfig.json | 19 +
.../vue/vite.config.ts | 36 ++
.../loader-data-retention/loader-data.ts | 48 ++
.../react/memory.bench.ts | 21 +
.../react/memory.flame.ts | 4 +
.../loader-data-retention/react/project.json | 54 ++
.../loader-data-retention/react/setup.ts | 14 +
.../loader-data-retention/react/src/app.tsx | 33 ++
.../react/src/routeTree.gen.ts | 102 ++++
.../react/src/router.tsx | 19 +
.../react/src/routes/__root.tsx | 9 +
.../react/src/routes/page.$id.tsx | 21 +
.../react/src/routes/shell.index.tsx | 9 +
.../react/src/routes/shell.tsx | 9 +
.../loader-data-retention/react/tsconfig.json | 19 +
.../react/vite.config.ts | 34 ++
.../scenarios/loader-data-retention/shared.ts | 187 +++++++
.../solid/memory.bench.ts | 21 +
.../solid/memory.flame.ts | 4 +
.../loader-data-retention/solid/project.json | 54 ++
.../loader-data-retention/solid/setup.ts | 14 +
.../loader-data-retention/solid/src/app.tsx | 31 ++
.../solid/src/routeTree.gen.ts | 102 ++++
.../solid/src/router.tsx | 19 +
.../solid/src/routes/__root.tsx | 9 +
.../solid/src/routes/page.$id.tsx | 21 +
.../solid/src/routes/shell.index.tsx | 9 +
.../solid/src/routes/shell.tsx | 9 +
.../loader-data-retention/solid/tsconfig.json | 19 +
.../solid/vite.config.ts | 34 ++
.../loader-data-retention/vue/memory.bench.ts | 21 +
.../loader-data-retention/vue/memory.flame.ts | 4 +
.../loader-data-retention/vue/project.json | 54 ++
.../loader-data-retention/vue/setup.ts | 14 +
.../loader-data-retention/vue/src/app.tsx | 38 ++
.../vue/src/routeTree.gen.ts | 102 ++++
.../loader-data-retention/vue/src/router.tsx | 19 +
.../vue/src/routes/__root.tsx | 9 +
.../vue/src/routes/page.$id.tsx | 22 +
.../vue/src/routes/shell.index.tsx | 9 +
.../vue/src/routes/shell.tsx | 9 +
.../loader-data-retention/vue/tsconfig.json | 19 +
.../loader-data-retention/vue/vite.config.ts | 36 ++
.../mount-unmount/react/memory.bench.ts | 21 +
.../mount-unmount/react/memory.flame.ts | 4 +
.../mount-unmount/react/project.json | 54 ++
.../scenarios/mount-unmount/react/setup.ts | 13 +
.../scenarios/mount-unmount/react/src/app.tsx | 29 ++
.../mount-unmount/react/src/routeTree.gen.ts | 59 +++
.../mount-unmount/react/src/router.tsx | 17 +
.../mount-unmount/react/src/routes/__root.tsx | 9 +
.../mount-unmount/react/src/routes/a.tsx | 12 +
.../mount-unmount/react/tsconfig.json | 19 +
.../mount-unmount/react/vite.config.ts | 34 ++
.../client/scenarios/mount-unmount/shared.ts | 74 +++
.../mount-unmount/solid/memory.bench.ts | 21 +
.../mount-unmount/solid/memory.flame.ts | 4 +
.../mount-unmount/solid/project.json | 54 ++
.../scenarios/mount-unmount/solid/setup.ts | 13 +
.../scenarios/mount-unmount/solid/src/app.tsx | 27 +
.../mount-unmount/solid/src/routeTree.gen.ts | 59 +++
.../mount-unmount/solid/src/router.tsx | 17 +
.../mount-unmount/solid/src/routes/__root.tsx | 9 +
.../mount-unmount/solid/src/routes/a.tsx | 12 +
.../mount-unmount/solid/tsconfig.json | 19 +
.../mount-unmount/solid/vite.config.ts | 34 ++
.../mount-unmount/vue/memory.bench.ts | 21 +
.../mount-unmount/vue/memory.flame.ts | 4 +
.../scenarios/mount-unmount/vue/project.json | 54 ++
.../scenarios/mount-unmount/vue/setup.ts | 13 +
.../scenarios/mount-unmount/vue/src/app.tsx | 34 ++
.../mount-unmount/vue/src/routeTree.gen.ts | 59 +++
.../mount-unmount/vue/src/router.tsx | 17 +
.../mount-unmount/vue/src/routes/__root.tsx | 9 +
.../mount-unmount/vue/src/routes/a.tsx | 12 +
.../scenarios/mount-unmount/vue/tsconfig.json | 19 +
.../mount-unmount/vue/vite.config.ts | 36 ++
.../navigation-churn/react/memory.bench.ts | 21 +
.../navigation-churn/react/memory.flame.ts | 4 +
.../navigation-churn/react/project.json | 54 ++
.../scenarios/navigation-churn/react/setup.ts | 13 +
.../navigation-churn/react/src/app.tsx | 31 ++
.../react/src/routeTree.gen.ts | 77 +++
.../navigation-churn/react/src/router.tsx | 17 +
.../react/src/routes/__root.tsx | 9 +
.../navigation-churn/react/src/routes/a.tsx | 14 +
.../navigation-churn/react/src/routes/b.tsx | 14 +
.../navigation-churn/react/tsconfig.json | 19 +
.../navigation-churn/react/vite.config.ts | 34 ++
.../scenarios/navigation-churn/shared.ts | 129 +++++
.../navigation-churn/solid/memory.bench.ts | 21 +
.../navigation-churn/solid/memory.flame.ts | 4 +
.../navigation-churn/solid/project.json | 54 ++
.../scenarios/navigation-churn/solid/setup.ts | 13 +
.../navigation-churn/solid/src/app.tsx | 29 ++
.../solid/src/routeTree.gen.ts | 77 +++
.../navigation-churn/solid/src/router.tsx | 17 +
.../solid/src/routes/__root.tsx | 9 +
.../navigation-churn/solid/src/routes/a.tsx | 14 +
.../navigation-churn/solid/src/routes/b.tsx | 14 +
.../navigation-churn/solid/tsconfig.json | 19 +
.../navigation-churn/solid/vite.config.ts | 34 ++
.../navigation-churn/vue/memory.bench.ts | 21 +
.../navigation-churn/vue/memory.flame.ts | 4 +
.../navigation-churn/vue/project.json | 54 ++
.../scenarios/navigation-churn/vue/setup.ts | 13 +
.../navigation-churn/vue/src/app.tsx | 36 ++
.../navigation-churn/vue/src/routeTree.gen.ts | 77 +++
.../navigation-churn/vue/src/router.tsx | 17 +
.../vue/src/routes/__root.tsx | 9 +
.../navigation-churn/vue/src/routes/a.tsx | 16 +
.../navigation-churn/vue/src/routes/b.tsx | 16 +
.../navigation-churn/vue/tsconfig.json | 19 +
.../navigation-churn/vue/vite.config.ts | 36 ++
.../scenarios/preload-churn/item-payload.ts | 51 ++
.../preload-churn/react/memory.bench.ts | 21 +
.../preload-churn/react/memory.flame.ts | 4 +
.../preload-churn/react/project.json | 54 ++
.../scenarios/preload-churn/react/setup.ts | 14 +
.../scenarios/preload-churn/react/src/app.tsx | 33 ++
.../preload-churn/react/src/routeTree.gen.ts | 77 +++
.../preload-churn/react/src/router.tsx | 18 +
.../preload-churn/react/src/routes/__root.tsx | 9 +
.../preload-churn/react/src/routes/index.tsx | 9 +
.../react/src/routes/items.$id.tsx | 20 +
.../preload-churn/react/tsconfig.json | 19 +
.../preload-churn/react/vite.config.ts | 34 ++
.../client/scenarios/preload-churn/shared.ts | 220 ++++++++
.../preload-churn/solid/memory.bench.ts | 21 +
.../preload-churn/solid/memory.flame.ts | 4 +
.../preload-churn/solid/project.json | 54 ++
.../scenarios/preload-churn/solid/setup.ts | 14 +
.../scenarios/preload-churn/solid/src/app.tsx | 31 ++
.../preload-churn/solid/src/routeTree.gen.ts | 77 +++
.../preload-churn/solid/src/router.tsx | 18 +
.../preload-churn/solid/src/routes/__root.tsx | 9 +
.../preload-churn/solid/src/routes/index.tsx | 9 +
.../solid/src/routes/items.$id.tsx | 20 +
.../preload-churn/solid/tsconfig.json | 19 +
.../preload-churn/solid/vite.config.ts | 34 ++
.../preload-churn/vue/memory.bench.ts | 21 +
.../preload-churn/vue/memory.flame.ts | 4 +
.../scenarios/preload-churn/vue/project.json | 54 ++
.../scenarios/preload-churn/vue/setup.ts | 14 +
.../scenarios/preload-churn/vue/src/app.tsx | 38 ++
.../preload-churn/vue/src/routeTree.gen.ts | 77 +++
.../preload-churn/vue/src/router.tsx | 18 +
.../preload-churn/vue/src/routes/__root.tsx | 9 +
.../preload-churn/vue/src/routes/index.tsx | 9 +
.../vue/src/routes/items.$id.tsx | 20 +
.../scenarios/preload-churn/vue/tsconfig.json | 19 +
.../preload-churn/vue/vite.config.ts | 36 ++
.../react/memory.bench.ts | 21 +
.../react/memory.flame.ts | 4 +
.../unique-location-churn/react/project.json | 54 ++
.../unique-location-churn/react/setup.ts | 13 +
.../unique-location-churn/react/src/app.tsx | 31 ++
.../react/src/routeTree.gen.ts | 59 +++
.../react/src/router.tsx | 17 +
.../react/src/routes/__root.tsx | 9 +
.../react/src/routes/items.$id.tsx | 28 ++
.../unique-location-churn/react/tsconfig.json | 19 +
.../react/vite.config.ts | 34 ++
.../scenarios/unique-location-churn/shared.ts | 149 ++++++
.../solid/memory.bench.ts | 21 +
.../solid/memory.flame.ts | 4 +
.../unique-location-churn/solid/project.json | 54 ++
.../unique-location-churn/solid/setup.ts | 13 +
.../unique-location-churn/solid/src/app.tsx | 29 ++
.../solid/src/routeTree.gen.ts | 59 +++
.../solid/src/router.tsx | 17 +
.../solid/src/routes/__root.tsx | 9 +
.../solid/src/routes/items.$id.tsx | 28 ++
.../unique-location-churn/solid/tsconfig.json | 19 +
.../solid/vite.config.ts | 34 ++
.../unique-location-churn/vue/memory.bench.ts | 21 +
.../unique-location-churn/vue/memory.flame.ts | 4 +
.../unique-location-churn/vue/project.json | 54 ++
.../unique-location-churn/vue/setup.ts | 13 +
.../unique-location-churn/vue/src/app.tsx | 36 ++
.../vue/src/routeTree.gen.ts | 59 +++
.../unique-location-churn/vue/src/router.tsx | 17 +
.../vue/src/routes/__root.tsx | 9 +
.../vue/src/routes/items.$id.tsx | 34 ++
.../unique-location-churn/vue/tsconfig.json | 19 +
.../unique-location-churn/vue/vite.config.ts | 36 ++
benchmarks/memory/client/tsconfig.json | 7 +
.../memory/client/vitest.react.config.ts | 9 +
benchmarks/memory/client/vitest.setup.ts | 9 +
.../memory/client/vitest.solid.config.ts | 9 +
benchmarks/memory/client/vitest.vue.config.ts | 9 +
benchmarks/memory/flame-control.ts | 123 +++++
benchmarks/memory/run-flame.mjs | 117 +++++
benchmarks/memory/server/bench-utils.ts | 90 ++++
benchmarks/memory/server/benchmark.ts | 9 +
benchmarks/memory/server/flame-runner.ts | 12 +
benchmarks/memory/server/package.json | 269 ++++++++++
.../aborted-requests/deferred-records.ts | 18 +
.../aborted-requests/react/memory.bench.ts | 11 +
.../aborted-requests/react/memory.flame.ts | 4 +
.../aborted-requests/react/project.json | 54 ++
.../scenarios/aborted-requests/react/setup.ts | 14 +
.../react/src/routeTree.gen.ts | 86 ++++
.../aborted-requests/react/src/router.tsx | 16 +
.../react/src/routes/__root.tsx | 27 +
.../react/src/routes/index.tsx | 9 +
.../react/src/routes/stream.$id.tsx | 66 +++
.../aborted-requests/react/tsconfig.json | 17 +
.../aborted-requests/react/vite.config.ts | 29 ++
.../scenarios/aborted-requests/shared.ts | 278 ++++++++++
.../aborted-requests/solid/memory.bench.ts | 11 +
.../aborted-requests/solid/memory.flame.ts | 4 +
.../aborted-requests/solid/project.json | 54 ++
.../scenarios/aborted-requests/solid/setup.ts | 14 +
.../solid/src/routeTree.gen.ts | 86 ++++
.../aborted-requests/solid/src/router.tsx | 16 +
.../solid/src/routes/__root.tsx | 29 ++
.../solid/src/routes/index.tsx | 9 +
.../solid/src/routes/stream.$id.tsx | 126 +++++
.../aborted-requests/solid/tsconfig.json | 17 +
.../aborted-requests/solid/vite.config.ts | 34 ++
.../aborted-requests/vue/memory.bench.ts | 11 +
.../aborted-requests/vue/memory.flame.ts | 4 +
.../aborted-requests/vue/project.json | 54 ++
.../scenarios/aborted-requests/vue/setup.ts | 14 +
.../aborted-requests/vue/src/routeTree.gen.ts | 86 ++++
.../aborted-requests/vue/src/router.tsx | 16 +
.../vue/src/routes/__root.tsx | 29 ++
.../aborted-requests/vue/src/routes/index.tsx | 9 +
.../vue/src/routes/stream.$id.tsx | 120 +++++
.../aborted-requests/vue/tsconfig.json | 17 +
.../aborted-requests/vue/vite.config.ts | 29 ++
.../error-paths/react/memory.bench.ts | 11 +
.../error-paths/react/memory.flame.ts | 4 +
.../scenarios/error-paths/react/project.json | 54 ++
.../scenarios/error-paths/react/setup.ts | 14 +
.../error-paths/react/src/routeTree.gen.ts | 146 ++++++
.../error-paths/react/src/router.tsx | 23 +
.../error-paths/react/src/routes/__root.tsx | 27 +
.../error-paths/react/src/routes/boom.$id.tsx | 17 +
.../error-paths/react/src/routes/from.$id.tsx | 14 +
.../error-paths/react/src/routes/index.tsx | 9 +
.../react/src/routes/missing.$id.tsx | 19 +
.../react/src/routes/target.$id.tsx | 11 +
.../scenarios/error-paths/react/tsconfig.json | 17 +
.../error-paths/react/vite.config.ts | 29 ++
.../server/scenarios/error-paths/shared.ts | 192 +++++++
.../error-paths/solid/memory.bench.ts | 11 +
.../error-paths/solid/memory.flame.ts | 4 +
.../scenarios/error-paths/solid/project.json | 54 ++
.../scenarios/error-paths/solid/setup.ts | 14 +
.../error-paths/solid/src/routeTree.gen.ts | 146 ++++++
.../error-paths/solid/src/router.tsx | 23 +
.../error-paths/solid/src/routes/__root.tsx | 29 ++
.../error-paths/solid/src/routes/boom.$id.tsx | 17 +
.../error-paths/solid/src/routes/from.$id.tsx | 14 +
.../error-paths/solid/src/routes/index.tsx | 9 +
.../solid/src/routes/missing.$id.tsx | 19 +
.../solid/src/routes/target.$id.tsx | 11 +
.../scenarios/error-paths/solid/tsconfig.json | 17 +
.../error-paths/solid/vite.config.ts | 34 ++
.../scenarios/error-paths/vue/memory.bench.ts | 11 +
.../scenarios/error-paths/vue/memory.flame.ts | 4 +
.../scenarios/error-paths/vue/project.json | 54 ++
.../server/scenarios/error-paths/vue/setup.ts | 14 +
.../error-paths/vue/src/routeTree.gen.ts | 146 ++++++
.../scenarios/error-paths/vue/src/router.tsx | 16 +
.../error-paths/vue/src/routes/__root.tsx | 29 ++
.../error-paths/vue/src/routes/boom.$id.tsx | 17 +
.../error-paths/vue/src/routes/from.$id.tsx | 14 +
.../error-paths/vue/src/routes/index.tsx | 9 +
.../vue/src/routes/missing.$id.tsx | 19 +
.../error-paths/vue/src/routes/target.$id.tsx | 11 +
.../scenarios/error-paths/vue/tsconfig.json | 17 +
.../scenarios/error-paths/vue/vite.config.ts | 29 ++
.../peak-large-page/large-page-data.ts | 111 ++++
.../peak-large-page/react/memory.bench.ts | 11 +
.../peak-large-page/react/memory.flame.ts | 4 +
.../peak-large-page/react/project.json | 54 ++
.../scenarios/peak-large-page/react/setup.ts | 14 +
.../react/src/routeTree.gen.ts | 305 +++++++++++
.../peak-large-page/react/src/router.tsx | 16 +
.../react/src/routes/__root.tsx | 27 +
.../react/src/routes/index.tsx | 9 +
.../src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx | 28 ++
.../react/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx | 29 ++
.../react/src/routes/l1.l2.l3.l4.l5.l6.tsx | 29 ++
.../react/src/routes/l1.l2.l3.l4.l5.tsx | 29 ++
.../react/src/routes/l1.l2.l3.l4.tsx | 29 ++
.../react/src/routes/l1.l2.l3.tsx | 29 ++
.../react/src/routes/l1.l2.tsx | 29 ++
.../peak-large-page/react/src/routes/l1.tsx | 29 ++
.../peak-large-page/react/tsconfig.json | 17 +
.../peak-large-page/react/vite.config.ts | 29 ++
.../scenarios/peak-large-page/shared.ts | 68 +++
.../peak-large-page/solid/memory.bench.ts | 11 +
.../peak-large-page/solid/memory.flame.ts | 4 +
.../peak-large-page/solid/project.json | 54 ++
.../scenarios/peak-large-page/solid/setup.ts | 14 +
.../solid/src/routeTree.gen.ts | 305 +++++++++++
.../peak-large-page/solid/src/router.tsx | 16 +
.../solid/src/routes/__root.tsx | 29 ++
.../solid/src/routes/index.tsx | 9 +
.../src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx | 28 ++
.../solid/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx | 29 ++
.../solid/src/routes/l1.l2.l3.l4.l5.l6.tsx | 29 ++
.../solid/src/routes/l1.l2.l3.l4.l5.tsx | 29 ++
.../solid/src/routes/l1.l2.l3.l4.tsx | 29 ++
.../solid/src/routes/l1.l2.l3.tsx | 29 ++
.../solid/src/routes/l1.l2.tsx | 29 ++
.../peak-large-page/solid/src/routes/l1.tsx | 29 ++
.../peak-large-page/solid/tsconfig.json | 17 +
.../peak-large-page/solid/vite.config.ts | 34 ++
.../peak-large-page/vue/memory.bench.ts | 11 +
.../peak-large-page/vue/memory.flame.ts | 4 +
.../peak-large-page/vue/project.json | 54 ++
.../scenarios/peak-large-page/vue/setup.ts | 14 +
.../peak-large-page/vue/src/routeTree.gen.ts | 305 +++++++++++
.../peak-large-page/vue/src/router.tsx | 16 +
.../peak-large-page/vue/src/routes/__root.tsx | 29 ++
.../peak-large-page/vue/src/routes/index.tsx | 9 +
.../src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx | 28 ++
.../vue/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx | 29 ++
.../vue/src/routes/l1.l2.l3.l4.l5.l6.tsx | 29 ++
.../vue/src/routes/l1.l2.l3.l4.l5.tsx | 29 ++
.../vue/src/routes/l1.l2.l3.l4.tsx | 29 ++
.../vue/src/routes/l1.l2.l3.tsx | 29 ++
.../peak-large-page/vue/src/routes/l1.l2.tsx | 29 ++
.../peak-large-page/vue/src/routes/l1.tsx | 29 ++
.../peak-large-page/vue/tsconfig.json | 17 +
.../peak-large-page/vue/vite.config.ts | 29 ++
.../request-churn/react/memory.bench.ts | 11 +
.../request-churn/react/memory.flame.ts | 4 +
.../request-churn/react/project.json | 54 ++
.../scenarios/request-churn/react/setup.ts | 14 +
.../request-churn/react/src/routeTree.gen.ts | 86 ++++
.../request-churn/react/src/router.tsx | 16 +
.../request-churn/react/src/routes/__root.tsx | 27 +
.../request-churn/react/src/routes/index.tsx | 9 +
.../react/src/routes/items.$id.tsx | 40 ++
.../request-churn/react/tsconfig.json | 17 +
.../request-churn/react/vite.config.ts | 29 ++
.../server/scenarios/request-churn/shared.ts | 82 +++
.../request-churn/solid/memory.bench.ts | 11 +
.../request-churn/solid/memory.flame.ts | 4 +
.../request-churn/solid/project.json | 54 ++
.../scenarios/request-churn/solid/setup.ts | 14 +
.../request-churn/solid/src/routeTree.gen.ts | 86 ++++
.../request-churn/solid/src/router.tsx | 16 +
.../request-churn/solid/src/routes/__root.tsx | 29 ++
.../request-churn/solid/src/routes/index.tsx | 9 +
.../solid/src/routes/items.$id.tsx | 40 ++
.../request-churn/solid/tsconfig.json | 17 +
.../request-churn/solid/vite.config.ts | 34 ++
.../request-churn/vue/memory.bench.ts | 11 +
.../request-churn/vue/memory.flame.ts | 4 +
.../scenarios/request-churn/vue/project.json | 54 ++
.../scenarios/request-churn/vue/setup.ts | 14 +
.../request-churn/vue/src/routeTree.gen.ts | 86 ++++
.../request-churn/vue/src/router.tsx | 16 +
.../request-churn/vue/src/routes/__root.tsx | 29 ++
.../request-churn/vue/src/routes/index.tsx | 9 +
.../vue/src/routes/items.$id.tsx | 40 ++
.../scenarios/request-churn/vue/tsconfig.json | 17 +
.../request-churn/vue/vite.config.ts | 29 ++
.../react/memory.bench.ts | 11 +
.../react/memory.flame.ts | 4 +
.../serialization-payload/react/project.json | 54 ++
.../serialization-payload/react/setup.ts | 14 +
.../react/src/routeTree.gen.ts | 68 +++
.../react/src/router.tsx | 16 +
.../react/src/routes/__root.tsx | 27 +
.../react/src/routes/data.$id.tsx | 15 +
.../serialization-payload/react/tsconfig.json | 17 +
.../react/vite.config.ts | 29 ++
.../serialization-payload.ts | 115 +++++
.../scenarios/serialization-payload/shared.ts | 75 +++
.../solid/memory.bench.ts | 11 +
.../solid/memory.flame.ts | 4 +
.../serialization-payload/solid/project.json | 54 ++
.../serialization-payload/solid/setup.ts | 14 +
.../solid/src/routeTree.gen.ts | 68 +++
.../solid/src/router.tsx | 16 +
.../solid/src/routes/__root.tsx | 29 ++
.../solid/src/routes/data.$id.tsx | 17 +
.../serialization-payload/solid/tsconfig.json | 17 +
.../solid/vite.config.ts | 34 ++
.../serialization-payload/vue/memory.bench.ts | 11 +
.../serialization-payload/vue/memory.flame.ts | 4 +
.../serialization-payload/vue/project.json | 54 ++
.../serialization-payload/vue/setup.ts | 14 +
.../vue/src/routeTree.gen.ts | 68 +++
.../serialization-payload/vue/src/router.tsx | 16 +
.../vue/src/routes/__root.tsx | 29 ++
.../vue/src/routes/data.$id.tsx | 17 +
.../serialization-payload/vue/tsconfig.json | 17 +
.../serialization-payload/vue/vite.config.ts | 29 ++
.../server-fn-churn/react/memory.bench.ts | 11 +
.../server-fn-churn/react/memory.flame.ts | 4 +
.../server-fn-churn/react/project.json | 54 ++
.../scenarios/server-fn-churn/react/setup.ts | 14 +
.../server-fn-churn/react/src/fns.ts | 24 +
.../react/src/routeTree.gen.ts | 86 ++++
.../server-fn-churn/react/src/router.tsx | 16 +
.../react/src/routes/__root.tsx | 27 +
.../react/src/routes/api.fn-urls.ts | 14 +
.../react/src/routes/index.tsx | 18 +
.../server-fn-churn/react/tsconfig.json | 17 +
.../server-fn-churn/react/vite.config.ts | 29 ++
.../server-fn-churn/server-fn-payload.ts | 33 ++
.../scenarios/server-fn-churn/shared.ts | 232 +++++++++
.../server-fn-churn/solid/memory.bench.ts | 11 +
.../server-fn-churn/solid/memory.flame.ts | 4 +
.../server-fn-churn/solid/project.json | 54 ++
.../scenarios/server-fn-churn/solid/setup.ts | 14 +
.../server-fn-churn/solid/src/fns.ts | 24 +
.../solid/src/routeTree.gen.ts | 86 ++++
.../server-fn-churn/solid/src/router.tsx | 16 +
.../solid/src/routes/__root.tsx | 29 ++
.../solid/src/routes/api.fn-urls.ts | 14 +
.../solid/src/routes/index.tsx | 18 +
.../server-fn-churn/solid/tsconfig.json | 17 +
.../server-fn-churn/solid/vite.config.ts | 34 ++
.../server-fn-churn/vue/memory.bench.ts | 11 +
.../server-fn-churn/vue/memory.flame.ts | 4 +
.../server-fn-churn/vue/project.json | 54 ++
.../scenarios/server-fn-churn/vue/setup.ts | 14 +
.../scenarios/server-fn-churn/vue/src/fns.ts | 24 +
.../server-fn-churn/vue/src/routeTree.gen.ts | 86 ++++
.../server-fn-churn/vue/src/router.tsx | 16 +
.../server-fn-churn/vue/src/routes/__root.tsx | 29 ++
.../vue/src/routes/api.fn-urls.ts | 14 +
.../server-fn-churn/vue/src/routes/index.tsx | 18 +
.../server-fn-churn/vue/tsconfig.json | 17 +
.../server-fn-churn/vue/vite.config.ts | 29 ++
.../streaming-peak/deferred-section-data.ts | 37 ++
.../streaming-peak/react/memory.bench.ts | 11 +
.../streaming-peak/react/memory.flame.ts | 4 +
.../streaming-peak/react/project.json | 54 ++
.../scenarios/streaming-peak/react/setup.ts | 14 +
.../streaming-peak/react/src/routeTree.gen.ts | 86 ++++
.../streaming-peak/react/src/router.tsx | 16 +
.../react/src/routes/__root.tsx | 27 +
.../streaming-peak/react/src/routes/index.tsx | 9 +
.../react/src/routes/stream.$id.tsx | 83 +++
.../streaming-peak/react/tsconfig.json | 17 +
.../streaming-peak/react/vite.config.ts | 29 ++
.../server/scenarios/streaming-peak/shared.ts | 112 +++++
.../streaming-peak/solid/memory.bench.ts | 11 +
.../streaming-peak/solid/memory.flame.ts | 4 +
.../streaming-peak/solid/project.json | 54 ++
.../scenarios/streaming-peak/solid/setup.ts | 14 +
.../streaming-peak/solid/src/routeTree.gen.ts | 86 ++++
.../streaming-peak/solid/src/router.tsx | 16 +
.../solid/src/routes/__root.tsx | 29 ++
.../streaming-peak/solid/src/routes/index.tsx | 9 +
.../solid/src/routes/stream.$id.tsx | 75 +++
.../streaming-peak/solid/tsconfig.json | 17 +
.../streaming-peak/solid/vite.config.ts | 34 ++
.../streaming-peak/vue/memory.bench.ts | 11 +
.../streaming-peak/vue/memory.flame.ts | 4 +
.../scenarios/streaming-peak/vue/project.json | 54 ++
.../scenarios/streaming-peak/vue/setup.ts | 14 +
.../streaming-peak/vue/src/routeTree.gen.ts | 86 ++++
.../streaming-peak/vue/src/router.tsx | 16 +
.../streaming-peak/vue/src/routes/__root.tsx | 29 ++
.../streaming-peak/vue/src/routes/index.tsx | 9 +
.../vue/src/routes/stream.$id.tsx | 82 +++
.../streaming-peak/vue/tsconfig.json | 17 +
.../streaming-peak/vue/vite.config.ts | 29 ++
benchmarks/memory/server/tsconfig.json | 7 +
.../memory/server/vitest.react.config.ts | 9 +
.../memory/server/vitest.solid.config.ts | 9 +
benchmarks/memory/server/vitest.vue.config.ts | 9 +
package.json | 8 +
.../src/ssr/renderRouterToStream.tsx | 45 +-
.../tests/renderRouterToStream.test.tsx | 4 +-
pnpm-lock.yaml | 475 ++++++++++++++++--
pnpm-workspace.yaml | 1 +
527 files changed, 18270 insertions(+), 38 deletions(-)
create mode 100644 benchmarks/memory/README.md
create mode 100644 benchmarks/memory/client/bench-utils.ts
create mode 100644 benchmarks/memory/client/benchmark.ts
create mode 100644 benchmarks/memory/client/flame-runner.ts
create mode 100644 benchmarks/memory/client/jsdom.ts
create mode 100644 benchmarks/memory/client/lifecycle.ts
create mode 100644 benchmarks/memory/client/package.json
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/project.json
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/fast.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/index.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/src/routes/slow.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/react/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/slow-loaders.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/project.json
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/fast.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/index.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/src/routes/slow.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/solid/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/project.json
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/fast.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/index.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/src/routes/slow.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/interrupted-navigations/vue/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/loader-data.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/project.json
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/page.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/shell.index.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/src/routes/shell.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/react/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/shared.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/project.json
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/page.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/shell.index.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/src/routes/shell.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/solid/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/project.json
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/page.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/shell.index.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/src/routes/shell.tsx
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/loader-data-retention/vue/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/project.json
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/src/routes/a.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/react/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/shared.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/project.json
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/src/routes/a.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/solid/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/project.json
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/src/routes/a.tsx
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/mount-unmount/vue/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/project.json
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/a.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/src/routes/b.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/react/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/shared.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/project.json
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/a.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/src/routes/b.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/solid/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/project.json
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/a.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/src/routes/b.tsx
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/navigation-churn/vue/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/item-payload.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/project.json
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/src/routes/index.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/react/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/shared.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/project.json
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/index.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/solid/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/project.json
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/index.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/preload-churn/vue/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/project.json
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/react/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/shared.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/project.json
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/solid/vite.config.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/memory.bench.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/memory.flame.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/project.json
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/setup.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/src/app.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/src/router.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/tsconfig.json
create mode 100644 benchmarks/memory/client/scenarios/unique-location-churn/vue/vite.config.ts
create mode 100644 benchmarks/memory/client/tsconfig.json
create mode 100644 benchmarks/memory/client/vitest.react.config.ts
create mode 100644 benchmarks/memory/client/vitest.setup.ts
create mode 100644 benchmarks/memory/client/vitest.solid.config.ts
create mode 100644 benchmarks/memory/client/vitest.vue.config.ts
create mode 100644 benchmarks/memory/flame-control.ts
create mode 100644 benchmarks/memory/run-flame.mjs
create mode 100644 benchmarks/memory/server/bench-utils.ts
create mode 100644 benchmarks/memory/server/benchmark.ts
create mode 100644 benchmarks/memory/server/flame-runner.ts
create mode 100644 benchmarks/memory/server/package.json
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/deferred-records.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/project.json
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/src/routes/stream.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/react/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/shared.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/project.json
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/src/routes/stream.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/project.json
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/src/routes/stream.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/aborted-requests/vue/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/project.json
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/src/routes/boom.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/src/routes/from.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/src/routes/missing.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/src/routes/target.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/error-paths/react/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/shared.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/project.json
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/src/routes/boom.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/src/routes/from.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/src/routes/missing.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/src/routes/target.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/error-paths/solid/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/project.json
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/src/routes/boom.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/src/routes/from.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/src/routes/missing.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/src/routes/target.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/error-paths/vue/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/large-page-data.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/project.json
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.l6.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.l5.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.l4.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.l3.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.l2.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/src/routes/l1.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/shared.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/project.json
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.l6.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.l5.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.l4.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.l3.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.l2.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/src/routes/l1.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/solid/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/project.json
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.l7.l8.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.l7.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.l6.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.l5.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.l4.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.l3.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.l2.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/src/routes/l1.tsx
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/peak-large-page/vue/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/project.json
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/request-churn/react/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/shared.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/project.json
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/request-churn/solid/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/project.json
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/src/routes/items.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/request-churn/vue/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/project.json
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/src/routes/data.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/react/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/serialization-payload.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/shared.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/project.json
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/src/routes/data.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/solid/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/project.json
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/src/routes/data.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/serialization-payload/vue/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/project.json
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/src/fns.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/api.fn-urls.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/react/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/server-fn-payload.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/shared.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/project.json
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/src/fns.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/api.fn-urls.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/solid/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/project.json
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/src/fns.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/api.fn-urls.ts
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/server-fn-churn/vue/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/deferred-section-data.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/project.json
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/src/routes/stream.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/react/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/shared.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/project.json
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/src/routes/stream.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/solid/vite.config.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/memory.bench.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/memory.flame.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/project.json
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/setup.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/src/router.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/__root.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/index.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/src/routes/stream.$id.tsx
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/tsconfig.json
create mode 100644 benchmarks/memory/server/scenarios/streaming-peak/vue/vite.config.ts
create mode 100644 benchmarks/memory/server/tsconfig.json
create mode 100644 benchmarks/memory/server/vitest.react.config.ts
create mode 100644 benchmarks/memory/server/vitest.solid.config.ts
create mode 100644 benchmarks/memory/server/vitest.vue.config.ts
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/*'
From a415471437955c7355cd0340226e2da97075ad12 Mon Sep 17 00:00:00 2001
From: Flo
Date: Sun, 14 Jun 2026 20:56:50 +0200
Subject: [PATCH 02/25] perf(router-core): cache lightweight route matches
(#7601)
---
packages/router-core/src/router.ts | 36 +++++++++++++++++++++++-------
1 file changed, 28 insertions(+), 8 deletions(-)
diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts
index 9ff7b82b05..2197dab737 100644
--- a/packages/router-core/src/router.ts
+++ b/packages/router-core/src/router.ts
@@ -909,6 +909,18 @@ export const locationHistoryActions = new WeakMap<
HistoryAction
>()
+type LightweightRouteMatchResult = {
+ matchedRoutes: ReadonlyArray
+ fullPath: string
+ search: Record
+ params: Record
+}
+
+type LightweightRouteMatchCacheEntry = [
+ lastMatchId: string | undefined,
+ result: LightweightRouteMatchResult,
+]
+
export type CreateRouterFn = <
TRouteTree extends AnyRoute,
TTrailingSlashOption extends TrailingSlashOption = 'never',
@@ -1002,6 +1014,10 @@ export class RouterCore<
processedTree!: ProcessedTree
resolvePathCache!: LRUCache
private routeBranchCache = new WeakMap>()
+ private lightweightCache = new WeakMap<
+ ParsedLocation,
+ LightweightRouteMatchCacheEntry
+ >()
isServer!: boolean
pathParamsDecoder?: (encoded: string) => string
protocolAllowlist!: Set
@@ -1719,12 +1735,15 @@ export class RouterCore<
* Only computes fullPath, accumulated search, and params - skipping expensive
* operations like AbortController, ControlledPromise, loaderDeps, and full match objects.
*/
- private matchRoutesLightweight(location: ParsedLocation): {
- matchedRoutes: ReadonlyArray
- fullPath: string
- search: Record
- params: Record
- } {
+ private matchRoutesLightweight(
+ location: ParsedLocation,
+ ): LightweightRouteMatchResult {
+ const lastStateMatchId = last(this.stores.matchesId.get())
+ const cached = this.lightweightCache.get(location)
+ if (cached && cached[0] === lastStateMatchId) {
+ return cached[1]
+ }
+
const { matchedRoutes, routeParams } = this.getMatchedRoutes(
location.pathname,
)
@@ -1753,7 +1772,6 @@ export class RouterCore<
}
// Determine params: reuse from state if possible, otherwise parse
- const lastStateMatchId = last(this.stores.matchesId.get())
const lastStateMatch =
lastStateMatchId && this.stores.matchStores.get(lastStateMatchId)?.get()
const canReuseParams =
@@ -1780,12 +1798,14 @@ export class RouterCore<
params = strictParams
}
- return {
+ const result = {
matchedRoutes,
fullPath: lastRoute.fullPath,
search: accumulatedSearch,
params,
}
+ this.lightweightCache.set(location, [lastStateMatchId, result])
+ return result
}
cancelMatch = (id: string) => {
From 734f9072e418bc6fe02e8e6d68855fc49b088b8d Mon Sep 17 00:00:00 2001
From: Flo
Date: Tue, 16 Jun 2026 10:22:04 +0200
Subject: [PATCH 03/25] ci: pnpm trustPolicy no-downgrade exclude
chokidar@4.0.3 (#7643)
---
pnpm-workspace.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 2c130509f2..002be83894 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -4,6 +4,9 @@ preferWorkspacePackages: true
blockExoticSubdeps: true
trustPolicy: 'no-downgrade'
+trustPolicyExclude:
+ - 'chokidar@4.0.3'
+
packages:
- 'packages/*'
- 'benchmarks/*'
From e499164c722841271af4aa2215483476bdb94c94 Mon Sep 17 00:00:00 2001
From: Elecmonkey
Date: Tue, 16 Jun 2026 16:30:39 +0800
Subject: [PATCH 04/25] feat(start-plugin-core): support Rsbuild preview SSR
middleware (#7372)
* fix(start-plugin-core): support rsbuild preview SSR
* fix(start-plugin-core): always install rsbuild SSR middleware in preview
Preview is the only SSR handler in `rsbuild preview`, so gate the opt-out
to dev only and keep the option named `installDevServerMiddleware`.
---
.changeset/quiet-rsbuild-preview.md | 5 +
benchmarks/bundle-size/package.json | 2 +-
.../rspack-basic-file-based/package.json | 2 +-
.../package.json | 2 +-
e2e/react-start/basic/package.json | 7 +-
e2e/react-start/basic/playwright.config.ts | 8 +-
e2e/react-start/css-inline/package.json | 2 +-
.../custom-server-rsbuild/package.json | 2 +-
.../custom-server-rsbuild/rsbuild.config.ts | 2 +-
.../deferred-hydration/package.json | 2 +-
e2e/react-start/hmr/package.json | 2 +-
.../import-protection/package.json | 2 +-
e2e/react-start/rsc/package.json | 2 +-
e2e/react-start/server-functions/package.json | 2 +-
.../rspack-basic-file-based/package.json | 2 +-
.../package.json | 2 +-
e2e/solid-start/basic/package.json | 2 +-
.../deferred-hydration/package.json | 2 +-
.../rspack-basic-file-based/package.json | 2 +-
.../package.json | 2 +-
e2e/vue-start/basic/package.json | 2 +-
.../quickstart-rspack-file-based/package.json | 2 +-
.../quickstart-rspack-file-based/package.json | 2 +-
packages/react-start/package.json | 2 +-
packages/solid-start/package.json | 2 +-
packages/start-plugin-core/package.json | 2 +-
.../src/rsbuild/dev-server.ts | 129 ------
.../start-plugin-core/src/rsbuild/plugin.ts | 17 +-
.../src/rsbuild/server-middleware.ts | 217 ++++++++++
packages/vue-start/package.json | 2 +-
pnpm-lock.yaml | 386 +++++++++++-------
31 files changed, 516 insertions(+), 301 deletions(-)
create mode 100644 .changeset/quiet-rsbuild-preview.md
delete mode 100644 packages/start-plugin-core/src/rsbuild/dev-server.ts
create mode 100644 packages/start-plugin-core/src/rsbuild/server-middleware.ts
diff --git a/.changeset/quiet-rsbuild-preview.md b/.changeset/quiet-rsbuild-preview.md
new file mode 100644
index 0000000000..f3378b21ff
--- /dev/null
+++ b/.changeset/quiet-rsbuild-preview.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/start-plugin-core': patch
+---
+
+Fix Rsbuild preview support for TanStack Start SSR. Preview always installs the SSR middleware; the `installDevServerMiddleware` option only applies to dev.
diff --git a/benchmarks/bundle-size/package.json b/benchmarks/bundle-size/package.json
index e03949297b..8cd8f73f41 100644
--- a/benchmarks/bundle-size/package.json
+++ b/benchmarks/bundle-size/package.json
@@ -39,7 +39,7 @@
"vue": "^3.5.16"
},
"devDependencies": {
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tanstack/router-plugin": "workspace:^",
"@types/react": "^19.0.8",
diff --git a/e2e/react-router/rspack-basic-file-based/package.json b/e2e/react-router/rspack-basic-file-based/package.json
index b19a27af6d..74659b51e1 100644
--- a/e2e/react-router/rspack-basic-file-based/package.json
+++ b/e2e/react-router/rspack-basic-file-based/package.json
@@ -18,7 +18,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tailwindcss/postcss": "^4.2.2",
"@tanstack/router-e2e-utils": "workspace:^",
diff --git a/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json b/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json
index 5609c6cef0..a7b9eb58d4 100644
--- a/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json
+++ b/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json
@@ -18,7 +18,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tailwindcss/postcss": "^4.2.2",
"@tanstack/router-e2e-utils": "workspace:^",
diff --git a/e2e/react-start/basic/package.json b/e2e/react-start/basic/package.json
index 4a6a92d1c5..37a3f4a0e2 100644
--- a/e2e/react-start/basic/package.json
+++ b/e2e/react-start/basic/package.json
@@ -12,6 +12,7 @@
"build:vite": "vite build && tsc --noEmit",
"build:rsbuild": "rsbuild build && tsc --noEmit",
"preview": "vite preview",
+ "preview:rsbuild": "rsbuild preview",
"start": "node server.js",
"test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"./tests/setup/waitForDummyServer.ts\").then(m => m.default())'",
"test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'",
@@ -31,7 +32,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tailwindcss/postcss": "^4.2.2",
"@tailwindcss/vite": "^4.2.2",
@@ -86,6 +87,10 @@
{
"toolchain": "rsbuild",
"mode": "prerender"
+ },
+ {
+ "toolchain": "rsbuild",
+ "mode": "preview"
}
]
}
diff --git a/e2e/react-start/basic/playwright.config.ts b/e2e/react-start/basic/playwright.config.ts
index 4d92fc032d..db07d18608 100644
--- a/e2e/react-start/basic/playwright.config.ts
+++ b/e2e/react-start/basic/playwright.config.ts
@@ -7,6 +7,7 @@ import {
import packageJson from './package.json' with { type: 'json' }
const mode = process.env.MODE ?? 'ssr'
+const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite'
const e2ePortKey = process.env.E2E_PORT_KEY ?? packageJson.name
const distDir = process.env.E2E_DIST_DIR ?? 'dist'
@@ -24,9 +25,13 @@ const PORT = await getTestServerPort(e2ePortKey)
const START_PORT = await getTestServerPort(`${e2ePortKey}_start`)
const EXTERNAL_PORT = await getDummyServerPort(e2ePortKey)
const baseURL = `http://localhost:${PORT}`
+const previewCommand =
+ toolchain === 'rsbuild'
+ ? `pnpm preview:rsbuild --port ${PORT}`
+ : `pnpm preview --outDir ${distDir} --port ${PORT}`
const commandByMode =
mode === 'preview'
- ? `pnpm run test:e2e:startDummyServer && pnpm preview --outDir ${distDir} --port ${PORT}`
+ ? `pnpm run test:e2e:startDummyServer && ${previewCommand}`
: `pnpm run test:e2e:startDummyServer && pnpm start`
/**
* See https://playwright.dev/docs/test-configuration.
@@ -50,6 +55,7 @@ export default defineConfig({
stdout: 'pipe',
env: {
MODE: mode,
+ E2E_TOOLCHAIN: toolchain,
VITE_NODE_ENV: 'test',
VITE_EXTERNAL_PORT: String(EXTERNAL_PORT),
VITE_SERVER_PORT: String(PORT),
diff --git a/e2e/react-start/css-inline/package.json b/e2e/react-start/css-inline/package.json
index e3e5d79678..a7bc5d023f 100644
--- a/e2e/react-start/css-inline/package.json
+++ b/e2e/react-start/css-inline/package.json
@@ -24,7 +24,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
diff --git a/e2e/react-start/custom-server-rsbuild/package.json b/e2e/react-start/custom-server-rsbuild/package.json
index 255b07edc6..75634f79f5 100644
--- a/e2e/react-start/custom-server-rsbuild/package.json
+++ b/e2e/react-start/custom-server-rsbuild/package.json
@@ -21,7 +21,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tailwindcss/postcss": "^4.2.2",
"@tanstack/router-e2e-utils": "workspace:^",
diff --git a/e2e/react-start/custom-server-rsbuild/rsbuild.config.ts b/e2e/react-start/custom-server-rsbuild/rsbuild.config.ts
index 5f9aa96e16..dec3a6d0fc 100644
--- a/e2e/react-start/custom-server-rsbuild/rsbuild.config.ts
+++ b/e2e/react-start/custom-server-rsbuild/rsbuild.config.ts
@@ -28,7 +28,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/rsbuild'
// explicit prefix.
export default defineConfig({
plugins: [
- pluginReact(),
+ pluginReact({ splitChunks: false }),
tanstackStart({
rsbuild: {
installDevServerMiddleware: false,
diff --git a/e2e/react-start/deferred-hydration/package.json b/e2e/react-start/deferred-hydration/package.json
index 9560a44c41..b225528b49 100644
--- a/e2e/react-start/deferred-hydration/package.json
+++ b/e2e/react-start/deferred-hydration/package.json
@@ -26,7 +26,7 @@
"react-dom": "^19.0.0"
},
"devDependencies": {
- "@rsbuild/core": "^2.0.1",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
diff --git a/e2e/react-start/hmr/package.json b/e2e/react-start/hmr/package.json
index 3854488170..b3a58125b9 100644
--- a/e2e/react-start/hmr/package.json
+++ b/e2e/react-start/hmr/package.json
@@ -21,7 +21,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tailwindcss/postcss": "^4.2.2",
"@tailwindcss/vite": "^4.2.2",
diff --git a/e2e/react-start/import-protection/package.json b/e2e/react-start/import-protection/package.json
index e4ad4a79ba..65ad70126b 100644
--- a/e2e/react-start/import-protection/package.json
+++ b/e2e/react-start/import-protection/package.json
@@ -25,7 +25,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
diff --git a/e2e/react-start/rsc/package.json b/e2e/react-start/rsc/package.json
index 7a499078d3..36f822e660 100644
--- a/e2e/react-start/rsc/package.json
+++ b/e2e/react-start/rsc/package.json
@@ -40,7 +40,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tanstack/eslint-plugin-start": "workspace:^",
"@tanstack/router-e2e-utils": "workspace:^",
diff --git a/e2e/react-start/server-functions/package.json b/e2e/react-start/server-functions/package.json
index 279883dbb4..5efdf6c42a 100644
--- a/e2e/react-start/server-functions/package.json
+++ b/e2e/react-start/server-functions/package.json
@@ -31,7 +31,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tailwindcss/postcss": "^4.2.2",
"@tailwindcss/vite": "^4.2.2",
diff --git a/e2e/solid-router/rspack-basic-file-based/package.json b/e2e/solid-router/rspack-basic-file-based/package.json
index 15e0106a68..5641433737 100644
--- a/e2e/solid-router/rspack-basic-file-based/package.json
+++ b/e2e/solid-router/rspack-basic-file-based/package.json
@@ -17,7 +17,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.1.2",
"@rsbuild/plugin-solid": "^1.1.1",
"@tailwindcss/postcss": "^4.2.2",
diff --git a/e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json b/e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json
index 49f56c5c7a..f298b42ca6 100644
--- a/e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json
+++ b/e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json
@@ -17,7 +17,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.1.2",
"@rsbuild/plugin-solid": "^1.1.1",
"@tailwindcss/postcss": "^4.2.2",
diff --git a/e2e/solid-start/basic/package.json b/e2e/solid-start/basic/package.json
index 0210535500..1993225f61 100644
--- a/e2e/solid-start/basic/package.json
+++ b/e2e/solid-start/basic/package.json
@@ -27,7 +27,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.1.2",
"@rsbuild/plugin-solid": "^1.1.1",
"@tailwindcss/postcss": "^4.2.2",
diff --git a/e2e/solid-start/deferred-hydration/package.json b/e2e/solid-start/deferred-hydration/package.json
index e7693d1f10..fd2b681ef1 100644
--- a/e2e/solid-start/deferred-hydration/package.json
+++ b/e2e/solid-start/deferred-hydration/package.json
@@ -23,7 +23,7 @@
"solid-js": "^1.9.10"
},
"devDependencies": {
- "@rsbuild/core": "^2.0.1",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.1.2",
"@rsbuild/plugin-solid": "^1.1.1",
"@tanstack/router-e2e-utils": "workspace:^",
diff --git a/e2e/vue-router/rspack-basic-file-based/package.json b/e2e/vue-router/rspack-basic-file-based/package.json
index 78b7e74a41..5d8a072d87 100644
--- a/e2e/vue-router/rspack-basic-file-based/package.json
+++ b/e2e/vue-router/rspack-basic-file-based/package.json
@@ -17,7 +17,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.1.2",
"@rsbuild/plugin-vue": "^1.2.7",
"@rsbuild/plugin-vue-jsx": "^2.0.0",
diff --git a/e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json b/e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json
index 6a5cfead84..11f61237d3 100644
--- a/e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json
+++ b/e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json
@@ -17,7 +17,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.1.2",
"@rsbuild/plugin-vue": "^1.2.7",
"@rsbuild/plugin-vue-jsx": "^2.0.0",
diff --git a/e2e/vue-start/basic/package.json b/e2e/vue-start/basic/package.json
index 2df94b1a20..073627701a 100644
--- a/e2e/vue-start/basic/package.json
+++ b/e2e/vue-start/basic/package.json
@@ -27,7 +27,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.0.5",
"@rsbuild/plugin-vue": "^1.2.2",
"@rsbuild/plugin-vue-jsx": "^1.1.1",
diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json
index c1fdf3a800..51346b18da 100644
--- a/examples/react/quickstart-rspack-file-based/package.json
+++ b/examples/react/quickstart-rspack-file-based/package.json
@@ -17,7 +17,7 @@
"tailwindcss": "^4.2.2"
},
"devDependencies": {
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
"@tanstack/router-plugin": "^1.168.18",
"@types/react": "^19.0.8",
diff --git a/examples/solid/quickstart-rspack-file-based/package.json b/examples/solid/quickstart-rspack-file-based/package.json
index 582dbe3313..58caf50475 100644
--- a/examples/solid/quickstart-rspack-file-based/package.json
+++ b/examples/solid/quickstart-rspack-file-based/package.json
@@ -16,7 +16,7 @@
"tailwindcss": "^4.2.2"
},
"devDependencies": {
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.1.2",
"@rsbuild/plugin-solid": "^1.1.1",
"@tanstack/router-plugin": "^1.168.18",
diff --git a/packages/react-start/package.json b/packages/react-start/package.json
index f19ff86212..cbaae21f27 100644
--- a/packages/react-start/package.json
+++ b/packages/react-start/package.json
@@ -188,7 +188,7 @@
}
},
"devDependencies": {
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@types/node": ">=20"
}
}
diff --git a/packages/solid-start/package.json b/packages/solid-start/package.json
index 7561c006e8..8059adf57c 100644
--- a/packages/solid-start/package.json
+++ b/packages/solid-start/package.json
@@ -127,7 +127,7 @@
"pathe": "^2.0.3"
},
"devDependencies": {
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@tanstack/router-utils": "workspace:*",
"@types/node": ">=20",
"vite": "*"
diff --git a/packages/start-plugin-core/package.json b/packages/start-plugin-core/package.json
index 8587b9dfca..b2f5cec25c 100644
--- a/packages/start-plugin-core/package.json
+++ b/packages/start-plugin-core/package.json
@@ -107,7 +107,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@types/babel__code-frame": "^7.0.6",
"@types/babel__core": "^7.20.5",
"@types/node": ">=20",
diff --git a/packages/start-plugin-core/src/rsbuild/dev-server.ts b/packages/start-plugin-core/src/rsbuild/dev-server.ts
deleted file mode 100644
index c83ec71448..0000000000
--- a/packages/start-plugin-core/src/rsbuild/dev-server.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import { NodeRequest, sendNodeResponse } from 'srvx/node'
-import { RSBUILD_ENVIRONMENT_NAMES } from './planning'
-import type { IncomingMessage, ServerResponse } from 'node:http'
-import type { RsbuildConfig } from '@rsbuild/core'
-
-type ServerSetupFn = Extract<
- NonNullable['setup']>,
- (...args: Array) => any
->
-type SSRMiddleware = (
- req: IncomingMessage & { originalUrl?: string },
- res: ServerResponse,
- next: () => void,
-) => Promise
-
-/**
- * Returns a `server.setup` function for rsbuild v2.
- *
- * Two middleware positions are used:
- *
- * 1. **Setup body** (BEFORE built-ins): Intercepts `/_serverFn/` URLs so
- * they never reach rsbuild's htmlFallback/htmlCompletion middleware,
- * which can swallow long base64 function IDs.
- *
- * 2. **Returned callback** (AFTER built-ins, BEFORE fallback): Handles
- * all remaining SSR requests (page navigations). This position lets
- * rsbuild's asset middleware serve compiled JS/CSS first.
- *
- * See rsbuild source: devMiddlewares.ts `applyDefaultMiddlewares()`.
- */
-export function createServerSetup(opts: {
- serverFnBasePath: string
-}): ServerSetupFn {
- return (context) => {
- // Only install SSR middleware in dev mode
- if (context.action !== 'dev') {
- return () => {}
- }
-
- const serverFnBase = opts.serverFnBasePath
-
- const handleSSR: SSRMiddleware = async (req, res, next) => {
- const ssrEnv =
- context.server.environments[RSBUILD_ENVIRONMENT_NAMES.server]
-
- if (!ssrEnv) {
- console.error(
- `[tanstack-start] SSR environment "${RSBUILD_ENVIRONMENT_NAMES.server}" not found`,
- )
- return next()
- }
-
- try {
- const serverEntry = await ssrEnv.loadBundle<{
- default: { fetch: (req: Request) => Promise }
- }>('index')
-
- // Restore the original URL (rsbuild may rewrite to /index.html)
- if (req.originalUrl) {
- req.url = req.originalUrl
- }
-
- const webReq = new NodeRequest({ req, res })
- const webRes = await serverEntry.default.fetch(webReq)
- return sendNodeResponse(res, webRes)
- } catch (e) {
- console.error('[tanstack-start] SSR error:', e)
-
- const webReq = new NodeRequest({ req, res })
- if (webReq.headers.get('content-type')?.includes('application/json')) {
- return sendNodeResponse(
- res,
- new Response(
- JSON.stringify(
- {
- status: 500,
- error: 'Internal Server Error',
- message:
- 'An unexpected error occurred. Please try again later.',
- timestamp: new Date().toISOString(),
- },
- null,
- 2,
- ),
- {
- status: 500,
- headers: { 'Content-Type': 'application/json' },
- },
- ),
- )
- }
-
- return sendNodeResponse(
- res,
- new Response(
- `
-
- Error
-
- Internal Server Error
- ${e instanceof Error ? e.message : String(e)}
-
-`,
- {
- status: 500,
- headers: { 'Content-Type': 'text/html' },
- },
- ),
- )
- }
- }
-
- // Position 1: BEFORE built-ins — intercept server function calls
- // early so they are not swallowed by htmlFallback or assetsMiddleware.
- context.server.middlewares.use(async (req, res, next) => {
- const url = req.url || '/'
- if (url.startsWith(serverFnBase)) {
- return handleSSR(req, res, next)
- }
- return next()
- })
-
- // Position 2: AFTER built-ins, before fallback — SSR catch-all for
- // page navigations. Assets are already handled by rsbuild middleware.
- return () => {
- context.server.middlewares.use(handleSSR)
- }
- }
-}
diff --git a/packages/start-plugin-core/src/rsbuild/plugin.ts b/packages/start-plugin-core/src/rsbuild/plugin.ts
index f9e48fe7a2..3de4ed8c2f 100644
--- a/packages/start-plugin-core/src/rsbuild/plugin.ts
+++ b/packages/start-plugin-core/src/rsbuild/plugin.ts
@@ -24,7 +24,7 @@ import {
START_MANIFEST_PLACEHOLDER,
registerVirtualModules,
} from './virtual-modules'
-import { createServerSetup } from './dev-server'
+import { createServerSetup } from './server-middleware'
import { registerClientBuildCapture } from './normalized-client-build'
import { registerRouterPlugins } from './start-router-plugin'
import { postBuildWithRsbuild } from './post-build'
@@ -159,6 +159,7 @@ export function tanStackStartRsbuild(
const resolvedEntryPlan = configContext.resolveEntries()
const isDev = api.context.action === 'dev'
+ const isPreview = api.context.action === 'preview'
const entryAliases = createRsbuildResolvedEntryAliases({
entryPaths: resolvedEntryPlan.entryPaths,
@@ -244,6 +245,10 @@ export function tanStackStartRsbuild(
},
},
server: {
+ ...(rsbuildConfig.server?.printUrls === undefined ||
+ rsbuildConfig.server.printUrls === true
+ ? { printUrls: ({ urls }: { urls: Array }) => urls }
+ : {}),
// Rsbuild compression currently treats Node's raw header array
// writeHead form as an object, which corrupts SSR response headers.
compress: false,
@@ -252,11 +257,17 @@ export function tanStackStartRsbuild(
htmlFallback: false,
// server.setup returned callback runs after built-in middleware
// but BEFORE fallback middleware — the ideal slot for SSR.
- ...(isDev &&
- startPluginOpts.rsbuild?.installDevServerMiddleware !== false
+ // Preview always installs the middleware since it is the only SSR
+ // handler; dev can opt out when a custom server hosts SSR.
+ ...(isPreview ||
+ (isDev &&
+ startPluginOpts.rsbuild?.installDevServerMiddleware !== false)
? {
setup: createServerSetup({
serverFnBasePath: serverFnBase,
+ serverOutputDirectory:
+ resolvedStartConfig.outputDirectories.server,
+ publicBase: resolvedStartConfig.basePaths.publicBase,
}),
}
: {}),
diff --git a/packages/start-plugin-core/src/rsbuild/server-middleware.ts b/packages/start-plugin-core/src/rsbuild/server-middleware.ts
new file mode 100644
index 0000000000..70e2f234ce
--- /dev/null
+++ b/packages/start-plugin-core/src/rsbuild/server-middleware.ts
@@ -0,0 +1,217 @@
+import { resolve } from 'node:path'
+import { pathToFileURL } from 'node:url'
+import { NodeRequest, sendNodeResponse } from 'srvx/node'
+import { joinURL } from 'ufo'
+import { RSBUILD_ENVIRONMENT_NAMES } from './planning'
+import type { IncomingMessage, ServerResponse } from 'node:http'
+import type { RsbuildConfig } from '@rsbuild/core'
+
+type ServerSetupFn = Extract<
+ NonNullable['setup']>,
+ (...args: Array) => any
+>
+type ServerSetupContext = Parameters[0]
+type SSRMiddleware = (
+ req: IncomingMessage & { originalUrl?: string },
+ res: ServerResponse,
+ next: (error?: unknown) => void,
+) => Promise
+type FetchHandler = (req: Request) => Promise | Response
+
+type ServerEntry =
+ | FetchHandler
+ | {
+ fetch?: FetchHandler
+ }
+
+function resolveFetchHandler(serverEntry: ServerEntry): FetchHandler {
+ if (typeof serverEntry === 'function') {
+ return serverEntry
+ }
+
+ if (typeof serverEntry.fetch === 'function') {
+ return serverEntry.fetch.bind(serverEntry)
+ }
+
+ throw new Error(
+ 'Unable to resolve a request handler from Rsbuild server bundle',
+ )
+}
+
+function getPublicBasePathname(publicBase: string): string {
+ try {
+ return new URL(publicBase, 'http://localhost').pathname
+ } catch {
+ return publicBase
+ }
+}
+
+function restorePreviewUrl(opts: {
+ req: IncomingMessage & { originalUrl?: string }
+ publicBase: string
+}) {
+ if (opts.req.originalUrl) {
+ opts.req.url = opts.req.originalUrl
+ }
+
+ const publicBasePathname = getPublicBasePathname(opts.publicBase)
+ if (publicBasePathname === '/') {
+ return
+ }
+
+ const url = opts.req.url ?? '/'
+ if (url.startsWith(publicBasePathname)) {
+ return
+ }
+
+ opts.req.url = joinURL(publicBasePathname, url)
+}
+
+async function loadDevFetchHandler(
+ context: ServerSetupContext,
+): Promise {
+ if (context.action !== 'dev') {
+ throw new Error('Cannot load Rsbuild dev SSR bundle outside dev mode')
+ }
+
+ const ssrEnv = context.server.environments[RSBUILD_ENVIRONMENT_NAMES.server]
+
+ if (!ssrEnv) {
+ throw new Error(
+ `SSR environment "${RSBUILD_ENVIRONMENT_NAMES.server}" not found`,
+ )
+ }
+
+ const serverEntry = await ssrEnv.loadBundle<{ default: ServerEntry }>('index')
+ return resolveFetchHandler(serverEntry.default)
+}
+
+/**
+ * Returns a `server.setup` function for rsbuild v2.
+ *
+ * Two middleware positions are used:
+ *
+ * 1. **Setup body** (BEFORE built-ins): Intercepts `/_serverFn/` URLs so
+ * they never reach rsbuild's htmlFallback/htmlCompletion middleware,
+ * which can swallow long base64 function IDs.
+ *
+ * 2. **Returned callback** (AFTER built-ins, BEFORE fallback): Handles
+ * all remaining SSR requests (page navigations). This position lets
+ * rsbuild's asset middleware serve compiled JS/CSS first.
+ *
+ * The middleware choreography is shared by dev and preview. The server entry
+ * loader differs: dev reads from Rsbuild's in-memory environment so rebuilds
+ * are reflected immediately, while preview lazy-imports the production server
+ * bundle from disk.
+ *
+ * See rsbuild source: devMiddlewares.ts `applyDefaultMiddlewares()` and
+ * previewServer.ts `startPreviewServer()`.
+ */
+export function createServerSetup(opts: {
+ serverFnBasePath: string
+ serverOutputDirectory: string
+ publicBase: string
+}): ServerSetupFn {
+ let previewFetchHandlerPromise: Promise | undefined
+
+ const getPreviewFetchHandler = () => {
+ if (!previewFetchHandlerPromise) {
+ previewFetchHandlerPromise = loadPreviewFetchHandler(
+ opts.serverOutputDirectory,
+ )
+ }
+
+ return previewFetchHandlerPromise
+ }
+
+ return (context) => {
+ const serverFnBase = opts.serverFnBasePath
+
+ const handleSSR: SSRMiddleware = async (req, res, next) => {
+ try {
+ const fetchHandler =
+ context.action === 'dev'
+ ? await loadDevFetchHandler(context)
+ : await getPreviewFetchHandler()
+
+ if (context.action === 'preview') {
+ // Rsbuild preview's base middleware strips server.base before the
+ // returned setup callback runs. Put it back before creating the Web
+ // Request so Start's router sees the same URL shape as build/custom
+ // servers. The early server-fn middleware runs before that base
+ // middleware, so avoid prepending when the URL already has the base.
+ restorePreviewUrl({ req, publicBase: opts.publicBase })
+ } else if (req.originalUrl) {
+ // Restore the original URL (rsbuild may rewrite to /index.html)
+ req.url = req.originalUrl
+ }
+
+ const webReq = new NodeRequest({ req, res })
+ const webRes = await fetchHandler(webReq)
+ return sendNodeResponse(res, webRes)
+ } catch (e) {
+ console.error('[tanstack-start] SSR error:', e)
+
+ const webReq = new NodeRequest({ req, res })
+ if (webReq.headers.get('content-type')?.includes('application/json')) {
+ return sendNodeResponse(
+ res,
+ new Response(
+ JSON.stringify(
+ {
+ status: 500,
+ error: 'Internal Server Error',
+ message:
+ 'An unexpected error occurred. Please try again later.',
+ timestamp: new Date().toISOString(),
+ },
+ null,
+ 2,
+ ),
+ {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ ),
+ )
+ }
+
+ return next(e)
+ }
+ }
+
+ // Position 1: BEFORE built-ins — intercept server function calls
+ // early so they are not swallowed by htmlFallback or assetsMiddleware.
+ context.server.middlewares.use(async (req, res, next) => {
+ const url = req.url || '/'
+ if (url.startsWith(serverFnBase)) {
+ return handleSSR(req, res, next)
+ }
+ return next()
+ })
+
+ // Position 2: AFTER built-ins, before fallback — SSR catch-all for
+ // page navigations. Assets are already handled by rsbuild middleware.
+ return () => {
+ context.server.middlewares.use(handleSSR)
+ }
+ }
+}
+
+async function loadPreviewFetchHandler(
+ serverOutputDirectory: string,
+): Promise {
+ const serverEntryPath = resolve(serverOutputDirectory, 'index.js')
+ const imported = (await import(
+ pathToFileURL(serverEntryPath).toString()
+ )) as { default: ServerEntry }
+
+ try {
+ return resolveFetchHandler(imported.default)
+ } catch (error) {
+ throw new Error(
+ `Unable to resolve a request handler from Rsbuild server bundle at ${serverEntryPath}`,
+ { cause: error },
+ )
+ }
+}
diff --git a/packages/vue-start/package.json b/packages/vue-start/package.json
index b4c35e32d5..c91f957527 100644
--- a/packages/vue-start/package.json
+++ b/packages/vue-start/package.json
@@ -121,7 +121,7 @@
"pathe": "^2.0.3"
},
"devDependencies": {
- "@rsbuild/core": "^2.0.8",
+ "@rsbuild/core": "^2.0.11",
"@tanstack/router-utils": "workspace:*",
"@types/node": ">=20",
"@vitejs/plugin-vue-jsx": "^4.1.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4917d0514f..a7d0915b77 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -219,11 +219,11 @@ importers:
version: 3.5.25(typescript@6.0.2)
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tanstack/router-plugin':
specifier: workspace:*
version: link:../../packages/router-plugin
@@ -1244,11 +1244,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -1296,11 +1296,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -1519,11 +1519,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -1922,11 +1922,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tanstack/router-e2e-utils':
specifier: workspace:^
version: link:../../e2e-utils
@@ -2093,11 +2093,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -2148,11 +2148,11 @@ importers:
version: 19.2.3(react@19.2.3)
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.1
- version: 2.0.1
+ specifier: ^2.0.11
+ version: 2.0.14
'@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.21))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tanstack/router-e2e-utils':
specifier: workspace:^
version: link:../../e2e-utils
@@ -2341,11 +2341,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -2451,11 +2451,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tanstack/router-e2e-utils':
specifier: workspace:^
version: link:../../e2e-utils
@@ -2610,11 +2610,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tanstack/eslint-plugin-start':
specifier: workspace:^
version: link:../../../packages/eslint-plugin-start
@@ -2779,7 +2779,7 @@ importers:
version: 2.0.8
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tanstack/router-e2e-utils':
specifier: workspace:^
version: link:../../e2e-utils
@@ -3007,11 +3007,11 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -4105,14 +4105,14 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-babel':
specifier: ^1.1.2
- version: 1.1.2(@rsbuild/core@2.0.8)
+ version: 1.1.2(@rsbuild/core@2.0.14)
'@rsbuild/plugin-solid':
specifier: ^1.1.1
- version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.8)(solid-js@1.9.12)
+ version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.14)(solid-js@1.9.12)
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -4151,14 +4151,14 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-babel':
specifier: ^1.1.2
- version: 1.1.2(@rsbuild/core@2.0.8)
+ version: 1.1.2(@rsbuild/core@2.0.14)
'@rsbuild/plugin-solid':
specifier: ^1.1.1
- version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.8)(solid-js@1.9.12)
+ version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.14)(solid-js@1.9.12)
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -4344,14 +4344,14 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-babel':
specifier: ^1.1.2
- version: 1.1.2(@rsbuild/core@2.0.8)
+ version: 1.1.2(@rsbuild/core@2.0.14)
'@rsbuild/plugin-solid':
specifier: ^1.1.1
- version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.8)(solid-js@1.9.12)
+ version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.14)(solid-js@1.9.12)
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -4733,14 +4733,14 @@ importers:
version: 1.9.12
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.1
- version: 2.0.1
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-babel':
specifier: ^1.1.2
- version: 1.1.2(@rsbuild/core@2.0.1)
+ version: 1.1.2(@rsbuild/core@2.0.14)
'@rsbuild/plugin-solid':
specifier: ^1.1.1
- version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.1)(solid-js@1.9.12)
+ version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.14)(solid-js@1.9.12)
'@tanstack/router-e2e-utils':
specifier: workspace:^
version: link:../../e2e-utils
@@ -5933,17 +5933,17 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-babel':
specifier: ^1.1.2
- version: 1.1.2(@rsbuild/core@2.0.8)
+ version: 1.1.2(@rsbuild/core@2.0.14)
'@rsbuild/plugin-vue':
specifier: ^1.2.7
- version: 1.2.7(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2))
+ version: 1.2.7(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2))
'@rsbuild/plugin-vue-jsx':
specifier: ^2.0.0
- version: 2.0.0(@babel/core@7.29.0)(@rsbuild/core@2.0.8)
+ version: 2.0.0(@babel/core@7.29.0)(@rsbuild/core@2.0.14)
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -5988,17 +5988,17 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-babel':
specifier: ^1.1.2
- version: 1.1.2(@rsbuild/core@2.0.8)
+ version: 1.1.2(@rsbuild/core@2.0.14)
'@rsbuild/plugin-vue':
specifier: ^1.2.7
- version: 1.2.7(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2))
+ version: 1.2.7(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2))
'@rsbuild/plugin-vue-jsx':
specifier: ^2.0.0
- version: 2.0.0(@babel/core@7.29.0)(@rsbuild/core@2.0.8)
+ version: 2.0.0(@babel/core@7.29.0)(@rsbuild/core@2.0.14)
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -6211,17 +6211,17 @@ importers:
specifier: ^1.57.0
version: 1.58.0
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-babel':
specifier: ^1.0.5
- version: 1.0.6(@rsbuild/core@2.0.8)
+ version: 1.0.6(@rsbuild/core@2.0.14)
'@rsbuild/plugin-vue':
specifier: ^1.2.2
- version: 1.2.2(@rsbuild/core@2.0.8)(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.27.4)(vue@3.5.25(typescript@6.0.2))
+ version: 1.2.2(@rsbuild/core@2.0.14)(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.27.4)(vue@3.5.25(typescript@6.0.2))
'@rsbuild/plugin-vue-jsx':
specifier: ^1.1.1
- version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.8)
+ version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.14)
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
@@ -8284,11 +8284,11 @@ importers:
version: 4.2.2
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-react':
specifier: ^2.0.0
- version: 2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))
+ version: 2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))
'@tanstack/router-plugin':
specifier: workspace:*
version: link:../../../packages/router-plugin
@@ -11257,14 +11257,14 @@ importers:
version: 4.2.2
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@rsbuild/plugin-babel':
specifier: ^1.1.2
- version: 1.1.2(@rsbuild/core@2.0.8)
+ version: 1.1.2(@rsbuild/core@2.0.14)
'@rsbuild/plugin-solid':
specifier: ^1.1.1
- version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.8)(solid-js@1.9.12)
+ version: 1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.14)(solid-js@1.9.12)
'@tanstack/router-plugin':
specifier: workspace:*
version: link:../../../packages/router-plugin
@@ -12722,8 +12722,8 @@ importers:
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)
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@types/node':
specifier: 25.0.9
version: 25.0.9
@@ -13214,8 +13214,8 @@ importers:
version: 1.9.12
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@tanstack/router-utils':
specifier: workspace:*
version: link:../router-utils
@@ -13368,8 +13368,8 @@ importers:
version: 4.4.3
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@types/babel__code-frame':
specifier: ^7.0.6
version: 7.0.6
@@ -13606,8 +13606,8 @@ importers:
version: 2.0.3
devDependencies:
'@rsbuild/core':
- specifier: ^2.0.8
- version: 2.0.8
+ specifier: ^2.0.11
+ version: 2.0.14
'@tanstack/router-utils':
specifier: workspace:*
version: link:../router-utils
@@ -18353,6 +18353,16 @@ packages:
core-js:
optional: true
+ '@rsbuild/core@2.0.14':
+ resolution: {integrity: sha512-SSPer6vM+v82CV6JXIOzyyT++A1ECgsVvvUuveD5rfGxX/W58vWGnB+zWcYbMfYOjTntD+9ve6QGGh6kBjPx6A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ core-js: '>= 3.0.0'
+ peerDependenciesMeta:
+ core-js:
+ optional: true
+
'@rsbuild/core@2.0.8':
resolution: {integrity: sha512-V5Bhn3zqljsaB5grw9oMkSk6XZFvMkr6UpIYPZnDmOWsRAT1fNQ6XF6cn8fVUKv/KILblBrKLUZf0DpvDt3exw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -18431,6 +18441,11 @@ packages:
cpu: [arm64]
os: [darwin]
+ '@rspack/binding-darwin-arm64@2.0.8':
+ resolution: {integrity: sha512-vCgbgH7B7qom+uID+RCZsTCOYFb9wC4/4+1U6rMfytrXGVJ72eNQs2tbdjOl0lb18CT3N/n+VkWynUiLk84GwA==}
+ cpu: [arm64]
+ os: [darwin]
+
'@rspack/binding-darwin-x64@2.0.0':
resolution: {integrity: sha512-YQ96LMmzIzhZt9cZWUDWXSxS9UWWHWoLxJyZ5f42DSaVPVelBg5ThbVORDwOP5QDA2xFXj60rVnmmcZLzg/aDA==}
cpu: [x64]
@@ -18441,6 +18456,11 @@ packages:
cpu: [x64]
os: [darwin]
+ '@rspack/binding-darwin-x64@2.0.8':
+ resolution: {integrity: sha512-satPm2PD4B7jDTVlVAdvMVdUszwLvWUEnUDzLb77mvVkezKNDZmuhb+e8s+FfKs8hJpNbZ9VAejuA2rr8o985w==}
+ cpu: [x64]
+ os: [darwin]
+
'@rspack/binding-linux-arm64-gnu@2.0.0':
resolution: {integrity: sha512-Ufn33gzkIV7JY69k6vJQEdOzRvBqThIgH46pwXksHSMwRZp8IbJhXfyYIAVsRWCk8fXpr9t1nAvCDvJXT2EeyA==}
cpu: [arm64]
@@ -18453,6 +18473,12 @@ packages:
os: [linux]
libc: [glibc]
+ '@rspack/binding-linux-arm64-gnu@2.0.8':
+ resolution: {integrity: sha512-pSI+npPQE/uDtiboqvcOIRJbEV2+B+H1xffmko/gw50la92oTUW60kVULFwsb6L0+GVCzIcwX3yq60GtYIn+Ug==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
'@rspack/binding-linux-arm64-musl@2.0.0':
resolution: {integrity: sha512-CZbvFKlNY9UC0C+Czz6i8JFCzGpuL9oX8gEqcJA1+84Y6eEEBH50UiTzeCewxKW3dOofkZdvT5vgNMXz6aMUmg==}
cpu: [arm64]
@@ -18465,6 +18491,12 @@ packages:
os: [linux]
libc: [musl]
+ '@rspack/binding-linux-arm64-musl@2.0.8':
+ resolution: {integrity: sha512-igjJ43yxWQ72GZqjDDZSSHax9/Vg+6rLMmOvFglTJUkQpB4Tyvu/YjW+WRjYj2xRw6blOjLxUSJWASvuSqqlvg==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
'@rspack/binding-linux-x64-gnu@2.0.0':
resolution: {integrity: sha512-dPjFGpoCvZfFpJBsWAUR+PR7mWYxpou6L026qIOpAVkz7WiTzErwKD3P1jVrpP4dM9yLb3fVE+PHHjTglhTJ4g==}
cpu: [x64]
@@ -18477,6 +18509,12 @@ packages:
os: [linux]
libc: [glibc]
+ '@rspack/binding-linux-x64-gnu@2.0.8':
+ resolution: {integrity: sha512-zrkoEOnqj1hOEBO5T2I/2Ts2HSJsYFh1qXwMpK4dMJFGGNWDfNeUa6/LF5uq3VINF3JUl7RL47AgrucoSZJXPA==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
'@rspack/binding-linux-x64-musl@2.0.0':
resolution: {integrity: sha512-4fgDTMWt0mJDiugdia2mdOjTbnm7yM1Drzl1JpPqlUlOr113byOhc+qgN57LURSGypz2yz/h/Zad7/UnVAxYJw==}
cpu: [x64]
@@ -18489,6 +18527,12 @@ packages:
os: [linux]
libc: [musl]
+ '@rspack/binding-linux-x64-musl@2.0.8':
+ resolution: {integrity: sha512-6CtDaGZjNDvJd9TBp7a9zABbrPORO21W96+3ZcGBn0YNUPUk4ARxIxrTTpeJ/1F41QDM8AYIkGDdqEYMqTYBsA==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
'@rspack/binding-wasm32-wasi@2.0.0':
resolution: {integrity: sha512-ANk73ZKtPrZf9gdtyRK2nQUfhi1uXoC5P2KF89pyVAE8+zcoLBnYtZGYpWa/cmNi5BcO5g4Z+v2l1UA3bUPLQQ==}
cpu: [wasm32]
@@ -18497,6 +18541,10 @@ packages:
resolution: {integrity: sha512-duEkRoXrl9SW8uGHv7JURJ5lgKu87qFDQ4Exy6UQPvsUJVXhtRXTfvMHCb/CejVJuW2Bw2D632/axZq3qRSuBQ==}
cpu: [wasm32]
+ '@rspack/binding-wasm32-wasi@2.0.8':
+ resolution: {integrity: sha512-Yf4SiqTUroT5Ju+te0YAY2xxKOb35tECsO21v7hYyGa705wrgoAK/MmF7enOvs9GR1iZIqgiLD/wxsIxl8GjJw==}
+ cpu: [wasm32]
+
'@rspack/binding-win32-arm64-msvc@2.0.0':
resolution: {integrity: sha512-IHZFRtJ85ONbM+BCtF4TeYXS2Fu9X0IJS2phX1rPibYq9iEtHGfBt4cNlnsJPhbPAXVvi4Oli/yiLRJ1zxtCIg==}
cpu: [arm64]
@@ -18507,6 +18555,11 @@ packages:
cpu: [arm64]
os: [win32]
+ '@rspack/binding-win32-arm64-msvc@2.0.8':
+ resolution: {integrity: sha512-8NCuiQsAhXrwRBy57QZoypqrws/zLBkaQVGiB8hksr6v++8hNigNjqpQARLbd0iyMuHsQQ++8+auGk6xlDXmzw==}
+ cpu: [arm64]
+ os: [win32]
+
'@rspack/binding-win32-ia32-msvc@2.0.0':
resolution: {integrity: sha512-n4tbIqacq/FhNJflMlgZV50AeQFTLh5hnDS3v4W+rJWa3IW1VfgB0+XppdeW+Dqhw7QcMIsCmro01kwNdlXZDQ==}
cpu: [ia32]
@@ -18517,6 +18570,11 @@ packages:
cpu: [ia32]
os: [win32]
+ '@rspack/binding-win32-ia32-msvc@2.0.8':
+ resolution: {integrity: sha512-bxiekytbX7V9KFAra+HkwtNWC6pYfHEBBZFpiT0xUs3mCFOmAAFVBsBSQsoCP9AdCEXoMAvNdnrHNw3iov4OZw==}
+ cpu: [ia32]
+ os: [win32]
+
'@rspack/binding-win32-x64-msvc@2.0.0':
resolution: {integrity: sha512-cJOgikIW2t3S+42TQZsv+DJriJt2m6lnUk+pUFu/fO93rrMvNrx8gfMxR8W5zDTreBX0cfMx2pw6EVmyi/YzsQ==}
cpu: [x64]
@@ -18527,12 +18585,20 @@ packages:
cpu: [x64]
os: [win32]
+ '@rspack/binding-win32-x64-msvc@2.0.8':
+ resolution: {integrity: sha512-7zPs8YCe/ZVJTwd+5lpB0CP0tkn2pONf/T1ycmVY76u21Nrwt8mXQGc/2yH2eWP4B7fikYBr3hGr7mpR2fajqQ==}
+ cpu: [x64]
+ os: [win32]
+
'@rspack/binding@2.0.0':
resolution: {integrity: sha512-WA2f9eQpejkvf5Vrnf6wNCn1m8RT1p08NjgOZpKhsCzr0uBjWeRvGduawlrFFHZh/jPnWZTVaVdQ08FEAWbwGw==}
'@rspack/binding@2.0.5':
resolution: {integrity: sha512-Ta1y4WXJA87wM1OstqaMddoPsBGv7Cu779bYToKxEAqR/Yy9DxLkp7bdgBaAx2JH++BwVjV+toWts2V9AaiTFQ==}
+ '@rspack/binding@2.0.8':
+ resolution: {integrity: sha512-3uZ+y8aQxq33ty2srMxg2Nu0XuBI6vVrG50rkDaXqwWqOohfgGUSfFuQK7EnSUNy4aFUQlCG6NHialQHJov0wg==}
+
'@rspack/core@2.0.0':
resolution: {integrity: sha512-WD1mJM9LbZ7Z399Rbv9dE3BNEV0+3sE5OzDdzV8hOxUb3mX++ynK5n9kil8w60B6nGdcKeV9ly5aN4PgqiwWUg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -18557,6 +18623,18 @@ packages:
'@swc/helpers':
optional: true
+ '@rspack/core@2.0.8':
+ resolution: {integrity: sha512-+NLGJf8gZxihDmMFzjlly3toc2SMjeDmuvz0/Cai9AMdV4F+Pqcnt2BA9V4e3SY2jmhJQtPwgyyLtR1RiJO77g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0
+ '@swc/helpers': ^0.5.23
+ peerDependenciesMeta:
+ '@module-federation/runtime-tools':
+ optional: true
+ '@swc/helpers':
+ optional: true
+
'@rspack/lite-tapable@1.1.0':
resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==}
@@ -31699,6 +31777,13 @@ snapshots:
transitivePeerDependencies:
- '@module-federation/runtime-tools'
+ '@rsbuild/core@2.0.14':
+ dependencies:
+ '@rspack/core': 2.0.8(@swc/helpers@0.5.23)
+ '@swc/helpers': 0.5.23
+ transitivePeerDependencies:
+ - '@module-federation/runtime-tools'
+
'@rsbuild/core@2.0.8':
dependencies:
'@rspack/core': 2.0.5(@swc/helpers@0.5.23)
@@ -31706,13 +31791,13 @@ snapshots:
transitivePeerDependencies:
- '@module-federation/runtime-tools'
- '@rsbuild/plugin-babel@1.0.6(@rsbuild/core@2.0.8)':
+ '@rsbuild/plugin-babel@1.0.6(@rsbuild/core@2.0.14)':
dependencies:
'@babel/core': 7.28.5
'@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.5)
'@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.5)
'@babel/preset-typescript': 7.28.5(@babel/core@7.28.5)
- '@rsbuild/core': 2.0.8
+ '@rsbuild/core': 2.0.14
'@types/babel__core': 7.20.5
deepmerge: 4.3.1
reduce-configs: 1.1.1
@@ -31720,20 +31805,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@rsbuild/plugin-babel@1.1.2(@rsbuild/core@2.0.1)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0)
- '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0)
- '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0)
- '@types/babel__core': 7.20.5
- reduce-configs: 1.1.1
- optionalDependencies:
- '@rsbuild/core': 2.0.1
- transitivePeerDependencies:
- - supports-color
-
- '@rsbuild/plugin-babel@1.1.2(@rsbuild/core@2.0.8)':
+ '@rsbuild/plugin-babel@1.1.2(@rsbuild/core@2.0.14)':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0)
@@ -31742,77 +31814,65 @@ snapshots:
'@types/babel__core': 7.20.5
reduce-configs: 1.1.1
optionalDependencies:
- '@rsbuild/core': 2.0.8
+ '@rsbuild/core': 2.0.14
transitivePeerDependencies:
- supports-color
- '@rsbuild/plugin-react@2.0.0(@rsbuild/core@2.0.1)(@rspack/core@2.0.5(@swc/helpers@0.5.21))':
+ '@rsbuild/plugin-react@2.0.0(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))':
dependencies:
- '@rspack/plugin-react-refresh': 2.0.0(@rspack/core@2.0.5(@swc/helpers@0.5.21))(react-refresh@0.18.0)
+ '@rspack/plugin-react-refresh': 2.0.0(@rspack/core@2.0.8(@swc/helpers@0.5.23))(react-refresh@0.18.0)
react-refresh: 0.18.0
optionalDependencies:
- '@rsbuild/core': 2.0.1
+ '@rsbuild/core': 2.0.14
transitivePeerDependencies:
- '@rspack/core'
- '@rsbuild/plugin-react@2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))':
+ '@rsbuild/plugin-react@2.0.0(@rsbuild/core@2.0.8)(@rspack/core@2.0.8(@swc/helpers@0.5.23))':
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.8(@swc/helpers@0.5.23))(react-refresh@0.18.0)
react-refresh: 0.18.0
optionalDependencies:
'@rsbuild/core': 2.0.8
transitivePeerDependencies:
- '@rspack/core'
- '@rsbuild/plugin-solid@1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.1)(solid-js@1.9.12)':
+ '@rsbuild/plugin-solid@1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.14)(solid-js@1.9.12)':
dependencies:
- '@rsbuild/plugin-babel': 1.1.2(@rsbuild/core@2.0.1)
+ '@rsbuild/plugin-babel': 1.1.2(@rsbuild/core@2.0.14)
babel-preset-solid: 1.9.10(@babel/core@7.29.0)(solid-js@1.9.12)
solid-refresh: 0.7.8(solid-js@1.9.12)
optionalDependencies:
- '@rsbuild/core': 2.0.1
- transitivePeerDependencies:
- - '@babel/core'
- - solid-js
- - supports-color
-
- '@rsbuild/plugin-solid@1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.8)(solid-js@1.9.12)':
- dependencies:
- '@rsbuild/plugin-babel': 1.1.2(@rsbuild/core@2.0.8)
- babel-preset-solid: 1.9.10(@babel/core@7.29.0)(solid-js@1.9.12)
- solid-refresh: 0.7.8(solid-js@1.9.12)
- optionalDependencies:
- '@rsbuild/core': 2.0.8
+ '@rsbuild/core': 2.0.14
transitivePeerDependencies:
- '@babel/core'
- solid-js
- supports-color
- '@rsbuild/plugin-vue-jsx@1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.8)':
+ '@rsbuild/plugin-vue-jsx@1.1.1(@babel/core@7.29.0)(@rsbuild/core@2.0.14)':
dependencies:
- '@rsbuild/plugin-babel': 1.1.2(@rsbuild/core@2.0.8)
+ '@rsbuild/plugin-babel': 1.1.2(@rsbuild/core@2.0.14)
'@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0)
babel-plugin-vue-jsx-hmr: 1.0.0
optionalDependencies:
- '@rsbuild/core': 2.0.8
+ '@rsbuild/core': 2.0.14
transitivePeerDependencies:
- '@babel/core'
- supports-color
- '@rsbuild/plugin-vue-jsx@2.0.0(@babel/core@7.29.0)(@rsbuild/core@2.0.8)':
+ '@rsbuild/plugin-vue-jsx@2.0.0(@babel/core@7.29.0)(@rsbuild/core@2.0.14)':
dependencies:
- '@rsbuild/plugin-babel': 1.1.2(@rsbuild/core@2.0.8)
+ '@rsbuild/plugin-babel': 1.1.2(@rsbuild/core@2.0.14)
'@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0)
babel-plugin-vue-jsx-hmr: 1.0.0
optionalDependencies:
- '@rsbuild/core': 2.0.8
+ '@rsbuild/core': 2.0.14
transitivePeerDependencies:
- '@babel/core'
- supports-color
- '@rsbuild/plugin-vue@1.2.2(@rsbuild/core@2.0.8)(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.27.4)(vue@3.5.25(typescript@6.0.2))':
+ '@rsbuild/plugin-vue@1.2.2(@rsbuild/core@2.0.14)(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.27.4)(vue@3.5.25(typescript@6.0.2))':
dependencies:
- '@rsbuild/core': 2.0.8
+ '@rsbuild/core': 2.0.14
rspack-vue-loader: 17.4.4(vue@3.5.25(typescript@6.0.2))(webpack@5.104.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.27.4))
webpack: 5.104.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.27.4)
transitivePeerDependencies:
@@ -31823,11 +31883,11 @@ snapshots:
- vue
- webpack-cli
- '@rsbuild/plugin-vue@1.2.7(@rsbuild/core@2.0.8)(@rspack/core@2.0.5(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2))':
+ '@rsbuild/plugin-vue@1.2.7(@rsbuild/core@2.0.14)(@rspack/core@2.0.8(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2))':
dependencies:
- rspack-vue-loader: 17.5.0(@rspack/core@2.0.5(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2))
+ rspack-vue-loader: 17.5.0(@rspack/core@2.0.8(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2))
optionalDependencies:
- '@rsbuild/core': 2.0.8
+ '@rsbuild/core': 2.0.14
transitivePeerDependencies:
- '@rspack/core'
- '@vue/compiler-sfc'
@@ -31839,36 +31899,54 @@ snapshots:
'@rspack/binding-darwin-arm64@2.0.5':
optional: true
+ '@rspack/binding-darwin-arm64@2.0.8':
+ optional: true
+
'@rspack/binding-darwin-x64@2.0.0':
optional: true
'@rspack/binding-darwin-x64@2.0.5':
optional: true
+ '@rspack/binding-darwin-x64@2.0.8':
+ optional: true
+
'@rspack/binding-linux-arm64-gnu@2.0.0':
optional: true
'@rspack/binding-linux-arm64-gnu@2.0.5':
optional: true
+ '@rspack/binding-linux-arm64-gnu@2.0.8':
+ optional: true
+
'@rspack/binding-linux-arm64-musl@2.0.0':
optional: true
'@rspack/binding-linux-arm64-musl@2.0.5':
optional: true
+ '@rspack/binding-linux-arm64-musl@2.0.8':
+ optional: true
+
'@rspack/binding-linux-x64-gnu@2.0.0':
optional: true
'@rspack/binding-linux-x64-gnu@2.0.5':
optional: true
+ '@rspack/binding-linux-x64-gnu@2.0.8':
+ optional: true
+
'@rspack/binding-linux-x64-musl@2.0.0':
optional: true
'@rspack/binding-linux-x64-musl@2.0.5':
optional: true
+ '@rspack/binding-linux-x64-musl@2.0.8':
+ optional: true
+
'@rspack/binding-wasm32-wasi@2.0.0':
dependencies:
'@emnapi/core': 1.10.0
@@ -31883,24 +31961,40 @@ snapshots:
'@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
optional: true
+ '@rspack/binding-wasm32-wasi@2.0.8':
+ dependencies:
+ '@emnapi/core': 1.10.0
+ '@emnapi/runtime': 1.10.0
+ '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ optional: true
+
'@rspack/binding-win32-arm64-msvc@2.0.0':
optional: true
'@rspack/binding-win32-arm64-msvc@2.0.5':
optional: true
+ '@rspack/binding-win32-arm64-msvc@2.0.8':
+ optional: true
+
'@rspack/binding-win32-ia32-msvc@2.0.0':
optional: true
'@rspack/binding-win32-ia32-msvc@2.0.5':
optional: true
+ '@rspack/binding-win32-ia32-msvc@2.0.8':
+ optional: true
+
'@rspack/binding-win32-x64-msvc@2.0.0':
optional: true
'@rspack/binding-win32-x64-msvc@2.0.5':
optional: true
+ '@rspack/binding-win32-x64-msvc@2.0.8':
+ optional: true
+
'@rspack/binding@2.0.0':
optionalDependencies:
'@rspack/binding-darwin-arm64': 2.0.0
@@ -31927,6 +32021,19 @@ snapshots:
'@rspack/binding-win32-ia32-msvc': 2.0.5
'@rspack/binding-win32-x64-msvc': 2.0.5
+ '@rspack/binding@2.0.8':
+ optionalDependencies:
+ '@rspack/binding-darwin-arm64': 2.0.8
+ '@rspack/binding-darwin-x64': 2.0.8
+ '@rspack/binding-linux-arm64-gnu': 2.0.8
+ '@rspack/binding-linux-arm64-musl': 2.0.8
+ '@rspack/binding-linux-x64-gnu': 2.0.8
+ '@rspack/binding-linux-x64-musl': 2.0.8
+ '@rspack/binding-wasm32-wasi': 2.0.8
+ '@rspack/binding-win32-arm64-msvc': 2.0.8
+ '@rspack/binding-win32-ia32-msvc': 2.0.8
+ '@rspack/binding-win32-x64-msvc': 2.0.8
+
'@rspack/core@2.0.0(@swc/helpers@0.5.21)':
dependencies:
'@rspack/binding': 2.0.0
@@ -31939,32 +32046,25 @@ snapshots:
optionalDependencies:
'@swc/helpers': 0.5.23
- '@rspack/core@2.0.5(@swc/helpers@0.5.21)':
+ '@rspack/core@2.0.5(@swc/helpers@0.5.23)':
dependencies:
'@rspack/binding': 2.0.5
optionalDependencies:
- '@swc/helpers': 0.5.21
- optional: true
+ '@swc/helpers': 0.5.23
- '@rspack/core@2.0.5(@swc/helpers@0.5.23)':
+ '@rspack/core@2.0.8(@swc/helpers@0.5.23)':
dependencies:
- '@rspack/binding': 2.0.5
+ '@rspack/binding': 2.0.8
optionalDependencies:
'@swc/helpers': 0.5.23
'@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)':
+ '@rspack/plugin-react-refresh@2.0.0(@rspack/core@2.0.8(@swc/helpers@0.5.23))(react-refresh@0.18.0)':
dependencies:
react-refresh: 0.18.0
optionalDependencies:
- '@rspack/core': 2.0.5(@swc/helpers@0.5.23)
+ '@rspack/core': 2.0.8(@swc/helpers@0.5.23)
'@rushstack/node-core-library@5.7.0(@types/node@25.0.9)':
dependencies:
@@ -39774,12 +39874,12 @@ snapshots:
optionalDependencies:
vue: 3.5.25(typescript@6.0.2)
- rspack-vue-loader@17.5.0(@rspack/core@2.0.5(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2)):
+ rspack-vue-loader@17.5.0(@rspack/core@2.0.8(@swc/helpers@0.5.23))(vue@3.5.25(typescript@6.0.2)):
dependencies:
'@rspack/lite-tapable': 1.1.0
chalk: 4.1.2
optionalDependencies:
- '@rspack/core': 2.0.5(@swc/helpers@0.5.23)
+ '@rspack/core': 2.0.8(@swc/helpers@0.5.23)
vue: 3.5.25(typescript@6.0.2)
run-applescript@7.0.0: {}
From 1362bf22d08b1f3369511f32f7d480f077a7f674 Mon Sep 17 00:00:00 2001
From: Flo
Date: Tue, 16 Jun 2026 12:12:14 +0200
Subject: [PATCH 05/25] ci: pnpm trustPolicy no-downgrade exclude semver@6.3.1
(#7645)
---
pnpm-workspace.yaml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 002be83894..fb3c02cdcc 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -5,7 +5,8 @@ blockExoticSubdeps: true
trustPolicy: 'no-downgrade'
trustPolicyExclude:
- - 'chokidar@4.0.3'
+ - 'chokidar@4.0.3' # known to be safe, installed by sass, prisma, netlify... no planned releases in v4, packages is on v5 now
+ - 'semver@6.3.1' # installed by babel, ecosystem has been on v7 for 7 years now, but babel is horribly out of date, 6.3.1 is the latest v6 and was released 3 years ago
packages:
- 'packages/*'
From f23ed0fe737b3d856ca76f4fa3d2fdbafd72acab Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 17 Jun 2026 00:12:54 +0200
Subject: [PATCH 06/25] ci: Version Packages (#7576)
---
.changeset/empty-olives-melt.md | 5 -
.changeset/four-carrots-post.md | 5 -
.changeset/large-insects-own.md | 5 -
.changeset/major-poems-drop.md | 5 -
.changeset/proud-clubs-buy.md | 5 -
.changeset/quiet-rsbuild-preview.md | 5 -
.changeset/silent-ghosts-sit.md | 5 -
.changeset/upset-eagles-happen.md | 5 -
.../package.json | 2 +-
.../react/authenticated-routes/package.json | 2 +-
.../basic-default-search-params/package.json | 2 +-
.../react/basic-devtools-panel/package.json | 2 +-
examples/react/basic-file-based/package.json | 2 +-
.../basic-non-nested-devtools/package.json | 2 +-
.../basic-react-query-file-based/package.json | 2 +-
examples/react/basic-react-query/package.json | 2 +-
.../react/basic-ssr-file-based/package.json | 2 +-
.../package.json | 2 +-
.../basic-virtual-file-based/package.json | 2 +-
.../package.json | 2 +-
examples/react/basic/package.json | 2 +-
examples/react/deferred-data/package.json | 2 +-
examples/react/i18n-paraglide/package.json | 2 +-
.../kitchen-sink-file-based/package.json | 2 +-
.../package.json | 2 +-
.../kitchen-sink-react-query/package.json | 2 +-
examples/react/kitchen-sink/package.json | 2 +-
examples/react/large-file-based/package.json | 2 +-
examples/react/location-masking/package.json | 2 +-
.../react/navigation-blocking/package.json | 2 +-
.../package.json | 2 +-
.../react/quickstart-file-based/package.json | 2 +-
.../quickstart-rspack-file-based/package.json | 2 +-
.../package.json | 2 +-
examples/react/quickstart/package.json | 2 +-
.../router-monorepo-react-query/package.json | 2 +-
.../packages/router/package.json | 2 +-
.../router-monorepo-simple-lazy/package.json | 2 +-
.../packages/router/package.json | 2 +-
.../react/router-monorepo-simple/package.json | 2 +-
.../packages/router/package.json | 2 +-
.../react/scroll-restoration/package.json | 2 +-
.../search-validator-adapters/package.json | 2 +-
examples/react/start-bare/package.json | 4 +-
examples/react/start-basic-auth/package.json | 4 +-
.../react/start-basic-authjs/package.json | 4 +-
.../react/start-basic-cloudflare/package.json | 4 +-
.../start-basic-react-query/package.json | 4 +-
.../react/start-basic-static/package.json | 4 +-
examples/react/start-basic/package.json | 4 +-
examples/react/start-bun/package.json | 4 +-
examples/react/start-clerk-basic/package.json | 4 +-
.../react/start-convex-trellaux/package.json | 4 +-
examples/react/start-counter/package.json | 4 +-
.../react/start-i18n-paraglide/package.json | 4 +-
examples/react/start-large/package.json | 4 +-
examples/react/start-material-ui/package.json | 4 +-
examples/react/start-rscs/package.json | 4 +-
.../package.json | 4 +-
.../react/start-supabase-basic/package.json | 4 +-
examples/react/start-tailwind-v4/package.json | 4 +-
examples/react/start-trellaux/package.json | 4 +-
examples/react/start-workos/package.json | 4 +-
examples/react/view-transitions/package.json | 2 +-
.../react/with-framer-motion/package.json | 2 +-
.../react/with-trpc-react-query/package.json | 2 +-
examples/react/with-trpc/package.json | 2 +-
.../package.json | 2 +-
.../solid/authenticated-routes/package.json | 2 +-
.../basic-default-search-params/package.json | 2 +-
.../solid/basic-devtools-panel/package.json | 2 +-
examples/solid/basic-file-based/package.json | 2 +-
.../basic-non-nested-devtools/package.json | 2 +-
.../basic-solid-query-file-based/package.json | 2 +-
examples/solid/basic-solid-query/package.json | 2 +-
.../solid/basic-ssr-file-based/package.json | 2 +-
.../package.json | 2 +-
.../basic-virtual-file-based/package.json | 2 +-
.../package.json | 2 +-
examples/solid/basic/package.json | 2 +-
examples/solid/deferred-data/package.json | 2 +-
examples/solid/i18n-paraglide/package.json | 2 +-
.../kitchen-sink-file-based/package.json | 2 +-
.../package.json | 2 +-
.../kitchen-sink-solid-query/package.json | 2 +-
examples/solid/kitchen-sink/package.json | 2 +-
examples/solid/large-file-based/package.json | 2 +-
examples/solid/location-masking/package.json | 2 +-
.../solid/navigation-blocking/package.json | 2 +-
.../package.json | 2 +-
.../solid/quickstart-file-based/package.json | 2 +-
.../quickstart-rspack-file-based/package.json | 2 +-
.../package.json | 2 +-
examples/solid/quickstart/package.json | 2 +-
.../router-monorepo-simple-lazy/package.json | 2 +-
.../packages/router/package.json | 2 +-
.../solid/router-monorepo-simple/package.json | 2 +-
.../packages/router/package.json | 2 +-
.../router-monorepo-solid-query/package.json | 2 +-
.../packages/router/package.json | 2 +-
.../solid/scroll-restoration/package.json | 2 +-
.../search-validator-adapters/package.json | 2 +-
examples/solid/start-basic-auth/package.json | 4 +-
.../solid/start-basic-authjs/package.json | 4 +-
.../solid/start-basic-cloudflare/package.json | 4 +-
.../solid/start-basic-netlify/package.json | 4 +-
examples/solid/start-basic-nitro/package.json | 4 +-
.../start-basic-solid-query/package.json | 4 +-
.../solid/start-basic-static/package.json | 4 +-
examples/solid/start-basic/package.json | 4 +-
examples/solid/start-bun/package.json | 4 +-
.../start-convex-better-auth/package.json | 4 +-
examples/solid/start-counter/package.json | 4 +-
.../solid/start-i18n-paraglide/package.json | 4 +-
examples/solid/start-large/package.json | 4 +-
.../package.json | 4 +-
.../solid/start-supabase-basic/package.json | 4 +-
examples/solid/start-tailwind-v4/package.json | 4 +-
examples/solid/view-transitions/package.json | 2 +-
.../solid/with-framer-motion/package.json | 2 +-
examples/solid/with-trpc/package.json | 2 +-
packages/react-router/CHANGELOG.md | 14 +++
packages/react-router/package.json | 2 +-
packages/react-start-client/CHANGELOG.md | 7 ++
packages/react-start-client/package.json | 2 +-
packages/react-start-rsc/CHANGELOG.md | 9 ++
packages/react-start-rsc/package.json | 2 +-
packages/react-start-server/CHANGELOG.md | 8 ++
packages/react-start-server/package.json | 2 +-
packages/react-start/CHANGELOG.md | 12 +++
packages/react-start/package.json | 2 +-
packages/solid-router/CHANGELOG.md | 6 ++
packages/solid-router/package.json | 2 +-
packages/solid-start-client/CHANGELOG.md | 7 ++
packages/solid-start-client/package.json | 2 +-
packages/solid-start-server/CHANGELOG.md | 8 ++
packages/solid-start-server/package.json | 2 +-
packages/solid-start/CHANGELOG.md | 11 ++
packages/solid-start/package.json | 2 +-
packages/start-plugin-core/CHANGELOG.md | 10 ++
packages/start-plugin-core/package.json | 2 +-
packages/start-server-core/CHANGELOG.md | 6 ++
packages/start-server-core/package.json | 2 +-
packages/vue-start-server/CHANGELOG.md | 7 ++
packages/vue-start-server/package.json | 2 +-
packages/vue-start/CHANGELOG.md | 9 ++
packages/vue-start/package.json | 2 +-
pnpm-lock.yaml | 102 +++++++++---------
148 files changed, 327 insertions(+), 253 deletions(-)
delete mode 100644 .changeset/empty-olives-melt.md
delete mode 100644 .changeset/four-carrots-post.md
delete mode 100644 .changeset/large-insects-own.md
delete mode 100644 .changeset/major-poems-drop.md
delete mode 100644 .changeset/proud-clubs-buy.md
delete mode 100644 .changeset/quiet-rsbuild-preview.md
delete mode 100644 .changeset/silent-ghosts-sit.md
delete mode 100644 .changeset/upset-eagles-happen.md
diff --git a/.changeset/empty-olives-melt.md b/.changeset/empty-olives-melt.md
deleted file mode 100644
index 97f0be2113..0000000000
--- a/.changeset/empty-olives-melt.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/react-router': patch
----
-
-Perf improvement of useMatch and derived hooks when navigating away from previous match
diff --git a/.changeset/four-carrots-post.md b/.changeset/four-carrots-post.md
deleted file mode 100644
index b9a1bcafee..0000000000
--- a/.changeset/four-carrots-post.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/react-router': patch
----
-
-useMatches can skip useRef for structural sharing during SSR
diff --git a/.changeset/large-insects-own.md b/.changeset/large-insects-own.md
deleted file mode 100644
index 514f393abf..0000000000
--- a/.changeset/large-insects-own.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/start-server-core': patch
----
-
-Skip a full router.update for faster createStartHandler
diff --git a/.changeset/major-poems-drop.md b/.changeset/major-poems-drop.md
deleted file mode 100644
index 6ffd45a57e..0000000000
--- a/.changeset/major-poems-drop.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/react-router': patch
----
-
-Reduce Match rerenders during navigations (PR #7596)
diff --git a/.changeset/proud-clubs-buy.md b/.changeset/proud-clubs-buy.md
deleted file mode 100644
index 4418e5101f..0000000000
--- a/.changeset/proud-clubs-buy.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/solid-router': patch
----
-
-Remove unnecessary setTimeout from Match components
diff --git a/.changeset/quiet-rsbuild-preview.md b/.changeset/quiet-rsbuild-preview.md
deleted file mode 100644
index f3378b21ff..0000000000
--- a/.changeset/quiet-rsbuild-preview.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/start-plugin-core': patch
----
-
-Fix Rsbuild preview support for TanStack Start SSR. Preview always installs the SSR middleware; the `installDevServerMiddleware` option only applies to dev.
diff --git a/.changeset/silent-ghosts-sit.md b/.changeset/silent-ghosts-sit.md
deleted file mode 100644
index d59d368570..0000000000
--- a/.changeset/silent-ghosts-sit.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/react-router': patch
----
-
-Reduce bundle size by sharing structuralSharing selector logic
diff --git a/.changeset/upset-eagles-happen.md b/.changeset/upset-eagles-happen.md
deleted file mode 100644
index 6369010b3d..0000000000
--- a/.changeset/upset-eagles-happen.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/react-router': patch
----
-
-Silence AbortError in renderRouterToStream, this is normal operation
diff --git a/examples/react/authenticated-routes-firebase/package.json b/examples/react/authenticated-routes-firebase/package.json
index f9111e84f7..dafac3ef01 100644
--- a/examples/react/authenticated-routes-firebase/package.json
+++ b/examples/react/authenticated-routes-firebase/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"firebase": "^11.4.0",
diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json
index e1e1985dbc..164e0c3b07 100644
--- a/examples/react/authenticated-routes/package.json
+++ b/examples/react/authenticated-routes/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json
index d157c36aa4..afda60a9de 100644
--- a/examples/react/basic-default-search-params/package.json
+++ b/examples/react/basic-default-search-params/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-devtools-panel/package.json b/examples/react/basic-devtools-panel/package.json
index d6a71e6b56..bc5502b64a 100644
--- a/examples/react/basic-devtools-panel/package.json
+++ b/examples/react/basic-devtools-panel/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query-devtools": "^5.67.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json
index 2d2a65bd32..c9b60b4a41 100644
--- a/examples/react/basic-file-based/package.json
+++ b/examples/react/basic-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/basic-non-nested-devtools/package.json b/examples/react/basic-non-nested-devtools/package.json
index c663f35cab..496bf4f428 100644
--- a/examples/react/basic-non-nested-devtools/package.json
+++ b/examples/react/basic-non-nested-devtools/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json
index f5efe842dc..5e4d1c16e4 100644
--- a/examples/react/basic-react-query-file-based/package.json
+++ b/examples/react/basic-react-query-file-based/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json
index 95b23d62e7..8afb35f672 100644
--- a/examples/react/basic-react-query/package.json
+++ b/examples/react/basic-react-query/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json
index 3154f64c52..b7df3723d4 100644
--- a/examples/react/basic-ssr-file-based/package.json
+++ b/examples/react/basic-ssr-file-based/package.json
@@ -11,7 +11,7 @@
"debug": "node --inspect-brk server"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"compression": "^1.8.0",
"express": "^5.2.1",
diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json
index b02db5be81..25b6c3ba79 100644
--- a/examples/react/basic-ssr-streaming-file-based/package.json
+++ b/examples/react/basic-ssr-streaming-file-based/package.json
@@ -11,7 +11,7 @@
"debug": "node --inspect-brk server"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"compression": "^1.8.0",
"express": "^5.2.1",
diff --git a/examples/react/basic-virtual-file-based/package.json b/examples/react/basic-virtual-file-based/package.json
index bb69746201..b6456f16c4 100644
--- a/examples/react/basic-virtual-file-based/package.json
+++ b/examples/react/basic-virtual-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@tanstack/virtual-file-routes": "^1.162.0",
diff --git a/examples/react/basic-virtual-inside-file-based/package.json b/examples/react/basic-virtual-inside-file-based/package.json
index 284a3ee3cc..f3b7ded6b5 100644
--- a/examples/react/basic-virtual-inside-file-based/package.json
+++ b/examples/react/basic-virtual-inside-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@tanstack/virtual-file-routes": "^1.162.0",
diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json
index 03787ad0e7..500d62838d 100644
--- a/examples/react/basic/package.json
+++ b/examples/react/basic/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json
index de4c82a3b9..ce4d0a23f9 100644
--- a/examples/react/deferred-data/package.json
+++ b/examples/react/deferred-data/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/i18n-paraglide/package.json b/examples/react/i18n-paraglide/package.json
index a4477eb5a3..ae8a8cf345 100644
--- a/examples/react/i18n-paraglide/package.json
+++ b/examples/react/i18n-paraglide/package.json
@@ -11,7 +11,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.1.1",
"react-dom": "^19.1.1",
diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json
index a89c35d8e9..f52cc14be2 100644
--- a/examples/react/kitchen-sink-file-based/package.json
+++ b/examples/react/kitchen-sink-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"immer": "^10.1.1",
diff --git a/examples/react/kitchen-sink-react-query-file-based/package.json b/examples/react/kitchen-sink-react-query-file-based/package.json
index 4f9e9f2255..a31d6bfd58 100644
--- a/examples/react/kitchen-sink-react-query-file-based/package.json
+++ b/examples/react/kitchen-sink-react-query-file-based/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"immer": "^10.1.1",
diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json
index 8940db7373..bd7cc359ec 100644
--- a/examples/react/kitchen-sink-react-query/package.json
+++ b/examples/react/kitchen-sink-react-query/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"react": "^19.0.0",
diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json
index 4fe86d8b37..40de321285 100644
--- a/examples/react/kitchen-sink/package.json
+++ b/examples/react/kitchen-sink/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"react": "^19.0.0",
diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json
index 3dd028bb0b..75429bd82a 100644
--- a/examples/react/large-file-based/package.json
+++ b/examples/react/large-file-based/package.json
@@ -13,7 +13,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json
index 12450ee97a..f593f59ba2 100644
--- a/examples/react/location-masking/package.json
+++ b/examples/react/location-masking/package.json
@@ -12,7 +12,7 @@
"@radix-ui/react-dialog": "^1.1.6",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json
index 238028ff84..b0255cf627 100644
--- a/examples/react/navigation-blocking/package.json
+++ b/examples/react/navigation-blocking/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json
index afdee5a742..4817cba83b 100644
--- a/examples/react/quickstart-esbuild-file-based/package.json
+++ b/examples/react/quickstart-esbuild-file-based/package.json
@@ -9,7 +9,7 @@
"start": "dev"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/quickstart-file-based/package.json b/examples/react/quickstart-file-based/package.json
index b083377a99..53d26bfa71 100644
--- a/examples/react/quickstart-file-based/package.json
+++ b/examples/react/quickstart-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json
index 51346b18da..2ee1c96f0d 100644
--- a/examples/react/quickstart-rspack-file-based/package.json
+++ b/examples/react/quickstart-rspack-file-based/package.json
@@ -9,7 +9,7 @@
},
"dependencies": {
"@tailwindcss/postcss": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"postcss": "^8.5.1",
"react": "^19.0.0",
diff --git a/examples/react/quickstart-webpack-file-based/package.json b/examples/react/quickstart-webpack-file-based/package.json
index 743215e62c..5e16ac8b4f 100644
--- a/examples/react/quickstart-webpack-file-based/package.json
+++ b/examples/react/quickstart-webpack-file-based/package.json
@@ -7,7 +7,7 @@
"build": "webpack build && tsc --noEmit"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json
index 6e95bc5057..19bd01d412 100644
--- a/examples/react/quickstart/package.json
+++ b/examples/react/quickstart/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/router-monorepo-react-query/package.json b/examples/react/router-monorepo-react-query/package.json
index b10e34bdaf..25de375254 100644
--- a/examples/react/router-monorepo-react-query/package.json
+++ b/examples/react/router-monorepo-react-query/package.json
@@ -12,7 +12,7 @@
"dependencies": {
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/router-monorepo-react-query/packages/router/package.json b/examples/react/router-monorepo-react-query/packages/router/package.json
index 622da2aed5..a0ef2fabd1 100644
--- a/examples/react/router-monorepo-react-query/packages/router/package.json
+++ b/examples/react/router-monorepo-react-query/packages/router/package.json
@@ -11,7 +11,7 @@
"@router-mono-react-query/post-query": "workspace:*",
"@tanstack/history": "^1.162.0",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/router-monorepo-simple-lazy/package.json b/examples/react/router-monorepo-simple-lazy/package.json
index 8e24568583..e6a6b51a7d 100644
--- a/examples/react/router-monorepo-simple-lazy/package.json
+++ b/examples/react/router-monorepo-simple-lazy/package.json
@@ -8,7 +8,7 @@
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/router-monorepo-simple-lazy/packages/router/package.json b/examples/react/router-monorepo-simple-lazy/packages/router/package.json
index 3fd61be7b1..55dfce9be7 100644
--- a/examples/react/router-monorepo-simple-lazy/packages/router/package.json
+++ b/examples/react/router-monorepo-simple-lazy/packages/router/package.json
@@ -9,7 +9,7 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/history": "^1.162.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/router-monorepo-simple/package.json b/examples/react/router-monorepo-simple/package.json
index f539e82a54..907bdc33a8 100644
--- a/examples/react/router-monorepo-simple/package.json
+++ b/examples/react/router-monorepo-simple/package.json
@@ -8,7 +8,7 @@
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/router-monorepo-simple/packages/router/package.json b/examples/react/router-monorepo-simple/packages/router/package.json
index 8503c8935e..755e90987e 100644
--- a/examples/react/router-monorepo-simple/packages/router/package.json
+++ b/examples/react/router-monorepo-simple/packages/router/package.json
@@ -9,7 +9,7 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/history": "^1.162.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json
index ca7dc300fb..0f14bafacc 100644
--- a/examples/react/scroll-restoration/package.json
+++ b/examples/react/scroll-restoration/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-virtual": "^3.13.0",
"react": "^19.0.0",
diff --git a/examples/react/search-validator-adapters/package.json b/examples/react/search-validator-adapters/package.json
index 93faf3821f..f0445b6f93 100644
--- a/examples/react/search-validator-adapters/package.json
+++ b/examples/react/search-validator-adapters/package.json
@@ -13,7 +13,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/arktype-adapter": "^1.167.0",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@tanstack/valibot-adapter": "^1.167.0",
diff --git a/examples/react/start-bare/package.json b/examples/react/start-bare/package.json
index 216fbbb3cc..190186a6f4 100644
--- a/examples/react/start-bare/package.json
+++ b/examples/react/start-bare/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^4.4.3"
diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json
index 62ad4b367f..9a9f93e019 100644
--- a/examples/react/start-basic-auth/package.json
+++ b/examples/react/start-basic-auth/package.json
@@ -14,9 +14,9 @@
"@libsql/client": "^0.15.15",
"@prisma/adapter-libsql": "^7.0.0",
"@prisma/client": "^7.0.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/start-basic-authjs/package.json b/examples/react/start-basic-authjs/package.json
index 07313c94a8..d1402e2c53 100644
--- a/examples/react/start-basic-authjs/package.json
+++ b/examples/react/start-basic-authjs/package.json
@@ -11,9 +11,9 @@
},
"dependencies": {
"@auth/core": "^0.41.1",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"start-authjs": "^1.0.0",
diff --git a/examples/react/start-basic-cloudflare/package.json b/examples/react/start-basic-cloudflare/package.json
index 4c223171eb..9c35d7ff6d 100644
--- a/examples/react/start-basic-cloudflare/package.json
+++ b/examples/react/start-basic-cloudflare/package.json
@@ -12,9 +12,9 @@
"postinstall": "npm run cf-typegen"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json
index f227d3800b..dcb5b81796 100644
--- a/examples/react/start-basic-react-query/package.json
+++ b/examples/react/start-basic-react-query/package.json
@@ -12,10 +12,10 @@
"dependencies": {
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-router-ssr-query": "^1.167.1",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/start-basic-static/package.json b/examples/react/start-basic-static/package.json
index 9cd22f25ee..9a2f888be5 100644
--- a/examples/react/start-basic-static/package.json
+++ b/examples/react/start-basic-static/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"@tanstack/start-static-server-functions": "^1.167.17",
"@vitejs/plugin-react": "^6.0.1",
"react": "^19.0.0",
diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json
index 08ef993e04..72372d7806 100644
--- a/examples/react/start-basic/package.json
+++ b/examples/react/start-basic/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^3.6.0",
diff --git a/examples/react/start-bun/package.json b/examples/react/start-bun/package.json
index e90822df7f..7b262c6bf7 100644
--- a/examples/react/start-bun/package.json
+++ b/examples/react/start-bun/package.json
@@ -15,10 +15,10 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-devtools": "^0.7.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-router-ssr-query": "^1.167.1",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.1.1",
"react-dom": "^19.1.1",
diff --git a/examples/react/start-clerk-basic/package.json b/examples/react/start-clerk-basic/package.json
index 68ab9e66e4..121b04bcfe 100644
--- a/examples/react/start-clerk-basic/package.json
+++ b/examples/react/start-clerk-basic/package.json
@@ -11,9 +11,9 @@
},
"dependencies": {
"@clerk/tanstack-react-start": "^0.27.14",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"@vitejs/plugin-react": "^6.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json
index d00d67dbc3..06616b8bcd 100644
--- a/examples/react/start-convex-trellaux/package.json
+++ b/examples/react/start-convex-trellaux/package.json
@@ -15,10 +15,10 @@
"@convex-dev/react-query": "0.0.0-alpha.8",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-router-ssr-query": "^1.167.1",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"concurrently": "^8.2.2",
"convex": "^1.19.0",
"ky": "^1.7.4",
diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json
index 738914667d..b9b113dcdb 100644
--- a/examples/react/start-counter/package.json
+++ b/examples/react/start-counter/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
diff --git a/examples/react/start-i18n-paraglide/package.json b/examples/react/start-i18n-paraglide/package.json
index bdc9193745..238ad332cf 100644
--- a/examples/react/start-i18n-paraglide/package.json
+++ b/examples/react/start-i18n-paraglide/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tanstack/react-devtools": "^0.7.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json
index 65167cdf0d..f26f5aa248 100644
--- a/examples/react/start-large/package.json
+++ b/examples/react/start-large/package.json
@@ -13,9 +13,9 @@
},
"dependencies": {
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/start-material-ui/package.json b/examples/react/start-material-ui/package.json
index b9656721d2..b4b1fdcc18 100644
--- a/examples/react/start-material-ui/package.json
+++ b/examples/react/start-material-ui/package.json
@@ -15,9 +15,9 @@
"@emotion/styled": "11.14.0",
"@fontsource-variable/roboto": "5.2.5",
"@mui/material": "6.4.7",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^4.4.3"
diff --git a/examples/react/start-rscs/package.json b/examples/react/start-rscs/package.json
index 58530b2ab1..e77685070f 100644
--- a/examples/react/start-rscs/package.json
+++ b/examples/react/start-rscs/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"dexie": "^4.0.10",
"react": "^19.2.0",
"react-dom": "^19.2.0",
diff --git a/examples/react/start-streaming-data-from-server-functions/package.json b/examples/react/start-streaming-data-from-server-functions/package.json
index edb78fee2f..149aed0d8e 100644
--- a/examples/react/start-streaming-data-from-server-functions/package.json
+++ b/examples/react/start-streaming-data-from-server-functions/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^4.4.3"
diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json
index 985b9d5adc..59aaaded53 100644
--- a/examples/react/start-supabase-basic/package.json
+++ b/examples/react/start-supabase-basic/package.json
@@ -15,9 +15,9 @@
"dependencies": {
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.48.1",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1"
diff --git a/examples/react/start-tailwind-v4/package.json b/examples/react/start-tailwind-v4/package.json
index 8e0ad0920a..b555b3703e 100644
--- a/examples/react/start-tailwind-v4/package.json
+++ b/examples/react/start-tailwind-v4/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^3.6.0",
diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json
index f60d918813..2de169fea6 100644
--- a/examples/react/start-trellaux/package.json
+++ b/examples/react/start-trellaux/package.json
@@ -12,10 +12,10 @@
"dependencies": {
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-router-ssr-query": "^1.167.1",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"ky": "^1.7.4",
"msw": "^2.7.0",
"react": "^19.0.0",
diff --git a/examples/react/start-workos/package.json b/examples/react/start-workos/package.json
index bbd4d3f546..4379c464b8 100644
--- a/examples/react/start-workos/package.json
+++ b/examples/react/start-workos/package.json
@@ -14,9 +14,9 @@
"license": "MIT",
"dependencies": {
"@radix-ui/themes": "^3.3.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.25",
+ "@tanstack/react-start": "^1.168.26",
"@workos/authkit-tanstack-react-start": "^0.5.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
diff --git a/examples/react/view-transitions/package.json b/examples/react/view-transitions/package.json
index 866f048d6c..3af69ed288 100644
--- a/examples/react/view-transitions/package.json
+++ b/examples/react/view-transitions/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"react": "^19.0.0",
diff --git a/examples/react/with-framer-motion/package.json b/examples/react/with-framer-motion/package.json
index 80b8fe37f5..6dee036c9e 100644
--- a/examples/react/with-framer-motion/package.json
+++ b/examples/react/with-framer-motion/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"framer-motion": "^11.18.2",
"react": "^19.0.0",
diff --git a/examples/react/with-trpc-react-query/package.json b/examples/react/with-trpc-react-query/package.json
index 3452b42c98..f849a2dc12 100644
--- a/examples/react/with-trpc-react-query/package.json
+++ b/examples/react/with-trpc-react-query/package.json
@@ -13,7 +13,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@trpc/client": "^11.4.3",
diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json
index ced1328bf5..bad08150a1 100644
--- a/examples/react/with-trpc/package.json
+++ b/examples/react/with-trpc/package.json
@@ -11,7 +11,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.15",
+ "@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@trpc/client": "^11.4.3",
diff --git a/examples/solid/authenticated-routes-firebase/package.json b/examples/solid/authenticated-routes-firebase/package.json
index 23e7d33a95..ce1178dcc3 100644
--- a/examples/solid/authenticated-routes-firebase/package.json
+++ b/examples/solid/authenticated-routes-firebase/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"firebase": "^11.4.0",
"redaxios": "^0.5.1",
diff --git a/examples/solid/authenticated-routes/package.json b/examples/solid/authenticated-routes/package.json
index e922b46783..79c7b3e35b 100644
--- a/examples/solid/authenticated-routes/package.json
+++ b/examples/solid/authenticated-routes/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-default-search-params/package.json b/examples/solid/basic-default-search-params/package.json
index 164f0cf117..7aa5a5771f 100644
--- a/examples/solid/basic-default-search-params/package.json
+++ b/examples/solid/basic-default-search-params/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-devtools-panel/package.json b/examples/solid/basic-devtools-panel/package.json
index 3ef02e48ff..21b0dbbb4d 100644
--- a/examples/solid/basic-devtools-panel/package.json
+++ b/examples/solid/basic-devtools-panel/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-file-based/package.json b/examples/solid/basic-file-based/package.json
index 69f16a2a15..3875d710ff 100644
--- a/examples/solid/basic-file-based/package.json
+++ b/examples/solid/basic-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-non-nested-devtools/package.json b/examples/solid/basic-non-nested-devtools/package.json
index ac5cad2f1f..f644b324b0 100644
--- a/examples/solid/basic-non-nested-devtools/package.json
+++ b/examples/solid/basic-non-nested-devtools/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-solid-query-file-based/package.json b/examples/solid/basic-solid-query-file-based/package.json
index 5f59cb2826..548e6630c7 100644
--- a/examples/solid/basic-solid-query-file-based/package.json
+++ b/examples/solid/basic-solid-query-file-based/package.json
@@ -13,7 +13,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-solid-query/package.json b/examples/solid/basic-solid-query/package.json
index 47fe793066..06a2d942fe 100644
--- a/examples/solid/basic-solid-query/package.json
+++ b/examples/solid/basic-solid-query/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-ssr-file-based/package.json b/examples/solid/basic-ssr-file-based/package.json
index 79709772b5..df458d40c7 100644
--- a/examples/solid/basic-ssr-file-based/package.json
+++ b/examples/solid/basic-ssr-file-based/package.json
@@ -11,7 +11,7 @@
"debug": "node --inspect-brk server"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"compression": "^1.8.0",
"express": "^5.2.1",
diff --git a/examples/solid/basic-ssr-streaming-file-based/package.json b/examples/solid/basic-ssr-streaming-file-based/package.json
index 93b32c9fa5..435d2b9337 100644
--- a/examples/solid/basic-ssr-streaming-file-based/package.json
+++ b/examples/solid/basic-ssr-streaming-file-based/package.json
@@ -12,7 +12,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"compression": "^1.8.0",
"express": "^5.2.1",
diff --git a/examples/solid/basic-virtual-file-based/package.json b/examples/solid/basic-virtual-file-based/package.json
index d20ffcf5c8..7670b540ab 100644
--- a/examples/solid/basic-virtual-file-based/package.json
+++ b/examples/solid/basic-virtual-file-based/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/virtual-file-routes": "^1.162.0",
"redaxios": "^0.5.1",
diff --git a/examples/solid/basic-virtual-inside-file-based/package.json b/examples/solid/basic-virtual-inside-file-based/package.json
index f612a19d81..29f03157a0 100644
--- a/examples/solid/basic-virtual-inside-file-based/package.json
+++ b/examples/solid/basic-virtual-inside-file-based/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/virtual-file-routes": "^1.162.0",
"redaxios": "^0.5.1",
diff --git a/examples/solid/basic/package.json b/examples/solid/basic/package.json
index 8ba2836212..e63f01566d 100644
--- a/examples/solid/basic/package.json
+++ b/examples/solid/basic/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/deferred-data/package.json b/examples/solid/deferred-data/package.json
index 6eeec469ac..6b2ba6437b 100644
--- a/examples/solid/deferred-data/package.json
+++ b/examples/solid/deferred-data/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/i18n-paraglide/package.json b/examples/solid/i18n-paraglide/package.json
index 80892c24af..ca853c7e08 100644
--- a/examples/solid/i18n-paraglide/package.json
+++ b/examples/solid/i18n-paraglide/package.json
@@ -11,7 +11,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
diff --git a/examples/solid/kitchen-sink-file-based/package.json b/examples/solid/kitchen-sink-file-based/package.json
index 0c81d80b53..8c02a9cd24 100644
--- a/examples/solid/kitchen-sink-file-based/package.json
+++ b/examples/solid/kitchen-sink-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"redaxios": "^0.5.1",
diff --git a/examples/solid/kitchen-sink-solid-query-file-based/package.json b/examples/solid/kitchen-sink-solid-query-file-based/package.json
index a392774074..3e818ed243 100644
--- a/examples/solid/kitchen-sink-solid-query-file-based/package.json
+++ b/examples/solid/kitchen-sink-solid-query-file-based/package.json
@@ -13,7 +13,7 @@
"@tanstack/router-plugin": "^1.168.18",
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"redaxios": "^0.5.1",
diff --git a/examples/solid/kitchen-sink-solid-query/package.json b/examples/solid/kitchen-sink-solid-query/package.json
index 68d0bfb922..c078ac2bb9 100644
--- a/examples/solid/kitchen-sink-solid-query/package.json
+++ b/examples/solid/kitchen-sink-solid-query/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"redaxios": "^0.5.1",
diff --git a/examples/solid/kitchen-sink/package.json b/examples/solid/kitchen-sink/package.json
index 2bc16074ac..4223f4b840 100644
--- a/examples/solid/kitchen-sink/package.json
+++ b/examples/solid/kitchen-sink/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"redaxios": "^0.5.1",
diff --git a/examples/solid/large-file-based/package.json b/examples/solid/large-file-based/package.json
index e9be9dba70..17b2ba0610 100644
--- a/examples/solid/large-file-based/package.json
+++ b/examples/solid/large-file-based/package.json
@@ -14,7 +14,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/router-plugin": "^1.168.18",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/location-masking/package.json b/examples/solid/location-masking/package.json
index cbe4fa6ad9..b15d437baf 100644
--- a/examples/solid/location-masking/package.json
+++ b/examples/solid/location-masking/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1",
diff --git a/examples/solid/navigation-blocking/package.json b/examples/solid/navigation-blocking/package.json
index 8e3c717b40..3a3d7c88e3 100644
--- a/examples/solid/navigation-blocking/package.json
+++ b/examples/solid/navigation-blocking/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1",
diff --git a/examples/solid/quickstart-esbuild-file-based/package.json b/examples/solid/quickstart-esbuild-file-based/package.json
index 92db8af6af..8ff9be64eb 100644
--- a/examples/solid/quickstart-esbuild-file-based/package.json
+++ b/examples/solid/quickstart-esbuild-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/quickstart-file-based/package.json b/examples/solid/quickstart-file-based/package.json
index 11b5a94481..16fa0bcce6 100644
--- a/examples/solid/quickstart-file-based/package.json
+++ b/examples/solid/quickstart-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/quickstart-rspack-file-based/package.json b/examples/solid/quickstart-rspack-file-based/package.json
index 58caf50475..82e1eaec3f 100644
--- a/examples/solid/quickstart-rspack-file-based/package.json
+++ b/examples/solid/quickstart-rspack-file-based/package.json
@@ -9,7 +9,7 @@
},
"dependencies": {
"@tailwindcss/postcss": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"postcss": "^8.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/quickstart-webpack-file-based/package.json b/examples/solid/quickstart-webpack-file-based/package.json
index 28d5a867a3..2a65a99a2a 100644
--- a/examples/solid/quickstart-webpack-file-based/package.json
+++ b/examples/solid/quickstart-webpack-file-based/package.json
@@ -7,7 +7,7 @@
"build": "webpack build && tsc --noEmit"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
diff --git a/examples/solid/quickstart/package.json b/examples/solid/quickstart/package.json
index f135f93b87..310ce847a1 100644
--- a/examples/solid/quickstart/package.json
+++ b/examples/solid/quickstart/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
diff --git a/examples/solid/router-monorepo-simple-lazy/package.json b/examples/solid/router-monorepo-simple-lazy/package.json
index 7f70e17c0e..d1d6bc352a 100644
--- a/examples/solid/router-monorepo-simple-lazy/package.json
+++ b/examples/solid/router-monorepo-simple-lazy/package.json
@@ -8,7 +8,7 @@
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"solid-js": "^1.9.10",
diff --git a/examples/solid/router-monorepo-simple-lazy/packages/router/package.json b/examples/solid/router-monorepo-simple-lazy/packages/router/package.json
index ecfa67e874..deefbaadac 100644
--- a/examples/solid/router-monorepo-simple-lazy/packages/router/package.json
+++ b/examples/solid/router-monorepo-simple-lazy/packages/router/package.json
@@ -9,7 +9,7 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/history": "^1.162.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"redaxios": "^0.5.1",
"zod": "^4.4.3",
diff --git a/examples/solid/router-monorepo-simple/package.json b/examples/solid/router-monorepo-simple/package.json
index 2cb1a38445..9741d561b4 100644
--- a/examples/solid/router-monorepo-simple/package.json
+++ b/examples/solid/router-monorepo-simple/package.json
@@ -8,7 +8,7 @@
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"solid-js": "^1.9.10",
diff --git a/examples/solid/router-monorepo-simple/packages/router/package.json b/examples/solid/router-monorepo-simple/packages/router/package.json
index 7ab8245732..6b5990d5c7 100644
--- a/examples/solid/router-monorepo-simple/packages/router/package.json
+++ b/examples/solid/router-monorepo-simple/packages/router/package.json
@@ -9,7 +9,7 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/history": "^1.162.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"redaxios": "^0.5.1",
"zod": "^4.4.3",
diff --git a/examples/solid/router-monorepo-solid-query/package.json b/examples/solid/router-monorepo-solid-query/package.json
index 6a4cdecf4b..50609c03ca 100644
--- a/examples/solid/router-monorepo-solid-query/package.json
+++ b/examples/solid/router-monorepo-solid-query/package.json
@@ -12,7 +12,7 @@
"dependencies": {
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"solid-js": "^1.9.10",
diff --git a/examples/solid/router-monorepo-solid-query/packages/router/package.json b/examples/solid/router-monorepo-solid-query/packages/router/package.json
index acb2835ff5..ffcf38ac81 100644
--- a/examples/solid/router-monorepo-solid-query/packages/router/package.json
+++ b/examples/solid/router-monorepo-solid-query/packages/router/package.json
@@ -10,7 +10,7 @@
"dependencies": {
"@tanstack/history": "^1.162.0",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/router-plugin": "^1.168.18",
"@router-solid-mono-solid-query/post-query": "workspace:*",
"redaxios": "^0.5.1",
diff --git a/examples/solid/scroll-restoration/package.json b/examples/solid/scroll-restoration/package.json
index 48b9af300b..482475cadf 100644
--- a/examples/solid/scroll-restoration/package.json
+++ b/examples/solid/scroll-restoration/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/solid-virtual": "^3.13.0",
"solid-js": "^1.9.10",
diff --git a/examples/solid/search-validator-adapters/package.json b/examples/solid/search-validator-adapters/package.json
index f67ea8266d..ebc44256b4 100644
--- a/examples/solid/search-validator-adapters/package.json
+++ b/examples/solid/search-validator-adapters/package.json
@@ -13,7 +13,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/arktype-adapter": "^1.167.0",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@tanstack/valibot-adapter": "^1.167.0",
diff --git a/examples/solid/start-basic-auth/package.json b/examples/solid/start-basic-auth/package.json
index a0c9fd6b3b..20f86e46b6 100644
--- a/examples/solid/start-basic-auth/package.json
+++ b/examples/solid/start-basic-auth/package.json
@@ -14,9 +14,9 @@
"@libsql/client": "^0.15.15",
"@prisma/adapter-libsql": "^7.0.0",
"@prisma/client": "^7.0.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-basic-authjs/package.json b/examples/solid/start-basic-authjs/package.json
index 3df8c7043b..f5396a2f42 100644
--- a/examples/solid/start-basic-authjs/package.json
+++ b/examples/solid/start-basic-authjs/package.json
@@ -11,9 +11,9 @@
},
"dependencies": {
"@auth/core": "^0.41.1",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"solid-js": "^1.9.10",
"start-authjs": "^1.0.0",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-basic-cloudflare/package.json b/examples/solid/start-basic-cloudflare/package.json
index 84fccfb47e..12b1f61b24 100644
--- a/examples/solid/start-basic-cloudflare/package.json
+++ b/examples/solid/start-basic-cloudflare/package.json
@@ -12,9 +12,9 @@
"postinstall": "npm run cf-typegen"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"solid-js": "^1.9.10"
},
"devDependencies": {
diff --git a/examples/solid/start-basic-netlify/package.json b/examples/solid/start-basic-netlify/package.json
index 688b5fc2aa..302e1ca086 100644
--- a/examples/solid/start-basic-netlify/package.json
+++ b/examples/solid/start-basic-netlify/package.json
@@ -9,9 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"solid-js": "^1.9.10"
},
"devDependencies": {
diff --git a/examples/solid/start-basic-nitro/package.json b/examples/solid/start-basic-nitro/package.json
index 6559147f3a..923396e37a 100644
--- a/examples/solid/start-basic-nitro/package.json
+++ b/examples/solid/start-basic-nitro/package.json
@@ -9,9 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"solid-js": "^1.9.10"
},
"devDependencies": {
diff --git a/examples/solid/start-basic-solid-query/package.json b/examples/solid/start-basic-solid-query/package.json
index c521c75ba2..5fc9f4dd82 100644
--- a/examples/solid/start-basic-solid-query/package.json
+++ b/examples/solid/start-basic-solid-query/package.json
@@ -12,10 +12,10 @@
"dependencies": {
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/solid-router-ssr-query": "^1.167.1",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-basic-static/package.json b/examples/solid/start-basic-static/package.json
index 4056ed16f3..e5bb50d54a 100644
--- a/examples/solid/start-basic-static/package.json
+++ b/examples/solid/start-basic-static/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"@tanstack/start-static-server-functions": "^1.167.17",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/start-basic/package.json b/examples/solid/start-basic/package.json
index 0c9a045310..49bd5d5fe9 100644
--- a/examples/solid/start-basic/package.json
+++ b/examples/solid/start-basic/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-bun/package.json b/examples/solid/start-bun/package.json
index a48f5b9173..dc944ee3b1 100644
--- a/examples/solid/start-bun/package.json
+++ b/examples/solid/start-bun/package.json
@@ -15,10 +15,10 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-devtools": "^0.7.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/solid-router-ssr-query": "^1.167.1",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"@tanstack/router-plugin": "^1.168.18",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
diff --git a/examples/solid/start-convex-better-auth/package.json b/examples/solid/start-convex-better-auth/package.json
index 8b668a5797..45a4bf91e6 100644
--- a/examples/solid/start-convex-better-auth/package.json
+++ b/examples/solid/start-convex-better-auth/package.json
@@ -13,9 +13,9 @@
"dependencies": {
"@convex-dev/better-auth": "^0.9.7",
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"better-auth": "^1.3.27",
"clsx": "^2.1.1",
"convex": "^1.28.2",
diff --git a/examples/solid/start-counter/package.json b/examples/solid/start-counter/package.json
index 5a39877d9c..6eaf05c9a1 100644
--- a/examples/solid/start-counter/package.json
+++ b/examples/solid/start-counter/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0",
diff --git a/examples/solid/start-i18n-paraglide/package.json b/examples/solid/start-i18n-paraglide/package.json
index 44ef7c1516..05a5e59bb6 100644
--- a/examples/solid/start-i18n-paraglide/package.json
+++ b/examples/solid/start-i18n-paraglide/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tanstack/solid-devtools": "^0.7.0",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"solid-js": "^1.9.10"
},
"devDependencies": {
diff --git a/examples/solid/start-large/package.json b/examples/solid/start-large/package.json
index 07ab65acbc..ccceb6045b 100644
--- a/examples/solid/start-large/package.json
+++ b/examples/solid/start-large/package.json
@@ -13,9 +13,9 @@
},
"dependencies": {
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0",
diff --git a/examples/solid/start-streaming-data-from-server-functions/package.json b/examples/solid/start-streaming-data-from-server-functions/package.json
index bc460906fd..6b62a70528 100644
--- a/examples/solid/start-streaming-data-from-server-functions/package.json
+++ b/examples/solid/start-streaming-data-from-server-functions/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"solid-js": "^1.9.10",
"zod": "^4.4.3"
},
diff --git a/examples/solid/start-supabase-basic/package.json b/examples/solid/start-supabase-basic/package.json
index fb6e5cd849..5dc8d866e8 100644
--- a/examples/solid/start-supabase-basic/package.json
+++ b/examples/solid/start-supabase-basic/package.json
@@ -14,9 +14,9 @@
"dependencies": {
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.48.1",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"solid-js": "^1.9.9",
"redaxios": "^0.5.1"
},
diff --git a/examples/solid/start-tailwind-v4/package.json b/examples/solid/start-tailwind-v4/package.json
index 4c9e734210..52caa94c8f 100644
--- a/examples/solid/start-tailwind-v4/package.json
+++ b/examples/solid/start-tailwind-v4/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.25",
+ "@tanstack/solid-start": "^1.168.26",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0",
"zod": "^4.4.3"
diff --git a/examples/solid/view-transitions/package.json b/examples/solid/view-transitions/package.json
index a5f00675fc..c2cd643fd7 100644
--- a/examples/solid/view-transitions/package.json
+++ b/examples/solid/view-transitions/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/with-framer-motion/package.json b/examples/solid/with-framer-motion/package.json
index cd36f584a5..d0af6cf9bb 100644
--- a/examples/solid/with-framer-motion/package.json
+++ b/examples/solid/with-framer-motion/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/with-trpc/package.json b/examples/solid/with-trpc/package.json
index f21bd60104..fa9dfaea02 100644
--- a/examples/solid/with-trpc/package.json
+++ b/examples/solid/with-trpc/package.json
@@ -12,7 +12,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.15",
+ "@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@trpc/client": "^11.4.3",
"@trpc/server": "^11.4.3",
diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md
index e2a8891a70..0e2cdc0de4 100644
--- a/packages/react-router/CHANGELOG.md
+++ b/packages/react-router/CHANGELOG.md
@@ -1,5 +1,19 @@
# @tanstack/react-router
+## 1.170.16
+
+### Patch Changes
+
+- [#7595](https://github.com/TanStack/router/pull/7595) [`52db703`](https://github.com/TanStack/router/commit/52db7034fd83b17cc45da9ba775ec253f2a9a149) - Perf improvement of useMatch and derived hooks when navigating away from previous match
+
+- [#7575](https://github.com/TanStack/router/pull/7575) [`4a93cff`](https://github.com/TanStack/router/commit/4a93cffffca34702e419bc47db827f964fd0cb65) - useMatches can skip useRef for structural sharing during SSR
+
+- [#7600](https://github.com/TanStack/router/pull/7600) [`f046b06`](https://github.com/TanStack/router/commit/f046b064d32222cad0b7ea061311ca9a474f5990) - Reduce Match rerenders during navigations (PR [#7596](https://github.com/TanStack/router/issues/7596))
+
+- [#7577](https://github.com/TanStack/router/pull/7577) [`689d88e`](https://github.com/TanStack/router/commit/689d88e04c48ec76a18453ab8b82d8d58f2668b6) - Reduce bundle size by sharing structuralSharing selector logic
+
+- [#7627](https://github.com/TanStack/router/pull/7627) [`689c5ab`](https://github.com/TanStack/router/commit/689c5ab32e9bb18d90a5a94c33237b0e418df3e3) - Silence AbortError in renderRouterToStream, this is normal operation
+
## 1.170.15
### Patch Changes
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index 59ebe688ee..ff94646687 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-router",
- "version": "1.170.15",
+ "version": "1.170.16",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/react-start-client/CHANGELOG.md b/packages/react-start-client/CHANGELOG.md
index 1019b96387..b7fa5058dc 100644
--- a/packages/react-start-client/CHANGELOG.md
+++ b/packages/react-start-client/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/react-start-client
+## 1.168.14
+
+### Patch Changes
+
+- Updated dependencies [[`52db703`](https://github.com/TanStack/router/commit/52db7034fd83b17cc45da9ba775ec253f2a9a149), [`4a93cff`](https://github.com/TanStack/router/commit/4a93cffffca34702e419bc47db827f964fd0cb65), [`f046b06`](https://github.com/TanStack/router/commit/f046b064d32222cad0b7ea061311ca9a474f5990), [`689d88e`](https://github.com/TanStack/router/commit/689d88e04c48ec76a18453ab8b82d8d58f2668b6), [`689c5ab`](https://github.com/TanStack/router/commit/689c5ab32e9bb18d90a5a94c33237b0e418df3e3)]:
+ - @tanstack/react-router@1.170.16
+
## 1.168.13
### Patch Changes
diff --git a/packages/react-start-client/package.json b/packages/react-start-client/package.json
index b34c6f4033..888a9cc609 100644
--- a/packages/react-start-client/package.json
+++ b/packages/react-start-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-start-client",
- "version": "1.168.13",
+ "version": "1.168.14",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/react-start-rsc/CHANGELOG.md b/packages/react-start-rsc/CHANGELOG.md
index d64d4a1f8e..70b1c785aa 100644
--- a/packages/react-start-rsc/CHANGELOG.md
+++ b/packages/react-start-rsc/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/react-start-rsc
+## 0.1.25
+
+### Patch Changes
+
+- Updated dependencies [[`52db703`](https://github.com/TanStack/router/commit/52db7034fd83b17cc45da9ba775ec253f2a9a149), [`4a93cff`](https://github.com/TanStack/router/commit/4a93cffffca34702e419bc47db827f964fd0cb65), [`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43), [`f046b06`](https://github.com/TanStack/router/commit/f046b064d32222cad0b7ea061311ca9a474f5990), [`e499164`](https://github.com/TanStack/router/commit/e499164c722841271af4aa2215483476bdb94c94), [`689d88e`](https://github.com/TanStack/router/commit/689d88e04c48ec76a18453ab8b82d8d58f2668b6), [`689c5ab`](https://github.com/TanStack/router/commit/689c5ab32e9bb18d90a5a94c33237b0e418df3e3)]:
+ - @tanstack/react-router@1.170.16
+ - @tanstack/start-server-core@1.169.15
+ - @tanstack/start-plugin-core@1.171.18
+
## 0.1.24
### Patch Changes
diff --git a/packages/react-start-rsc/package.json b/packages/react-start-rsc/package.json
index 1f7ce47a02..ccaa3a1195 100644
--- a/packages/react-start-rsc/package.json
+++ b/packages/react-start-rsc/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-start-rsc",
- "version": "0.1.24",
+ "version": "0.1.25",
"description": "React Server Components support for TanStack Start",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/react-start-server/CHANGELOG.md b/packages/react-start-server/CHANGELOG.md
index 2b3ecb3dc6..e78748be9b 100644
--- a/packages/react-start-server/CHANGELOG.md
+++ b/packages/react-start-server/CHANGELOG.md
@@ -1,5 +1,13 @@
# @tanstack/react-start-server
+## 1.167.20
+
+### Patch Changes
+
+- Updated dependencies [[`52db703`](https://github.com/TanStack/router/commit/52db7034fd83b17cc45da9ba775ec253f2a9a149), [`4a93cff`](https://github.com/TanStack/router/commit/4a93cffffca34702e419bc47db827f964fd0cb65), [`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43), [`f046b06`](https://github.com/TanStack/router/commit/f046b064d32222cad0b7ea061311ca9a474f5990), [`689d88e`](https://github.com/TanStack/router/commit/689d88e04c48ec76a18453ab8b82d8d58f2668b6), [`689c5ab`](https://github.com/TanStack/router/commit/689c5ab32e9bb18d90a5a94c33237b0e418df3e3)]:
+ - @tanstack/react-router@1.170.16
+ - @tanstack/start-server-core@1.169.15
+
## 1.167.19
### Patch Changes
diff --git a/packages/react-start-server/package.json b/packages/react-start-server/package.json
index 1a10090d2f..54b1b5759a 100644
--- a/packages/react-start-server/package.json
+++ b/packages/react-start-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-start-server",
- "version": "1.167.19",
+ "version": "1.167.20",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/react-start/CHANGELOG.md b/packages/react-start/CHANGELOG.md
index a11c4f8699..42ea16093d 100644
--- a/packages/react-start/CHANGELOG.md
+++ b/packages/react-start/CHANGELOG.md
@@ -1,5 +1,17 @@
# @tanstack/react-start
+## 1.168.26
+
+### Patch Changes
+
+- Updated dependencies [[`52db703`](https://github.com/TanStack/router/commit/52db7034fd83b17cc45da9ba775ec253f2a9a149), [`4a93cff`](https://github.com/TanStack/router/commit/4a93cffffca34702e419bc47db827f964fd0cb65), [`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43), [`f046b06`](https://github.com/TanStack/router/commit/f046b064d32222cad0b7ea061311ca9a474f5990), [`e499164`](https://github.com/TanStack/router/commit/e499164c722841271af4aa2215483476bdb94c94), [`689d88e`](https://github.com/TanStack/router/commit/689d88e04c48ec76a18453ab8b82d8d58f2668b6), [`689c5ab`](https://github.com/TanStack/router/commit/689c5ab32e9bb18d90a5a94c33237b0e418df3e3)]:
+ - @tanstack/react-router@1.170.16
+ - @tanstack/start-server-core@1.169.15
+ - @tanstack/start-plugin-core@1.171.18
+ - @tanstack/react-start-client@1.168.14
+ - @tanstack/react-start-rsc@0.1.25
+ - @tanstack/react-start-server@1.167.20
+
## 1.168.25
### Patch Changes
diff --git a/packages/react-start/package.json b/packages/react-start/package.json
index cbaae21f27..b037e44f8f 100644
--- a/packages/react-start/package.json
+++ b/packages/react-start/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-start",
- "version": "1.168.25",
+ "version": "1.168.26",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/solid-router/CHANGELOG.md b/packages/solid-router/CHANGELOG.md
index 996b09965e..23b61a6dd7 100644
--- a/packages/solid-router/CHANGELOG.md
+++ b/packages/solid-router/CHANGELOG.md
@@ -1,5 +1,11 @@
# @tanstack/solid-router
+## 1.170.16
+
+### Patch Changes
+
+- [#7584](https://github.com/TanStack/router/pull/7584) [`41e7a24`](https://github.com/TanStack/router/commit/41e7a24f693b0f58c2bef89a2b8c4d084acbd531) - Remove unnecessary setTimeout from Match components
+
## 1.170.15
### Patch Changes
diff --git a/packages/solid-router/package.json b/packages/solid-router/package.json
index 207ac5fda7..6b8e7a2949 100644
--- a/packages/solid-router/package.json
+++ b/packages/solid-router/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/solid-router",
- "version": "1.170.15",
+ "version": "1.170.16",
"description": "Modern and scalable routing for Solid applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/solid-start-client/CHANGELOG.md b/packages/solid-start-client/CHANGELOG.md
index a6d6727dfe..3b9ebc20ce 100644
--- a/packages/solid-start-client/CHANGELOG.md
+++ b/packages/solid-start-client/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/solid-start-client
+## 1.168.14
+
+### Patch Changes
+
+- Updated dependencies [[`41e7a24`](https://github.com/TanStack/router/commit/41e7a24f693b0f58c2bef89a2b8c4d084acbd531)]:
+ - @tanstack/solid-router@1.170.16
+
## 1.168.13
### Patch Changes
diff --git a/packages/solid-start-client/package.json b/packages/solid-start-client/package.json
index 234a55d8d3..e043dba060 100644
--- a/packages/solid-start-client/package.json
+++ b/packages/solid-start-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/solid-start-client",
- "version": "1.168.13",
+ "version": "1.168.14",
"description": "Modern and scalable routing for Solid applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/solid-start-server/CHANGELOG.md b/packages/solid-start-server/CHANGELOG.md
index bc33bdd103..433045af39 100644
--- a/packages/solid-start-server/CHANGELOG.md
+++ b/packages/solid-start-server/CHANGELOG.md
@@ -1,5 +1,13 @@
# @tanstack/solid-start-server
+## 1.167.20
+
+### Patch Changes
+
+- Updated dependencies [[`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43), [`41e7a24`](https://github.com/TanStack/router/commit/41e7a24f693b0f58c2bef89a2b8c4d084acbd531)]:
+ - @tanstack/start-server-core@1.169.15
+ - @tanstack/solid-router@1.170.16
+
## 1.167.19
### Patch Changes
diff --git a/packages/solid-start-server/package.json b/packages/solid-start-server/package.json
index 4e3d7df1fb..8d2ed4817a 100644
--- a/packages/solid-start-server/package.json
+++ b/packages/solid-start-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/solid-start-server",
- "version": "1.167.19",
+ "version": "1.167.20",
"description": "Modern and scalable routing for Solid applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/solid-start/CHANGELOG.md b/packages/solid-start/CHANGELOG.md
index b0156a34fc..ff2ba4779a 100644
--- a/packages/solid-start/CHANGELOG.md
+++ b/packages/solid-start/CHANGELOG.md
@@ -1,5 +1,16 @@
# @tanstack/solid-start
+## 1.168.26
+
+### Patch Changes
+
+- Updated dependencies [[`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43), [`41e7a24`](https://github.com/TanStack/router/commit/41e7a24f693b0f58c2bef89a2b8c4d084acbd531), [`e499164`](https://github.com/TanStack/router/commit/e499164c722841271af4aa2215483476bdb94c94)]:
+ - @tanstack/start-server-core@1.169.15
+ - @tanstack/solid-router@1.170.16
+ - @tanstack/start-plugin-core@1.171.18
+ - @tanstack/solid-start-server@1.167.20
+ - @tanstack/solid-start-client@1.168.14
+
## 1.168.25
### Patch Changes
diff --git a/packages/solid-start/package.json b/packages/solid-start/package.json
index 8059adf57c..4b11e9a219 100644
--- a/packages/solid-start/package.json
+++ b/packages/solid-start/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/solid-start",
- "version": "1.168.25",
+ "version": "1.168.26",
"description": "Modern and scalable routing for Solid applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/start-plugin-core/CHANGELOG.md b/packages/start-plugin-core/CHANGELOG.md
index 5f78c2ceed..a611c1f2d2 100644
--- a/packages/start-plugin-core/CHANGELOG.md
+++ b/packages/start-plugin-core/CHANGELOG.md
@@ -1,5 +1,15 @@
# @tanstack/start-plugin-core
+## 1.171.18
+
+### Patch Changes
+
+- [#7372](https://github.com/TanStack/router/pull/7372) [`e499164`](https://github.com/TanStack/router/commit/e499164c722841271af4aa2215483476bdb94c94) - Fix Rsbuild preview support for TanStack Start SSR. Preview always installs the SSR middleware; the `installDevServerMiddleware` option only applies to dev.
+
+- Updated dependencies [[`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43)]:
+ - @tanstack/start-server-core@1.169.15
+ - @tanstack/router-generator@1.167.17
+
## 1.171.17
### Patch Changes
diff --git a/packages/start-plugin-core/package.json b/packages/start-plugin-core/package.json
index b2f5cec25c..b0f1c1e09d 100644
--- a/packages/start-plugin-core/package.json
+++ b/packages/start-plugin-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/start-plugin-core",
- "version": "1.171.17",
+ "version": "1.171.18",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/start-server-core/CHANGELOG.md b/packages/start-server-core/CHANGELOG.md
index 5da4e58b2b..2d66cd9533 100644
--- a/packages/start-server-core/CHANGELOG.md
+++ b/packages/start-server-core/CHANGELOG.md
@@ -1,5 +1,11 @@
# @tanstack/start-server-core
+## 1.169.15
+
+### Patch Changes
+
+- [#7599](https://github.com/TanStack/router/pull/7599) [`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43) - Skip a full router.update for faster createStartHandler
+
## 1.169.14
### Patch Changes
diff --git a/packages/start-server-core/package.json b/packages/start-server-core/package.json
index 906c25f2fe..ce25f2ad3e 100644
--- a/packages/start-server-core/package.json
+++ b/packages/start-server-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/start-server-core",
- "version": "1.169.14",
+ "version": "1.169.15",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/vue-start-server/CHANGELOG.md b/packages/vue-start-server/CHANGELOG.md
index 1967ab903c..660c89abcb 100644
--- a/packages/vue-start-server/CHANGELOG.md
+++ b/packages/vue-start-server/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/vue-start-server
+## 1.167.20
+
+### Patch Changes
+
+- Updated dependencies [[`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43)]:
+ - @tanstack/start-server-core@1.169.15
+
## 1.167.19
### Patch Changes
diff --git a/packages/vue-start-server/package.json b/packages/vue-start-server/package.json
index ccc302a05b..524e54b994 100644
--- a/packages/vue-start-server/package.json
+++ b/packages/vue-start-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/vue-start-server",
- "version": "1.167.19",
+ "version": "1.167.20",
"description": "Modern and scalable routing for Vue applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/vue-start/CHANGELOG.md b/packages/vue-start/CHANGELOG.md
index a7eaf13cf9..9ea76e4663 100644
--- a/packages/vue-start/CHANGELOG.md
+++ b/packages/vue-start/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/vue-start
+## 1.168.25
+
+### Patch Changes
+
+- Updated dependencies [[`96eca43`](https://github.com/TanStack/router/commit/96eca43ab8ee21917a8075521bef6dcf15fc7a43), [`e499164`](https://github.com/TanStack/router/commit/e499164c722841271af4aa2215483476bdb94c94)]:
+ - @tanstack/start-server-core@1.169.15
+ - @tanstack/start-plugin-core@1.171.18
+ - @tanstack/vue-start-server@1.167.20
+
## 1.168.24
### Patch Changes
diff --git a/packages/vue-start/package.json b/packages/vue-start/package.json
index c91f957527..5afd0d06a5 100644
--- a/packages/vue-start/package.json
+++ b/packages/vue-start/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/vue-start",
- "version": "1.168.24",
+ "version": "1.168.25",
"description": "Modern and scalable routing for Vue applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a7d0915b77..66e022dc6c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10292,7 +10292,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10329,7 +10329,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10369,7 +10369,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10409,7 +10409,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10443,7 +10443,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10474,7 +10474,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10511,7 +10511,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10554,7 +10554,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10594,7 +10594,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10631,7 +10631,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
compression:
specifier: ^1.8.0
@@ -10671,7 +10671,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10726,7 +10726,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10766,7 +10766,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10803,7 +10803,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10840,7 +10840,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
solid-js:
specifier: 1.9.12
@@ -10871,7 +10871,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10908,7 +10908,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10954,7 +10954,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11000,7 +11000,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11043,7 +11043,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11080,7 +11080,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11114,7 +11114,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11145,7 +11145,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11173,7 +11173,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11204,7 +11204,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11241,7 +11241,7 @@ importers:
specifier: ^4.2.2
version: 4.2.2
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11275,7 +11275,7 @@ importers:
examples/solid/quickstart-webpack-file-based:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11336,7 +11336,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11370,7 +11370,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11410,7 +11410,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11444,7 +11444,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11484,7 +11484,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11530,7 +11530,7 @@ importers:
examples/solid/start-basic:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11582,7 +11582,7 @@ importers:
specifier: ^7.0.0
version: 7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11631,7 +11631,7 @@ importers:
specifier: ^0.41.1
version: 0.41.1
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11671,7 +11671,7 @@ importers:
examples/solid/start-basic-cloudflare:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11711,7 +11711,7 @@ importers:
examples/solid/start-basic-netlify:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11748,7 +11748,7 @@ importers:
examples/solid/start-basic-nitro:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11791,7 +11791,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11834,7 +11834,7 @@ importers:
examples/solid/start-basic-static:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11886,7 +11886,7 @@ importers:
specifier: ^0.7.0
version: 0.7.14(csstype@3.2.3)(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11950,7 +11950,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12005,7 +12005,7 @@ importers:
examples/solid/start-counter:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12048,7 +12048,7 @@ importers:
specifier: ^0.7.0
version: 0.7.14(csstype@3.2.3)(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12088,7 +12088,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12131,7 +12131,7 @@ importers:
examples/solid/start-streaming-data-from-server-functions:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12168,7 +12168,7 @@ importers:
specifier: ^2.48.1
version: 2.48.1
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12205,7 +12205,7 @@ importers:
examples/solid/start-tailwind-v4:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12251,7 +12251,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12285,7 +12285,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12325,7 +12325,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.15
+ specifier: ^1.170.16
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
From ac821f40aa53d387d4b5f9a5fa5e47d9407665bf Mon Sep 17 00:00:00 2001
From: Flo
Date: Thu, 18 Jun 2026 12:49:10 +0200
Subject: [PATCH 07/25] docs(start): clarify server functions vs server routes
(#7651)
* docs(start): clarify server functions and routes usage
* clean up repetition
---
docs/start/framework/react/guide/server-functions.md | 3 +++
docs/start/framework/react/guide/server-routes.md | 3 +++
2 files changed, 6 insertions(+)
diff --git a/docs/start/framework/react/guide/server-functions.md b/docs/start/framework/react/guide/server-functions.md
index d762667bd6..903e5d7b86 100644
--- a/docs/start/framework/react/guide/server-functions.md
+++ b/docs/start/framework/react/guide/server-functions.md
@@ -21,6 +21,9 @@ const time = await getServerTime()
Server functions provide server capabilities (database access, environment variables, file system) while maintaining type safety across the network boundary.
+> [!NOTE]
+> Server functions are meant to be called by your TanStack Start application. They are easy to use from your app code, and Start handles serialization across the client/server boundary. If you need an endpoint that can be called from outside your Start app, use [server routes](./server-routes) instead.
+
## Same-Origin Requests
Server functions are same-origin RPC endpoints for your application. Browser requests to server functions should come from the same origin, verified with Fetch Metadata (`Sec-Fetch-Site`), `Origin`, or `Referer` headers. Use server routes for public APIs or endpoints that intentionally support cross-origin requests.
diff --git a/docs/start/framework/react/guide/server-routes.md b/docs/start/framework/react/guide/server-routes.md
index 542b274147..598f586450 100644
--- a/docs/start/framework/react/guide/server-routes.md
+++ b/docs/start/framework/react/guide/server-routes.md
@@ -24,6 +24,9 @@ export const Route = createFileRoute('/hello')({
})
```
+> [!NOTE]
+> Server routes are meant for HTTP endpoints that need to be called from outside your TanStack Start application. If you only need to call server-side logic from within your Start app and want Start to handle serialization for you, use [server functions](./server-functions) instead.
+
## Server Routes and App Routes
Because server routes can be defined in the same directory as your app routes, you can even use the same file for both!
From 279a8499162c6229665d26826eb7f7dfc5428cb4 Mon Sep 17 00:00:00 2001
From: Flo
Date: Fri, 19 Jun 2026 20:08:09 +0200
Subject: [PATCH 08/25] update better-auth in solid convex example so
@noble/ciphers passes `trustPolicy` (#7656)
---
.../start-convex-better-auth/convex/auth.ts | 3 +-
.../start-convex-better-auth/package.json | 2 +-
pnpm-lock.yaml | 452 +++++++++---------
3 files changed, 234 insertions(+), 223 deletions(-)
diff --git a/examples/solid/start-convex-better-auth/convex/auth.ts b/examples/solid/start-convex-better-auth/convex/auth.ts
index e9c2cdb499..7509818aac 100644
--- a/examples/solid/start-convex-better-auth/convex/auth.ts
+++ b/examples/solid/start-convex-better-auth/convex/auth.ts
@@ -4,6 +4,7 @@ import { betterAuth } from 'better-auth'
import { components } from './_generated/api'
import { query } from './_generated/server'
import type { GenericCtx } from '@convex-dev/better-auth'
+import type { BetterAuthOptions } from 'better-auth'
import type { DataModel } from './_generated/dataModel'
const siteUrl = process.env.SITE_URL!
@@ -16,7 +17,7 @@ export const createAuth = (
ctx: GenericCtx,
{ optionsOnly } = { optionsOnly: false },
) => {
- return betterAuth({
+ return betterAuth({
// disable logging when createAuth is called just to generate options.
// this is not required, but there's a lot of noise in logs without it.
logger: {
diff --git a/examples/solid/start-convex-better-auth/package.json b/examples/solid/start-convex-better-auth/package.json
index 45a4bf91e6..1a787178ed 100644
--- a/examples/solid/start-convex-better-auth/package.json
+++ b/examples/solid/start-convex-better-auth/package.json
@@ -16,7 +16,7 @@
"@tanstack/solid-router": "^1.170.16",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/solid-start": "^1.168.26",
- "better-auth": "^1.3.27",
+ "better-auth": "^1.6.19",
"clsx": "^2.1.1",
"convex": "^1.28.2",
"convex-solidjs": "^0.0.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 66e022dc6c..69c556d118 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11945,7 +11945,7 @@ importers:
dependencies:
'@convex-dev/better-auth':
specifier: ^0.9.7
- version: 0.9.7(@standard-schema/spec@1.1.0)(better-auth@1.3.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vue@3.5.25(typescript@6.0.2)))(convex@1.28.2(@clerk/clerk-react@5.59.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3))(hono@4.7.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2)
+ version: 0.9.7(@standard-schema/spec@1.1.0)(better-auth@1.6.19(@prisma/client@7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2))(@tanstack/solid-start@packages+solid-start)(mysql2@3.15.3)(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vitest@4.1.4)(vue@3.5.25(typescript@6.0.2)))(convex@1.28.2(@clerk/clerk-react@5.59.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3))(hono@4.7.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2)
'@tailwindcss/vite':
specifier: ^4.2.2
version: 4.2.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))
@@ -11959,8 +11959,8 @@ importers:
specifier: workspace:*
version: link:../../../packages/solid-start
better-auth:
- specifier: ^1.3.27
- version: 1.3.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vue@3.5.25(typescript@6.0.2))
+ specifier: ^1.6.19
+ version: 1.6.19(@prisma/client@7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2))(@tanstack/solid-start@packages+solid-start)(mysql2@3.15.3)(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vitest@4.1.4)(vue@3.5.25(typescript@6.0.2))
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -14065,14 +14065,84 @@ packages:
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
- '@better-auth/core@1.3.27':
- resolution: {integrity: sha512-3Sfdax6MQyronY+znx7bOsfQHI6m1SThvJWb0RDscFEAhfqLy95k1sl+/PgGyg0cwc2cUXoEiAOSqYdFYrg3vA==}
+ '@better-auth/core@1.6.19':
+ resolution: {integrity: sha512-ddE3Y9MoQ8t32QSO5Y8mV7pmnDAv5LdJjX1SPWUH6JUUmuOc7YEy3B5JfXZIJbRaXFdnAitN7pHPVa5u/dYAZA==}
+ peerDependencies:
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+ '@cloudflare/workers-types': '>=4'
+ '@opentelemetry/api': ^1.9.0
+ better-call: 1.3.6
+ jose: ^6.1.0
+ kysely: ^0.28.5 || ^0.29.0
+ nanostores: ^1.0.1
+ peerDependenciesMeta:
+ '@cloudflare/workers-types':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
- '@better-auth/utils@0.3.0':
- resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==}
+ '@better-auth/drizzle-adapter@1.6.19':
+ resolution: {integrity: sha512-57C9ePorPmIEez6dHuQMz3hCTkYim0lfVRIoRtX7PiVfiRFB2bjXseQwrCJfQmkgMFlkp1s/c9nKgAjc2EvAIg==}
+ peerDependencies:
+ '@better-auth/core': ^1.6.19
+ '@better-auth/utils': 0.4.2
+ drizzle-orm: ^0.45.2
+ peerDependenciesMeta:
+ drizzle-orm:
+ optional: true
- '@better-fetch/fetch@1.1.18':
- resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
+ '@better-auth/kysely-adapter@1.6.19':
+ resolution: {integrity: sha512-DlmvllEd0nv8JL+plX3JB3WTmqDFnGFOmjmIiUDHo8R3PTAvC0ZaJq3Jk+LQLN5PyVQSUzXZKtvTQYaqRHzBaw==}
+ peerDependencies:
+ '@better-auth/core': ^1.6.19
+ '@better-auth/utils': 0.4.2
+ kysely: ^0.28.17 || ^0.29.0
+ peerDependenciesMeta:
+ kysely:
+ optional: true
+
+ '@better-auth/memory-adapter@1.6.19':
+ resolution: {integrity: sha512-cZ8iLRG/T8Oi/CqE9FTHj3z8pIOqRsINi50trWxPNwyY/Eyb7YCljrBi0PuqgIdyVs7BWfrrtEYTpO4ddfuwEw==}
+ peerDependencies:
+ '@better-auth/core': ^1.6.19
+ '@better-auth/utils': 0.4.2
+
+ '@better-auth/mongo-adapter@1.6.19':
+ resolution: {integrity: sha512-8AReXqhMGiGQIPEpGbmAhh+R4g70TsAVvzwdd6Aj4q+LTSwd3tqC89TFJ4eX8KSplxm9PFBZ6g6gsRmDd7urQg==}
+ peerDependencies:
+ '@better-auth/core': ^1.6.19
+ '@better-auth/utils': 0.4.2
+ mongodb: ^6.0.0 || ^7.0.0
+ peerDependenciesMeta:
+ mongodb:
+ optional: true
+
+ '@better-auth/prisma-adapter@1.6.19':
+ resolution: {integrity: sha512-pXZBhR7/bzJb48IUHlGMyz9SM9h1OCO5GIIuHEllJYt8MKgrjtsnXfUkwZh6pAUEIp3WxBEYMMK96bfkqHiWEg==}
+ peerDependencies:
+ '@better-auth/core': ^1.6.19
+ '@better-auth/utils': 0.4.2
+ '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0
+ prisma: ^5.0.0 || ^6.0.0 || ^7.0.0
+ peerDependenciesMeta:
+ '@prisma/client':
+ optional: true
+ prisma:
+ optional: true
+
+ '@better-auth/telemetry@1.6.19':
+ resolution: {integrity: sha512-bBaB6SMIsrD3WutDdm5YQ1bQyinANTimHD8RtpLWhNh/jIXvzgwVVCrDFqA256vcGC/ZRCydmtW8ZrUqNMW9Og==}
+ peerDependencies:
+ '@better-auth/core': ^1.6.19
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+
+ '@better-auth/utils@0.4.2':
+ resolution: {integrity: sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==}
+
+ '@better-fetch/fetch@1.3.1':
+ resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==}
'@blazediff/core@1.9.1':
resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==}
@@ -15666,9 +15736,6 @@ packages:
engines: {node: '>=6'}
hasBin: true
- '@hexagon/base64@1.1.28':
- resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
-
'@hono/node-server@1.14.2':
resolution: {integrity: sha512-GHjpOeHYbr9d1vkID2sNUYkl5IxumyhDrUJB7wBp7jvqYwPFt+oNKsAPBRcdSbV7kIrXhouLE199ks1QcK4r7A==}
engines: {node: '>=18.14.1'}
@@ -16095,9 +16162,6 @@ packages:
'@leichtgewicht/ip-codec@2.0.5':
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
- '@levischuck/tiny-cbor@0.2.11':
- resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
-
'@libsql/client@0.15.15':
resolution: {integrity: sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==}
@@ -16484,8 +16548,8 @@ packages:
engines: {node: '>=18.14.0'}
hasBin: true
- '@noble/ciphers@2.0.1':
- resolution: {integrity: sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g==}
+ '@noble/ciphers@2.2.0':
+ resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==}
engines: {node: '>= 20.19.0'}
'@noble/hashes@1.4.0':
@@ -16595,6 +16659,10 @@ packages:
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
+ '@opentelemetry/semantic-conventions@1.41.1':
+ resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==}
+ engines: {node: '>=14'}
+
'@oxc-minify/binding-android-arm-eabi@0.110.0':
resolution: {integrity: sha512-43fMTO8/5bMlqfOiNSZNKUzIqeLIYuB9Hr1Ohyf58B1wU11S2dPGibTXOGNaWsfgHy99eeZ1bSgeIHy/fEYqbw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -17060,75 +17128,39 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
- '@peculiar/asn1-android@2.5.0':
- resolution: {integrity: sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A==}
-
- '@peculiar/asn1-cms@2.5.0':
- resolution: {integrity: sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A==}
-
'@peculiar/asn1-cms@2.7.0':
resolution: {integrity: sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==}
- '@peculiar/asn1-csr@2.5.0':
- resolution: {integrity: sha512-ioigvA6WSYN9h/YssMmmoIwgl3RvZlAYx4A/9jD2qaqXZwGcNlAxaw54eSx2QG1Yu7YyBC5Rku3nNoHrQ16YsQ==}
-
'@peculiar/asn1-csr@2.7.0':
resolution: {integrity: sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==}
- '@peculiar/asn1-ecc@2.5.0':
- resolution: {integrity: sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg==}
-
'@peculiar/asn1-ecc@2.7.0':
resolution: {integrity: sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==}
- '@peculiar/asn1-pfx@2.5.0':
- resolution: {integrity: sha512-Vj0d0wxJZA+Ztqfb7W+/iu8Uasw6hhKtCdLKXLG/P3kEPIQpqGI4P4YXlROfl7gOCqFIbgsj1HzFIFwQ5s20ug==}
-
'@peculiar/asn1-pfx@2.7.0':
resolution: {integrity: sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==}
- '@peculiar/asn1-pkcs8@2.5.0':
- resolution: {integrity: sha512-L7599HTI2SLlitlpEP8oAPaJgYssByI4eCwQq2C9eC90otFpm8MRn66PpbKviweAlhinWQ3ZjDD2KIVtx7PaVw==}
-
'@peculiar/asn1-pkcs8@2.7.0':
resolution: {integrity: sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==}
- '@peculiar/asn1-pkcs9@2.5.0':
- resolution: {integrity: sha512-UgqSMBLNLR5TzEZ5ZzxR45Nk6VJrammxd60WMSkofyNzd3DQLSNycGWSK5Xg3UTYbXcDFyG8pA/7/y/ztVCa6A==}
-
'@peculiar/asn1-pkcs9@2.7.0':
resolution: {integrity: sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==}
- '@peculiar/asn1-rsa@2.5.0':
- resolution: {integrity: sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q==}
-
'@peculiar/asn1-rsa@2.7.0':
resolution: {integrity: sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==}
- '@peculiar/asn1-schema@2.5.0':
- resolution: {integrity: sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ==}
-
'@peculiar/asn1-schema@2.7.0':
resolution: {integrity: sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==}
- '@peculiar/asn1-x509-attr@2.5.0':
- resolution: {integrity: sha512-9f0hPOxiJDoG/bfNLAFven+Bd4gwz/VzrCIIWc1025LEI4BXO0U5fOCTNDPbbp2ll+UzqKsZ3g61mpBp74gk9A==}
-
'@peculiar/asn1-x509-attr@2.7.0':
resolution: {integrity: sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==}
- '@peculiar/asn1-x509@2.5.0':
- resolution: {integrity: sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ==}
-
'@peculiar/asn1-x509@2.7.0':
resolution: {integrity: sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==}
'@peculiar/utils@2.0.3':
resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==}
- '@peculiar/x509@1.14.0':
- resolution: {integrity: sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg==}
-
'@peculiar/x509@1.14.3':
resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==}
engines: {node: '>=20.0.0'}
@@ -18809,13 +18841,6 @@ packages:
pinia:
optional: true
- '@simplewebauthn/browser@13.2.2':
- resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
-
- '@simplewebauthn/server@13.2.2':
- resolution: {integrity: sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==}
- engines: {node: '>=20.0.0'}
-
'@sinclair/typebox@0.31.28':
resolution: {integrity: sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ==}
@@ -20629,24 +20654,55 @@ packages:
peerDependencies:
ajv: 4.11.8 - 8
- better-auth@1.3.27:
- resolution: {integrity: sha512-SwiGAJ7yU6dBhNg0NdV1h5M8T5sa7/AszZVc4vBfMDrLLmvUfbt9JoJ0uRUJUEdKRAAxTyl9yA+F3+GhtAD80w==}
+ better-auth@1.6.19:
+ resolution: {integrity: sha512-68eXWKj0sxa0xW4+n4tENd6Co94UCynPKe1fncmO6kIB3XhSXWgwDEpiUouJV2dmLBrHM1FPkoI6Q5597zCGpQ==}
peerDependencies:
'@lynx-js/react': '*'
- '@sveltejs/kit': '*'
- next: '*'
- react: '*'
- react-dom: '*'
- solid-js: '*'
- svelte: '*'
- vue: '*'
+ '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0
+ '@sveltejs/kit': ^2.0.0
+ '@tanstack/react-start': workspace:*
+ '@tanstack/solid-start': workspace:*
+ better-sqlite3: ^12.0.0
+ drizzle-kit: '>=0.31.4'
+ drizzle-orm: ^0.45.2
+ mongodb: ^6.0.0 || ^7.0.0
+ mysql2: ^3.0.0
+ next: ^14.0.0 || ^15.0.0 || ^16.0.0
+ pg: ^8.0.0
+ prisma: ^5.0.0 || ^6.0.0 || ^7.0.0
+ react: ^19.2.3
+ react-dom: ^19.2.3
+ solid-js: 1.9.12
+ svelte: ^4.0.0 || ^5.0.0
+ vitest: ^2.0.0 || ^3.0.0 || ^4.0.0
+ vue: ^3.0.0
peerDependenciesMeta:
'@lynx-js/react':
optional: true
+ '@prisma/client':
+ optional: true
'@sveltejs/kit':
optional: true
+ '@tanstack/react-start':
+ optional: true
+ '@tanstack/solid-start':
+ optional: true
+ better-sqlite3:
+ optional: true
+ drizzle-kit:
+ optional: true
+ drizzle-orm:
+ optional: true
+ mongodb:
+ optional: true
+ mysql2:
+ optional: true
next:
optional: true
+ pg:
+ optional: true
+ prisma:
+ optional: true
react:
optional: true
react-dom:
@@ -20655,11 +20711,18 @@ packages:
optional: true
svelte:
optional: true
+ vitest:
+ optional: true
vue:
optional: true
- better-call@1.0.19:
- resolution: {integrity: sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw==}
+ better-call@1.3.6:
+ resolution: {integrity: sha512-no1jI+h6Bkxs1NVBo4rONbVIzsPjZ8IUu7IHaJBiFwVX1XEQGN8KpHots5fSWmXe9nNyLuLIcgx6WEUcE6EDaA==}
+ peerDependencies:
+ zod: ^4.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
better-path-resolve@1.0.0:
resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
@@ -23284,9 +23347,9 @@ packages:
resolution: {integrity: sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==}
engines: {node: '>=14.0.0'}
- kysely@0.28.8:
- resolution: {integrity: sha512-QUOgl5ZrS9IRuhq5FvOKFSsD/3+IA6MLE81/bOOTRA/YQpKDza2sFdN5g6JCB9BOpqMJDGefLCQ9F12hRS13TA==}
- engines: {node: '>=20.0.0'}
+ kysely@0.29.2:
+ resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==}
+ engines: {node: '>=22.0.0'}
lambda-local@2.2.0:
resolution: {integrity: sha512-bPcgpIXbHnVGfI/omZIlgucDqlf4LrsunwoKue5JdZeGybt8L6KyJz2Zu19ffuZwIwLj2NAI2ZyaqNT6/cetcg==}
@@ -23787,8 +23850,8 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- nanostores@1.0.1:
- resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==}
+ nanostores@1.3.0:
+ resolution: {integrity: sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==}
engines: {node: ^20.0.0 || >=22.0.0}
napi-postinstall@0.3.3:
@@ -24979,9 +25042,6 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
- rou3@0.5.1:
- resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
-
rou3@0.7.12:
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
@@ -25172,6 +25232,9 @@ packages:
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
+ set-cookie-parser@3.1.0:
+ resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==}
+
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -27582,14 +27645,59 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
- '@better-auth/core@1.3.27':
+ '@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0)':
dependencies:
- better-call: 1.0.19
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+ '@opentelemetry/semantic-conventions': 1.41.1
+ '@standard-schema/spec': 1.1.0
+ better-call: 1.3.6(zod@4.4.3)
+ jose: 6.1.3
+ kysely: 0.29.2
+ nanostores: 1.3.0
zod: 4.4.3
- '@better-auth/utils@0.3.0': {}
+ '@better-auth/drizzle-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)':
+ dependencies:
+ '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0)
+ '@better-auth/utils': 0.4.2
- '@better-fetch/fetch@1.1.18': {}
+ '@better-auth/kysely-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(kysely@0.29.2)':
+ dependencies:
+ '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0)
+ '@better-auth/utils': 0.4.2
+ optionalDependencies:
+ kysely: 0.29.2
+
+ '@better-auth/memory-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)':
+ dependencies:
+ '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0)
+ '@better-auth/utils': 0.4.2
+
+ '@better-auth/mongo-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)':
+ dependencies:
+ '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0)
+ '@better-auth/utils': 0.4.2
+
+ '@better-auth/prisma-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@prisma/client@7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2))(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))':
+ dependencies:
+ '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0)
+ '@better-auth/utils': 0.4.2
+ optionalDependencies:
+ '@prisma/client': 7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2)
+ prisma: 7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2)
+
+ '@better-auth/telemetry@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)':
+ dependencies:
+ '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0)
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+
+ '@better-auth/utils@0.4.2':
+ dependencies:
+ '@noble/hashes': 2.0.1
+
+ '@better-fetch/fetch@1.3.1': {}
'@blazediff/core@1.9.1': {}
@@ -27942,9 +28050,9 @@ snapshots:
'@colors/colors@1.6.0': {}
- '@convex-dev/better-auth@0.9.7(@standard-schema/spec@1.1.0)(better-auth@1.3.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vue@3.5.25(typescript@6.0.2)))(convex@1.28.2(@clerk/clerk-react@5.59.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3))(hono@4.7.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2)':
+ '@convex-dev/better-auth@0.9.7(@standard-schema/spec@1.1.0)(better-auth@1.6.19(@prisma/client@7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2))(@tanstack/solid-start@packages+solid-start)(mysql2@3.15.3)(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vitest@4.1.4)(vue@3.5.25(typescript@6.0.2)))(convex@1.28.2(@clerk/clerk-react@5.59.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3))(hono@4.7.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2)':
dependencies:
- better-auth: 1.3.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vue@3.5.25(typescript@6.0.2))
+ better-auth: 1.6.19(@prisma/client@7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2))(@tanstack/solid-start@packages+solid-start)(mysql2@3.15.3)(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vitest@4.1.4)(vue@3.5.25(typescript@6.0.2))
common-tags: 1.8.2
convex: 1.28.2(@clerk/clerk-react@5.59.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
convex-helpers: 0.1.104(@standard-schema/spec@1.1.0)(convex@1.28.2(@clerk/clerk-react@5.59.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3))(hono@4.7.10)(react@19.2.3)(typescript@6.0.2)(zod@3.25.76)
@@ -29103,8 +29211,6 @@ snapshots:
protobufjs: 7.4.0
yargs: 17.7.2
- '@hexagon/base64@1.1.28': {}
-
'@hono/node-server@1.14.2(hono@4.7.10)':
dependencies:
hono: 4.7.10
@@ -29432,8 +29538,6 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.5': {}
- '@levischuck/tiny-cbor@0.2.11': {}
-
'@libsql/client@0.15.15':
dependencies:
'@libsql/core': 0.15.15
@@ -30119,7 +30223,7 @@ snapshots:
- rollup
- supports-color
- '@noble/ciphers@2.0.1': {}
+ '@noble/ciphers@2.2.0': {}
'@noble/hashes@1.4.0': {}
@@ -30206,6 +30310,8 @@ snapshots:
'@open-draft/until@2.1.0': {}
+ '@opentelemetry/semantic-conventions@1.41.1': {}
+
'@oxc-minify/binding-android-arm-eabi@0.110.0':
optional: true
@@ -30472,20 +30578,6 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
- '@peculiar/asn1-android@2.5.0':
- dependencies:
- '@peculiar/asn1-schema': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
- '@peculiar/asn1-cms@2.5.0':
- dependencies:
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- '@peculiar/asn1-x509-attr': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
'@peculiar/asn1-cms@2.7.0':
dependencies:
'@peculiar/asn1-schema': 2.7.0
@@ -30494,13 +30586,6 @@ snapshots:
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-csr@2.5.0':
- dependencies:
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
'@peculiar/asn1-csr@2.7.0':
dependencies:
'@peculiar/asn1-schema': 2.7.0
@@ -30508,13 +30593,6 @@ snapshots:
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-ecc@2.5.0':
- dependencies:
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
'@peculiar/asn1-ecc@2.7.0':
dependencies:
'@peculiar/asn1-schema': 2.7.0
@@ -30522,15 +30600,6 @@ snapshots:
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-pfx@2.5.0':
- dependencies:
- '@peculiar/asn1-cms': 2.5.0
- '@peculiar/asn1-pkcs8': 2.5.0
- '@peculiar/asn1-rsa': 2.5.0
- '@peculiar/asn1-schema': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
'@peculiar/asn1-pfx@2.7.0':
dependencies:
'@peculiar/asn1-cms': 2.7.0
@@ -30540,13 +30609,6 @@ snapshots:
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-pkcs8@2.5.0':
- dependencies:
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
'@peculiar/asn1-pkcs8@2.7.0':
dependencies:
'@peculiar/asn1-schema': 2.7.0
@@ -30554,17 +30616,6 @@ snapshots:
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-pkcs9@2.5.0':
- dependencies:
- '@peculiar/asn1-cms': 2.5.0
- '@peculiar/asn1-pfx': 2.5.0
- '@peculiar/asn1-pkcs8': 2.5.0
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- '@peculiar/asn1-x509-attr': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
'@peculiar/asn1-pkcs9@2.7.0':
dependencies:
'@peculiar/asn1-cms': 2.7.0
@@ -30576,13 +30627,6 @@ snapshots:
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-rsa@2.5.0':
- dependencies:
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
'@peculiar/asn1-rsa@2.7.0':
dependencies:
'@peculiar/asn1-schema': 2.7.0
@@ -30590,25 +30634,12 @@ snapshots:
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-schema@2.5.0':
- dependencies:
- asn1js: 3.0.6
- pvtsutils: 1.3.6
- tslib: 2.8.1
-
'@peculiar/asn1-schema@2.7.0':
dependencies:
'@peculiar/utils': 2.0.3
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-x509-attr@2.5.0':
- dependencies:
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- asn1js: 3.0.6
- tslib: 2.8.1
-
'@peculiar/asn1-x509-attr@2.7.0':
dependencies:
'@peculiar/asn1-schema': 2.7.0
@@ -30616,13 +30647,6 @@ snapshots:
asn1js: 3.0.6
tslib: 2.8.1
- '@peculiar/asn1-x509@2.5.0':
- dependencies:
- '@peculiar/asn1-schema': 2.5.0
- asn1js: 3.0.6
- pvtsutils: 1.3.6
- tslib: 2.8.1
-
'@peculiar/asn1-x509@2.7.0':
dependencies:
'@peculiar/asn1-schema': 2.7.0
@@ -30634,20 +30658,6 @@ snapshots:
dependencies:
tslib: 2.8.1
- '@peculiar/x509@1.14.0':
- dependencies:
- '@peculiar/asn1-cms': 2.5.0
- '@peculiar/asn1-csr': 2.5.0
- '@peculiar/asn1-ecc': 2.5.0
- '@peculiar/asn1-pkcs9': 2.5.0
- '@peculiar/asn1-rsa': 2.5.0
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- pvtsutils: 1.3.6
- reflect-metadata: 0.2.2
- tslib: 2.8.1
- tsyringe: 4.10.0
-
'@peculiar/x509@1.14.3':
dependencies:
'@peculiar/asn1-cms': 2.7.0
@@ -32240,19 +32250,6 @@ snapshots:
optionalDependencies:
'@tanstack/vue-router': link:packages/vue-router
- '@simplewebauthn/browser@13.2.2': {}
-
- '@simplewebauthn/server@13.2.2':
- dependencies:
- '@hexagon/base64': 1.1.28
- '@levischuck/tiny-cbor': 0.2.11
- '@peculiar/asn1-android': 2.5.0
- '@peculiar/asn1-ecc': 2.5.0
- '@peculiar/asn1-rsa': 2.5.0
- '@peculiar/asn1-schema': 2.5.0
- '@peculiar/asn1-x509': 2.5.0
- '@peculiar/x509': 1.14.0
-
'@sinclair/typebox@0.31.28': {}
'@sindresorhus/is@4.6.0': {}
@@ -34738,34 +34735,47 @@ snapshots:
jsonpointer: 5.0.1
leven: 3.1.0
- better-auth@1.3.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vue@3.5.25(typescript@6.0.2)):
- dependencies:
- '@better-auth/core': 1.3.27
- '@better-auth/utils': 0.3.0
- '@better-fetch/fetch': 1.1.18
- '@noble/ciphers': 2.0.1
+ better-auth@1.6.19(@prisma/client@7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2))(@tanstack/solid-start@packages+solid-start)(mysql2@3.15.3)(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12)(vitest@4.1.4)(vue@3.5.25(typescript@6.0.2)):
+ dependencies:
+ '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0)
+ '@better-auth/drizzle-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)
+ '@better-auth/kysely-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(kysely@0.29.2)
+ '@better-auth/memory-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)
+ '@better-auth/mongo-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)
+ '@better-auth/prisma-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@prisma/client@7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2))(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))
+ '@better-auth/telemetry': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+ '@noble/ciphers': 2.2.0
'@noble/hashes': 2.0.1
- '@simplewebauthn/browser': 13.2.2
- '@simplewebauthn/server': 13.2.2
- better-call: 1.0.19
+ better-call: 1.3.6(zod@4.4.3)
defu: 6.1.4
- jose: 6.1.0
- kysely: 0.28.8
- nanostores: 1.0.1
+ jose: 6.1.3
+ kysely: 0.29.2
+ nanostores: 1.3.0
zod: 4.4.3
optionalDependencies:
+ '@prisma/client': 7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2)
+ '@tanstack/solid-start': link:packages/solid-start
+ mysql2: 3.15.3
+ prisma: 7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
solid-js: 1.9.12
+ 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))
vue: 3.5.25(typescript@6.0.2)
+ transitivePeerDependencies:
+ - '@cloudflare/workers-types'
+ - '@opentelemetry/api'
- better-call@1.0.19:
+ better-call@1.3.6(zod@4.4.3):
dependencies:
- '@better-auth/utils': 0.3.0
- '@better-fetch/fetch': 1.1.18
- rou3: 0.5.1
- set-cookie-parser: 2.7.2
- uncrypto: 0.1.3
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+ rou3: 0.7.12
+ set-cookie-parser: 3.1.0
+ optionalDependencies:
+ zod: 4.4.3
better-path-resolve@1.0.0:
dependencies:
@@ -37716,7 +37726,7 @@ snapshots:
kysely@0.27.6: {}
- kysely@0.28.8: {}
+ kysely@0.29.2: {}
lambda-local@2.2.0:
dependencies:
@@ -38222,7 +38232,7 @@ snapshots:
nanoid@3.3.12: {}
- nanostores@1.0.1: {}
+ nanostores@1.3.0: {}
napi-postinstall@0.3.3: {}
@@ -39846,8 +39856,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.56.0
fsevents: 2.3.3
- rou3@0.5.1: {}
-
rou3@0.7.12: {}
rou3@0.8.1: {}
@@ -40061,6 +40069,8 @@ snapshots:
set-cookie-parser@2.7.2: {}
+ set-cookie-parser@3.1.0: {}
+
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
From ba52d2b8f9e95d6becd1be14c020b35f3231e02c Mon Sep 17 00:00:00 2001
From: Yagiz Nizipli
Date: Mon, 22 Jun 2026 18:15:37 -0400
Subject: [PATCH 09/25] perf(start-client-core): zero-copy frame payload
extraction (#7662)
extractFlattened() always allocated a new Uint8Array and copied `count`
bytes, even when the requested bytes were fully contained in the first
buffered chunk (the common case, since most frames arrive within a single
network read).
Add a fast path that returns a subarray view of the first chunk when the
bytes are contiguous, avoiding the allocation and the byte copy. The
multi-chunk path is unchanged. The returned view shares the chunk's
backing ArrayBuffer, which is safe because buffered chunks are never
mutated in place after being read.
This is on the hot path for decoding every streamed server-function
response and RawStream binary payload on the client. A micro-benchmark
shows ~3x faster extraction for 1KB frames and ~27x for 64KB frames (the
win scales with payload size).
---
.changeset/perf-frame-decoder-zero-copy.md | 5 ++
.../src/client-rpc/frame-decoder.ts | 19 ++++++++
.../tests/frame-decoder.test.ts | 46 +++++++++++++++++++
3 files changed, 70 insertions(+)
create mode 100644 .changeset/perf-frame-decoder-zero-copy.md
diff --git a/.changeset/perf-frame-decoder-zero-copy.md b/.changeset/perf-frame-decoder-zero-copy.md
new file mode 100644
index 0000000000..182c81b66a
--- /dev/null
+++ b/.changeset/perf-frame-decoder-zero-copy.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/start-client-core': patch
+---
+
+perf: zero-copy frame payload extraction in the client frame decoder. When a frame's bytes are fully contained in the first buffered chunk (the common case), return a subarray view instead of allocating a new buffer and copying. This is on the hot path for decoding streamed server-function responses and `RawStream` payloads.
diff --git a/packages/start-client-core/src/client-rpc/frame-decoder.ts b/packages/start-client-core/src/client-rpc/frame-decoder.ts
index dbdd605bf2..9834b10a1b 100644
--- a/packages/start-client-core/src/client-rpc/frame-decoder.ts
+++ b/packages/start-client-core/src/client-rpc/frame-decoder.ts
@@ -201,6 +201,25 @@ export function createFrameDecoder(
function extractFlattened(count: number): Uint8Array {
if (count === 0) return EMPTY_BUFFER
+ // Fast path: the requested bytes are fully contained in the first buffered
+ // chunk (the common case — most frames arrive within a single network
+ // read). Return a subarray view instead of allocating a new buffer and
+ // copying `count` bytes. The view shares the chunk's backing ArrayBuffer,
+ // which is safe because buffered chunks are never mutated in place after
+ // being read from the network.
+ const first = bufferList[0]
+ if (first && first.length >= count) {
+ const result = first.subarray(0, count)
+ if (first.length === count) {
+ bufferList.shift()
+ } else {
+ bufferList[0] = first.subarray(count)
+ }
+ totalLength -= count
+ return result
+ }
+
+ // Slow path: the requested bytes span multiple chunks — flatten by copying.
const result = new Uint8Array(count)
let offset = 0
let remaining = count
diff --git a/packages/start-client-core/tests/frame-decoder.test.ts b/packages/start-client-core/tests/frame-decoder.test.ts
index 29b8974dea..3a05611392 100644
--- a/packages/start-client-core/tests/frame-decoder.test.ts
+++ b/packages/start-client-core/tests/frame-decoder.test.ts
@@ -558,5 +558,51 @@ describe('frame-decoder', () => {
const { done: finalDone } = await reader.read()
expect(finalDone).toBe(true)
})
+
+ it('should reassemble a chunk payload that spans many small reads', async () => {
+ // A large binary payload delivered in tiny network reads forces the
+ // multi-chunk (copy) path; the contiguous fast path must not change the
+ // reassembled bytes.
+ const payload = new Uint8Array(300)
+ for (let i = 0; i < payload.length; i++) payload[i] = i % 256
+
+ const jsonFrame = encodeJSONFrame('{"ref":11}')
+ const chunkFrame = encodeChunkFrame(11, payload)
+ const endFrame = encodeEndFrame(11)
+
+ const combined = new Uint8Array(
+ jsonFrame.length + chunkFrame.length + endFrame.length,
+ )
+ combined.set(jsonFrame, 0)
+ combined.set(chunkFrame, jsonFrame.length)
+ combined.set(endFrame, jsonFrame.length + chunkFrame.length)
+
+ const input = new ReadableStream({
+ start(controller) {
+ // 7-byte reads: smaller than the 9-byte header and the payload, so
+ // both header and payload span multiple buffered chunks.
+ for (let i = 0; i < combined.length; i += 7) {
+ controller.enqueue(combined.subarray(i, i + 7))
+ }
+ controller.close()
+ },
+ })
+
+ const { getOrCreateStream, jsonChunks } = createFrameDecoder(input)
+ const stream11 = getOrCreateStream(11)
+
+ const jsonReader = jsonChunks.getReader()
+ const { value: jsonValue } = await jsonReader.read()
+ expect(jsonValue).toBe('{"ref":11}')
+
+ const rawReader = stream11.getReader()
+ const received: Array = []
+ while (true) {
+ const { done, value } = await rawReader.read()
+ if (done) break
+ if (value) received.push(...value)
+ }
+ expect(received).toEqual(Array.from(payload))
+ })
})
})
From 7538668f2123ba2d50324e61276a2eda64ae0856 Mon Sep 17 00:00:00 2001
From: Flo
Date: Tue, 23 Jun 2026 01:19:54 +0200
Subject: [PATCH 10/25] ci: bundle-size workdlow outputs its report to stdout
(#7677)
---
.github/workflows/bundle-size.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml
index 866db1399b..d0deda36a7 100644
--- a/.github/workflows/bundle-size.yml
+++ b/.github/workflows/bundle-size.yml
@@ -98,6 +98,7 @@ jobs:
--output benchmarks/bundle-size/results/pr-comment.md \
--base-sha "$BASE_SHA" \
--dashboard-url "https://${REPOSITORY_OWNER}.github.io/${REPOSITORY_NAME}/benchmarks/bundle-size/"
+ cat benchmarks/bundle-size/results/pr-comment.md
- name: Upsert Sticky PR Comment
env:
From d71af0bbcfd08aa9e199b52d544923fcb2e41ed9 Mon Sep 17 00:00:00 2001
From: Flo
Date: Tue, 23 Jun 2026 21:58:18 +0200
Subject: [PATCH 11/25] add undici 6 to trustPolicyExclude for vite ecosystem
ci (#7680)
---
pnpm-workspace.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index fb3c02cdcc..14be68782f 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -7,6 +7,7 @@ trustPolicy: 'no-downgrade'
trustPolicyExclude:
- 'chokidar@4.0.3' # known to be safe, installed by sass, prisma, netlify... no planned releases in v4, packages is on v5 now
- 'semver@6.3.1' # installed by babel, ecosystem has been on v7 for 7 years now, but babel is horribly out of date, 6.3.1 is the latest v6 and was released 3 years ago
+ - 'undici@6.21.2' # installed by cheerio@1.0.0 -> html-link-extractor@1.0.5 -> markdown-link-extractor@4.0.3, is only 1 year old, v6, v7 and v8 are maintained in parallel
packages:
- 'packages/*'
From 80d098f5a4b292f44e26286acc6220abfb5a931d Mon Sep 17 00:00:00 2001
From: Flo
Date: Tue, 23 Jun 2026 22:48:11 +0200
Subject: [PATCH 12/25] fix pnpm trustPolicy (for good) (#7681)
---
package.json | 30 +++++++++++++-------------
pnpm-lock.yaml | 51 +++++++++++++++++++--------------------------
pnpm-workspace.yaml | 42 ++++++++++++++++++++++++++-----------
3 files changed, 67 insertions(+), 56 deletions(-)
diff --git a/package.json b/package.json
index f544549330..f901a4be58 100644
--- a/package.json
+++ b/package.json
@@ -5,9 +5,9 @@
"type": "git",
"url": "git+https://github.com/TanStack/router.git"
},
- "packageManager": "pnpm@11.1.0",
+ "packageManager": "pnpm@11.9.0",
"engines": {
- "pnpm": ">=11.0.0"
+ "pnpm": ">=11.9.0"
},
"type": "module",
"scripts": {
@@ -61,26 +61,26 @@
]
},
"devDependencies": {
- "@arethetypeswrong/cli": "^0.18.2",
+ "@arethetypeswrong/cli": "^0.18.4",
"@changesets/changelog-github": "^0.7.0",
"@changesets/cli": "^2.30.0",
"@eslint-react/eslint-plugin": "^1.26.2",
"@nx/devkit": "22.7.5",
- "@playwright/test": "^1.57.0",
+ "@playwright/test": "catalog:",
"@swc-node/core": "^1.14.1",
"@swc-node/register": "^1.11.1",
"@tanstack/eslint-config": "0.4.0",
- "@tanstack/query-core": "^5.99.0",
- "@tanstack/react-query": "^5.99.0",
- "@tanstack/solid-query": "^5.99.0",
+ "@tanstack/query-core": "catalog:",
+ "@tanstack/react-query": "catalog:",
+ "@tanstack/solid-query": "catalog:",
"@tanstack/vite-config": "0.5.2",
- "@tanstack/vue-query": "^5.99.0",
- "@types/node": "25.0.9",
- "@types/react": "^19.2.8",
- "@types/react-dom": "^19.2.3",
+ "@tanstack/vue-query": "catalog:",
+ "@types/node": "catalog:",
+ "@types/react": "catalog:",
+ "@types/react-dom": "catalog:",
"@vitest/browser": "^4.1.4",
"@vitest/ui": "^4.1.4",
- "eslint": "^9.22.0",
+ "eslint": "catalog:",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-unused-imports": "^4.1.4",
"jsdom": "^25.0.1",
@@ -88,8 +88,8 @@
"nx": "22.7.5",
"prettier": "^3.8.0",
"publint": "^0.3.16",
- "react": "^19.2.3",
- "react-dom": "^19.2.3",
+ "react": "catalog:",
+ "react-dom": "catalog:",
"redaxios": "^0.5.1",
"rimraf": "^6.1.2",
"tinyglobby": "^0.2.15",
@@ -99,7 +99,7 @@
"typescript57": "npm:typescript@5.7",
"typescript58": "npm:typescript@5.8",
"typescript59": "npm:typescript@5.9",
- "vite": "^8.0.14",
+ "vite": "catalog:",
"vitest": "^4.1.4"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 69c556d118..7a7c3a7348 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -65,8 +65,8 @@ importers:
.:
devDependencies:
'@arethetypeswrong/cli':
- specifier: ^0.18.2
- version: 0.18.2
+ specifier: ^0.18.4
+ version: 0.18.4
'@changesets/changelog-github':
specifier: ^0.7.0
version: 0.7.0
@@ -13707,13 +13707,13 @@ packages:
'@andrewbranch/untar.js@1.0.3':
resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==}
- '@arethetypeswrong/cli@0.18.2':
- resolution: {integrity: sha512-PcFM20JNlevEDKBg4Re29Rtv2xvjvQZzg7ENnrWFSS0PHgdP2njibVFw+dRUhNkPgNfac9iUqO0ohAXqQL4hbw==}
+ '@arethetypeswrong/cli@0.18.4':
+ resolution: {integrity: sha512-kNWo6LTzGAuLYPpJ7Sgo63whSUeeSuKMlYx6IBgzs4ONEG807gW4hSSENvpeCHzO2H2wIzG5EFl0OKBbqGBAyA==}
engines: {node: '>=20'}
hasBin: true
- '@arethetypeswrong/core@0.18.2':
- resolution: {integrity: sha512-GiwTmBFOU1/+UVNqqCGzFJYfBXEytUkiI+iRZ6Qx7KmUVtLm00sYySkfe203C9QtPG11yOz1ZaMek8dT/xnlgg==}
+ '@arethetypeswrong/core@0.18.4':
+ resolution: {integrity: sha512-M5F0ePyN6h2Z6XxRiyIPqjGbltotXLjR0CKA0uKspsDu0QmgTNYvRb4RSQPMUs2ZXZHCCYpbaZbFbYOXLxCjUA==}
engines: {node: '>=20'}
'@ark/schema@0.44.2':
@@ -20432,10 +20432,6 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
- ansi-regex@6.1.0:
- resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
- engines: {node: '>=12'}
-
ansi-regex@6.2.2:
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
@@ -20877,10 +20873,6 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
- chalk@5.4.1:
- resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
- engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
-
chalk@5.6.2:
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
@@ -22271,6 +22263,9 @@ packages:
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+ fflate@0.8.3:
+ resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==}
+
figures@3.2.0:
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
engines: {node: '>=8'}
@@ -27046,24 +27041,24 @@ snapshots:
'@andrewbranch/untar.js@1.0.3': {}
- '@arethetypeswrong/cli@0.18.2':
+ '@arethetypeswrong/cli@0.18.4':
dependencies:
- '@arethetypeswrong/core': 0.18.2
+ '@arethetypeswrong/core': 0.18.4
chalk: 4.1.2
cli-table3: 0.6.5
commander: 10.0.1
marked: 9.1.6
marked-terminal: 7.3.0(marked@9.1.6)
- semver: 7.7.3
+ semver: 7.8.2
- '@arethetypeswrong/core@0.18.2':
+ '@arethetypeswrong/core@0.18.4':
dependencies:
'@andrewbranch/untar.js': 1.0.3
'@loaderkit/resolve': 1.0.4
cjs-module-lexer: 1.4.3
- fflate: 0.8.2
- lru-cache: 11.2.2
- semver: 7.7.3
+ fflate: 0.8.3
+ lru-cache: 11.5.1
+ semver: 7.8.2
typescript: 5.6.1-rc
validate-npm-package-name: 5.0.1
@@ -29689,7 +29684,7 @@ snapshots:
https-proxy-agent: 7.0.6
node-fetch: 2.7.0
nopt: 8.1.0
- semver: 7.7.3
+ semver: 7.8.2
tar: 7.4.3
transitivePeerDependencies:
- encoding
@@ -33448,7 +33443,7 @@ snapshots:
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.9
- semver: 7.7.2
+ semver: 7.8.2
ts-api-utils: 2.4.0(typescript@6.0.2)
typescript: 6.0.2
transitivePeerDependencies:
@@ -34467,8 +34462,6 @@ snapshots:
ansi-regex@5.0.1: {}
- ansi-regex@6.1.0: {}
-
ansi-regex@6.2.2: {}
ansi-styles@4.3.0:
@@ -34976,8 +34969,6 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
- chalk@5.4.1: {}
-
chalk@5.6.2: {}
char-regex@1.0.2: {}
@@ -36593,6 +36584,8 @@ snapshots:
fflate@0.8.2: {}
+ fflate@0.8.3: {}
+
figures@3.2.0:
dependencies:
escape-string-regexp: 1.0.5
@@ -37991,8 +37984,8 @@ snapshots:
marked-terminal@7.3.0(marked@9.1.6):
dependencies:
ansi-escapes: 7.0.0
- ansi-regex: 6.1.0
- chalk: 5.4.1
+ ansi-regex: 6.2.2
+ chalk: 5.6.2
cli-highlight: 2.1.11
cli-table3: 0.6.5
marked: 9.1.6
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 14be68782f..22d1b42ad5 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,4 +1,5 @@
cleanupUnusedCatalogs: true
+minimumReleaseAge: 1
linkWorkspacePackages: true
preferWorkspacePackages: true
blockExoticSubdeps: true
@@ -8,6 +9,9 @@ trustPolicyExclude:
- 'chokidar@4.0.3' # known to be safe, installed by sass, prisma, netlify... no planned releases in v4, packages is on v5 now
- 'semver@6.3.1' # installed by babel, ecosystem has been on v7 for 7 years now, but babel is horribly out of date, 6.3.1 is the latest v6 and was released 3 years ago
- 'undici@6.21.2' # installed by cheerio@1.0.0 -> html-link-extractor@1.0.5 -> markdown-link-extractor@4.0.3, is only 1 year old, v6, v7 and v8 are maintained in parallel
+ - '@netlify/edge-bundler@14.7.0' # existing Netlify dev transitive flagged by pnpm 11.9 trust downgrade checks
+ - '@netlify/serverless-functions-api@2.7.1' # existing Netlify dev transitive flagged by pnpm 11.9 trust downgrade checks
+ - '@netlify/zip-it-and-ship-it@14.1.11' # existing Netlify dev transitive flagged by pnpm 11.9 trust downgrade checks
packages:
- 'packages/*'
@@ -28,22 +32,36 @@ packages:
- 'e2e/vue-start/*'
- 'e2e/eslint-plugin-start'
+catalog:
+ react: ^19.2.3
+ react-dom: ^19.2.3
+ '@types/react': ^19.2.8
+ '@types/react-dom': ^19.2.3
+ eslint: ^9.22.0
+ vite: ^8.0.14
+ '@types/node': 25.0.9
+ '@playwright/test': ^1.57.0
+ '@tanstack/query-core': ^5.99.0
+ '@tanstack/react-query': ^5.99.0
+ '@tanstack/solid-query': ^5.99.0
+ '@tanstack/vue-query': ^5.99.0
+
overrides:
'@types/babel__traverse': '^7.28.0'
vite-plugin-dts: 4.2.3
solid-js: 1.9.12
- react: $react
- react-dom: $react-dom
- '@types/react': $@types/react
- '@types/react-dom': $@types/react-dom
- eslint: $eslint
- vite: $vite
- '@types/node': $@types/node
- '@playwright/test': $@playwright/test
- '@tanstack/query-core': $@tanstack/query-core
- '@tanstack/react-query': $@tanstack/react-query
- '@tanstack/solid-query': $@tanstack/solid-query
- '@tanstack/vue-query': $@tanstack/vue-query
+ react: 'catalog:'
+ react-dom: 'catalog:'
+ '@types/react': 'catalog:'
+ '@types/react-dom': 'catalog:'
+ eslint: 'catalog:'
+ vite: 'catalog:'
+ '@types/node': 'catalog:'
+ '@playwright/test': 'catalog:'
+ '@tanstack/query-core': 'catalog:'
+ '@tanstack/react-query': 'catalog:'
+ '@tanstack/solid-query': 'catalog:'
+ '@tanstack/vue-query': 'catalog:'
'@tanstack/history': 'workspace:*'
'@tanstack/router-core': 'workspace:*'
'@tanstack/react-router': 'workspace:*'
From 3d86aee2640c58a045d0946e717e282b66608e1d Mon Sep 17 00:00:00 2001
From: Flo
Date: Wed, 24 Jun 2026 13:48:04 +0200
Subject: [PATCH 13/25] pnpm: fix minReleaseAge to 1 day (#7686)
---
pnpm-workspace.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 22d1b42ad5..de56e54dd7 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,5 +1,5 @@
cleanupUnusedCatalogs: true
-minimumReleaseAge: 1
+minimumReleaseAge: 1440
linkWorkspacePackages: true
preferWorkspacePackages: true
blockExoticSubdeps: true
From bb2daa6952f3689684b0cb6e4bd5f726fcbf8f42 Mon Sep 17 00:00:00 2001
From: Flo
Date: Thu, 25 Jun 2026 23:29:10 +0200
Subject: [PATCH 14/25] Reduce postinstall wrangler noise (#7696)
---
e2e/react-start/basic-cloudflare/package.json | 2 +-
e2e/solid-start/basic-cloudflare/package.json | 2 +-
e2e/vue-start/basic-cloudflare/package.json | 2 +-
examples/react/start-basic-cloudflare/package.json | 2 +-
examples/solid/start-basic-cloudflare/package.json | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/e2e/react-start/basic-cloudflare/package.json b/e2e/react-start/basic-cloudflare/package.json
index 291dc8fcf2..4d90b4124e 100644
--- a/e2e/react-start/basic-cloudflare/package.json
+++ b/e2e/react-start/basic-cloudflare/package.json
@@ -9,7 +9,7 @@
"build": "vite build && tsc --noEmit",
"preview": "vite preview",
"cf-typegen": "wrangler types",
- "postinstall": "npm run cf-typegen",
+ "postinstall": "WRANGLER_LOG=warn npm run --silent cf-typegen",
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
},
"dependencies": {
diff --git a/e2e/solid-start/basic-cloudflare/package.json b/e2e/solid-start/basic-cloudflare/package.json
index cdd58f76c1..9026db1619 100644
--- a/e2e/solid-start/basic-cloudflare/package.json
+++ b/e2e/solid-start/basic-cloudflare/package.json
@@ -9,7 +9,7 @@
"build": "vite build && tsc --noEmit",
"preview": "vite preview",
"cf-typegen": "wrangler types",
- "postinstall": "npm run cf-typegen",
+ "postinstall": "WRANGLER_LOG=warn npm run --silent cf-typegen",
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
},
"dependencies": {
diff --git a/e2e/vue-start/basic-cloudflare/package.json b/e2e/vue-start/basic-cloudflare/package.json
index b3c4273718..c7e55f7937 100644
--- a/e2e/vue-start/basic-cloudflare/package.json
+++ b/e2e/vue-start/basic-cloudflare/package.json
@@ -9,7 +9,7 @@
"build": "vite build && tsc --noEmit",
"preview": "vite preview",
"cf-typegen": "wrangler types",
- "postinstall": "npm run cf-typegen",
+ "postinstall": "WRANGLER_LOG=warn npm run --silent cf-typegen",
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
},
"dependencies": {
diff --git a/examples/react/start-basic-cloudflare/package.json b/examples/react/start-basic-cloudflare/package.json
index 9c35d7ff6d..b512fe867d 100644
--- a/examples/react/start-basic-cloudflare/package.json
+++ b/examples/react/start-basic-cloudflare/package.json
@@ -9,7 +9,7 @@
"preview": "vite preview",
"deploy": "npm run build && wrangler deploy",
"cf-typegen": "wrangler types",
- "postinstall": "npm run cf-typegen"
+ "postinstall": "WRANGLER_LOG=warn npm run --silent cf-typegen"
},
"dependencies": {
"@tanstack/react-router": "^1.170.16",
diff --git a/examples/solid/start-basic-cloudflare/package.json b/examples/solid/start-basic-cloudflare/package.json
index 12b1f61b24..b186dd9d6e 100644
--- a/examples/solid/start-basic-cloudflare/package.json
+++ b/examples/solid/start-basic-cloudflare/package.json
@@ -9,7 +9,7 @@
"preview": "vite preview",
"deploy": "npm run build && wrangler deploy",
"cf-typegen": "wrangler types",
- "postinstall": "npm run cf-typegen"
+ "postinstall": "WRANGLER_LOG=warn npm run --silent cf-typegen"
},
"dependencies": {
"@tanstack/solid-router": "^1.170.16",
From 9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b Mon Sep 17 00:00:00 2001
From: Collin Dillinger
Date: Tue, 30 Jun 2026 17:27:21 -0400
Subject: [PATCH 15/25] fix(router-core): preserve percent-encoded URL-unsafe
chars in decodeSegment (#7695)
* fix(router-core): preserve percent-encoded URL-unsafe chars in decodeSegment
Replace sanitizePathSegment (which stripped control characters) with a
re-encode step that keeps WHATWG path percent-encode set characters and
control characters in their encoded form after decodeURI.
This preserves the existing decodeURI-based approach which correctly
handles multi-byte UTF-8 sequences, while fixing the mismatch between
the original request URL and the router's internal representation that
caused infinite 307 redirect loops on paths containing these characters.
Fixes #7587.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* ci: apply automated fixes
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Flo
---
.changeset/fix-decode-path-preserve-unsafe.md | 9 +++
.../tests/open-redirect-prevention.spec.ts | 7 +-
.../tests/open-redirect-prevention.spec.ts | 12 +---
.../basic/tests/special-characters.spec.ts | 16 +++++
packages/router-core/src/utils.ts | 30 ++++++---
packages/router-core/tests/utils.test.ts | 67 +++++++++++++------
6 files changed, 96 insertions(+), 45 deletions(-)
create mode 100644 .changeset/fix-decode-path-preserve-unsafe.md
diff --git a/.changeset/fix-decode-path-preserve-unsafe.md b/.changeset/fix-decode-path-preserve-unsafe.md
new file mode 100644
index 0000000000..a68ae9e3e1
--- /dev/null
+++ b/.changeset/fix-decode-path-preserve-unsafe.md
@@ -0,0 +1,9 @@
+---
+'@tanstack/router-core': patch
+---
+
+fix(router-core): re-encode URL-unsafe characters in `sanitizePathSegment` to prevent infinite redirect loops
+
+`sanitizePathSegment` now re-encodes characters in the WHATWG URL "path percent-encode set" (`<`, `>`, `"`, `` ` ``, `{`, `}`) and ASCII control characters back to their percent-encoded form, instead of stripping control characters. This prevents mismatches between the original URL and the router's internal representation that previously caused infinite 307 redirect loops on paths containing these characters (e.g. `/%7B%7Btemplate%7D%7D`).
+
+Fixes #7587.
diff --git a/e2e/react-router/basic-file-based/tests/open-redirect-prevention.spec.ts b/e2e/react-router/basic-file-based/tests/open-redirect-prevention.spec.ts
index 3ad83fb455..2f0fe256cd 100644
--- a/e2e/react-router/basic-file-based/tests/open-redirect-prevention.spec.ts
+++ b/e2e/react-router/basic-file-based/tests/open-redirect-prevention.spec.ts
@@ -69,10 +69,7 @@ test.describe('Open redirect prevention', () => {
page,
baseURL,
}) => {
- // When control characters are stripped from paths like /%0d/evil.com/
- // the result could be //evil.com/ which is a protocol-relative URL
- // Our fix collapses these to /evil.com/ to prevent external redirects
- // This is already tested above, but we verify the collapsed path works
+ // %0d is kept encoded, so /%0d/test-path/ stays as-is and won't become //test-path/
await page.goto('/%0d/test-path/')
await page.waitForLoadState('networkidle')
@@ -80,8 +77,6 @@ test.describe('Open redirect prevention', () => {
expect(page.url().startsWith(baseURL!)).toBe(true)
const url = new URL(page.url())
expect(url.origin).toBe(new URL(baseURL!).origin)
- // Path should be collapsed to /test-path (not //test-path/)
- expect(url.pathname).toMatch(/^\/test-path\/?$/)
})
})
diff --git a/e2e/react-start/basic/tests/open-redirect-prevention.spec.ts b/e2e/react-start/basic/tests/open-redirect-prevention.spec.ts
index e497109889..bc88c33605 100644
--- a/e2e/react-start/basic/tests/open-redirect-prevention.spec.ts
+++ b/e2e/react-start/basic/tests/open-redirect-prevention.spec.ts
@@ -77,22 +77,14 @@ test.describe('Open redirect prevention', () => {
page,
baseURL,
}) => {
- // When control characters are stripped from paths like /%0d/evil.com/
- // the result could be //evil.com/ which is a protocol-relative URL
- // Our fix collapses these to /evil.com/ to prevent external redirects
- // This is already tested above, but we verify the collapsed path works
- const res = await page.goto('/%0d/test-path/')
+ // %0d is kept encoded, so /%0d/test-path/ stays as-is and won't become //test-path/
+ await page.goto('/%0d/test-path/')
await page.waitForLoadState('networkidle')
// Should stay on the same origin
expect(page.url().startsWith(baseURL!)).toBe(true)
const url = new URL(page.url())
expect(url.origin).toBe(new URL(baseURL!).origin)
- // Path should be collapsed to /test-path (not //test-path/)
- expect(url.pathname).toMatch(/^\/test-path\/?$/)
- if (!isSpaMode) {
- expect(res?.request().redirectedFrom()?.url()).not.toBe(undefined)
- }
})
})
diff --git a/e2e/react-start/basic/tests/special-characters.spec.ts b/e2e/react-start/basic/tests/special-characters.spec.ts
index e88280e2e1..7cf50c39c8 100644
--- a/e2e/react-start/basic/tests/special-characters.spec.ts
+++ b/e2e/react-start/basic/tests/special-characters.spec.ts
@@ -64,6 +64,22 @@ test.describe('Unicode route rendering', () => {
expect(param).toBe('대|')
})
+
+ test('should not cause redirect loop for curly braces in path params', async ({
+ page,
+ baseURL,
+ }) => {
+ // %7B/%7D (curly braces) must not trigger infinite 307 redirects
+ const res = await page.goto('/specialChars/%7B%7Bapp_name%7D%7D')
+ await page.waitForLoadState('load')
+
+ // Should not redirect — URL stays encoded
+ expect(page.url()).toBe(`${baseURL}/specialChars/%7B%7Bapp_name%7D%7D`)
+ expect(res!.status()).toBe(200)
+
+ const param = await page.getByTestId('special-param').textContent()
+ expect(param).toBe('{{app_name}}')
+ })
})
test.describe('Special characters in search params', () => {
diff --git a/packages/router-core/src/utils.ts b/packages/router-core/src/utils.ts
index f017215b5c..91011bfa7f 100644
--- a/packages/router-core/src/utils.ts
+++ b/packages/router-core/src/utils.ts
@@ -523,15 +523,26 @@ export function findLast(
}
/**
- * Remove control characters that can cause open redirect vulnerabilities.
- * Characters like \r (CR) and \n (LF) can trick URL parsers into interpreting
- * paths like "/\r/evil.com" as "http://evil.com".
+ * Re-encode characters that are unsafe in URL paths.
+ * Includes ASCII control characters (0x00-0x1F, 0x7F) and a subset of the
+ * WHATWG URL "path percent-encode set" (", <, >, `, {, }).
+ *
+ * Space (0x20) is intentionally excluded — decodeURI decodes %20 to space
+ * and the router stores decoded spaces in location.pathname. The existing
+ * encodePathLikeUrl already handles re-encoding spaces for outgoing URLs.
+ *
+ * These characters are decoded by decodeURI but must remain percent-encoded
+ * in paths to match how upstream layers (CDNs, edge middleware, browsers)
+ * interpret the URL, preventing infinite redirect loops and path mismatches.
*/
+// eslint-disable-next-line no-control-regex
+const PATH_UNSAFE_RE = /[\x00-\x1f\x7f"<>`{}]/g
+
function sanitizePathSegment(segment: string): string {
- // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)
- // These include CR (\r = 0x0D), LF (\n = 0x0A), and other potentially dangerous characters
- // eslint-disable-next-line no-control-regex
- return segment.replace(/[\x00-\x1f\x7f]/g, '')
+ return segment.replace(
+ PATH_UNSAFE_RE,
+ (ch) => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'),
+ )
}
function decodeSegment(segment: string): string {
@@ -644,8 +655,9 @@ export function decodePath(path: string) {
result = result + decodeSegment(cursor ? path.slice(cursor) : path)
// Prevent open redirect via protocol-relative URLs (e.g. "//evil.com")
- // After sanitizing control characters, paths like "/\r/evil.com" become "//evil.com"
- // Collapse leading double slashes to a single slash
+ // This is defense-in-depth: since control characters are no longer decoded,
+ // paths like "/%0d/evil.com" can no longer become "//evil.com". But we keep
+ // this check to guard against other edge cases.
let handledProtocolRelativeURL = false
if (result.startsWith('//')) {
handledProtocolRelativeURL = true
diff --git a/packages/router-core/tests/utils.test.ts b/packages/router-core/tests/utils.test.ts
index 95874f230e..fff911df37 100644
--- a/packages/router-core/tests/utils.test.ts
+++ b/packages/router-core/tests/utils.test.ts
@@ -547,46 +547,45 @@ describe('decodePath', () => {
})
describe('open redirect prevention', () => {
- it('should strip CR (%0d) to prevent open redirect', () => {
- // /%0d/google.com/ decodes to /\r/google.com/ which becomes //google.com/
- // This must be sanitized to prevent protocol-relative URL interpretation
+ it('should keep CR (%0d) encoded to prevent open redirect', () => {
+ // %0d stays encoded — no decoding, no stripping, no path mismatch
+ // Output is uppercase hex per RFC 3986
const result = decodePath('/%0d/google.com/')
- expect(result.path).toBe('/google.com/')
+ expect(result.path).toBe('/%0D/google.com/')
expect(result.path).not.toMatch(/^\/\//)
- expect(result.handledProtocolRelativeURL).toBe(true)
+ expect(result.handledProtocolRelativeURL).toBe(false)
})
- it('should strip LF (%0a) to prevent open redirect', () => {
+ it('should keep LF (%0a) encoded to prevent open redirect', () => {
const result = decodePath('/%0a/evil.com/')
- expect(result.path).toBe('/evil.com/')
+ expect(result.path).toBe('/%0A/evil.com/')
expect(result.path).not.toMatch(/^\/\//)
- expect(result.handledProtocolRelativeURL).toBe(true)
+ expect(result.handledProtocolRelativeURL).toBe(false)
})
- it('should strip CRLF (%0d%0a) to prevent open redirect', () => {
+ it('should keep CRLF (%0d%0a) encoded to prevent open redirect', () => {
const result = decodePath('/%0d%0a/evil.com/')
- expect(result.path).toBe('/evil.com/')
+ expect(result.path).toBe('/%0D%0A/evil.com/')
expect(result.path).not.toMatch(/^\/\//)
- expect(result.handledProtocolRelativeURL).toBe(true)
+ expect(result.handledProtocolRelativeURL).toBe(false)
})
- it('should strip multiple control characters to prevent open redirect', () => {
+ it('should keep multiple control characters encoded', () => {
const result = decodePath('/%0d%0d%0d/evil.com/')
- expect(result.path).toBe('/evil.com/')
+ expect(result.path).toBe('/%0D%0D%0D/evil.com/')
expect(result.path).not.toMatch(/^\/\//)
- expect(result.handledProtocolRelativeURL).toBe(true)
+ expect(result.handledProtocolRelativeURL).toBe(false)
})
- it('should strip null bytes and other control characters', () => {
+ it('should keep null bytes encoded', () => {
const result = decodePath('/%00/test/')
- expect(result.path).toBe('/test/')
- expect(result.handledProtocolRelativeURL).toBe(true)
+ expect(result.path).toBe('/%00/test/')
+ expect(result.handledProtocolRelativeURL).toBe(false)
})
it('should collapse leading double slashes to prevent protocol-relative URLs', () => {
- // After stripping control chars, ensure we don't end up with //evil.com
- const result = decodePath('/%0d%0a/evil.com/path')
- // Should resolve to localhost, not evil.com
+ // Direct // input should still be collapsed as defense-in-depth
+ const result = decodePath('//evil.com/path')
const url = new URL(result.path, 'http://localhost:3000')
expect(url.origin).toBe('http://localhost:3000')
expect(result.handledProtocolRelativeURL).toBe(true)
@@ -608,6 +607,34 @@ describe('decodePath', () => {
expect(result.handledProtocolRelativeURL).toBe(true)
})
})
+
+ describe('WHATWG path percent-encode set preserved', () => {
+ it('should keep curly braces encoded', () => {
+ const result = decodePath('/%7B%7Bapp_name%7D%7D/Makefile')
+ expect(result.path).toBe('/%7B%7Bapp_name%7D%7D/Makefile')
+ })
+
+ it('should keep angle brackets encoded', () => {
+ expect(decodePath('/%3Ctest%3E').path).toBe('/%3Ctest%3E')
+ })
+
+ it('should keep double quotes encoded', () => {
+ expect(decodePath('/foo%22bar').path).toBe('/foo%22bar')
+ })
+
+ it('should keep backticks encoded', () => {
+ expect(decodePath('/back%60tick').path).toBe('/back%60tick')
+ })
+
+ it('should decode space (handled by encodePathLikeUrl for outgoing URLs)', () => {
+ expect(decodePath('/file%20name').path).toBe('/file name')
+ })
+
+ it('should still decode safe characters', () => {
+ // Regular letters/unicode should still be decoded
+ expect(decodePath('/%D1%88%D0%B5%D0%BB%D0%BB%D1%8B').path).toBe('/шеллы')
+ })
+ })
})
/**
From 572fb2b86086d9914d2376736cfcd1b0426b12ff Mon Sep 17 00:00:00 2001
From: Flo
Date: Wed, 1 Jul 2026 22:34:33 +0200
Subject: [PATCH 16/25] fix(ci): release workflow OOM during
update-example-deps lockfile update (#7722)
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index f901a4be58..cdc2bf2bc8 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
"format": "prettier --experimental-cli --ignore-unknown '**/*' --write",
"changeset": "changeset",
"changeset:publish": "changeset publish",
- "changeset:version": "changeset version && node scripts/update-example-deps.mjs && pnpm install --no-frozen-lockfile && pnpm format",
+ "changeset:version": "changeset version && node scripts/update-example-deps.mjs && pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile && pnpm format",
"gpt-generate": "node gpt/generate.js",
"set-ts-version": "node scripts/set-ts-version.js",
"labeler-generate": "node scripts/generate-labeler-config.ts",
From 8bdb21e06239ec62257292ad93fa342ff034c4f5 Mon Sep 17 00:00:00 2001
From: Flo
Date: Wed, 1 Jul 2026 22:55:47 +0200
Subject: [PATCH 17/25] fix(ci): release workflow OOM increase node
max-old-space (#7723)
---
.github/workflows/release.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fb5c69751c..115c3445e6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -55,6 +55,7 @@ jobs:
commit: 'ci: changeset release'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NODE_OPTIONS: --max-old-space-size=8192
- name: Create GitHub Release
if: steps.changesets.outputs.published == 'true'
run: node scripts/create-github-release.mjs ${{ steps.dist-tag.outputs.prerelease == 'true' && '--prerelease' }} ${{ steps.dist-tag.outputs.latest == 'true' && '--latest' }}
From ecbbd9a8ec1d2433382cd30d13b0764e504e91cf Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 1 Jul 2026 23:33:19 +0200
Subject: [PATCH 18/25] ci: Version Packages (#7676)
ci: changeset release
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/fix-decode-path-preserve-unsafe.md | 9 -
.changeset/perf-frame-decoder-zero-copy.md | 5 -
.../package.json | 4 +-
.../react/authenticated-routes/package.json | 4 +-
.../basic-default-search-params/package.json | 2 +-
.../react/basic-devtools-panel/package.json | 2 +-
examples/react/basic-file-based/package.json | 4 +-
.../basic-non-nested-devtools/package.json | 2 +-
.../basic-react-query-file-based/package.json | 4 +-
examples/react/basic-react-query/package.json | 2 +-
.../react/basic-ssr-file-based/package.json | 4 +-
.../package.json | 4 +-
.../basic-virtual-file-based/package.json | 4 +-
.../package.json | 4 +-
examples/react/basic/package.json | 2 +-
examples/react/deferred-data/package.json | 2 +-
examples/react/i18n-paraglide/package.json | 4 +-
.../kitchen-sink-file-based/package.json | 4 +-
.../package.json | 4 +-
.../kitchen-sink-react-query/package.json | 2 +-
examples/react/kitchen-sink/package.json | 2 +-
examples/react/large-file-based/package.json | 4 +-
examples/react/location-masking/package.json | 2 +-
.../react/navigation-blocking/package.json | 2 +-
.../package.json | 4 +-
.../react/quickstart-file-based/package.json | 4 +-
.../quickstart-rspack-file-based/package.json | 4 +-
.../package.json | 4 +-
examples/react/quickstart/package.json | 2 +-
.../router-monorepo-react-query/package.json | 4 +-
.../packages/router/package.json | 4 +-
.../router-monorepo-simple-lazy/package.json | 4 +-
.../packages/router/package.json | 4 +-
.../react/router-monorepo-simple/package.json | 4 +-
.../packages/router/package.json | 4 +-
.../react/scroll-restoration/package.json | 2 +-
.../search-validator-adapters/package.json | 4 +-
examples/react/start-bare/package.json | 4 +-
examples/react/start-basic-auth/package.json | 4 +-
.../react/start-basic-authjs/package.json | 4 +-
.../react/start-basic-cloudflare/package.json | 4 +-
.../start-basic-react-query/package.json | 4 +-
.../react/start-basic-static/package.json | 6 +-
examples/react/start-basic/package.json | 4 +-
examples/react/start-bun/package.json | 6 +-
examples/react/start-clerk-basic/package.json | 4 +-
.../react/start-convex-trellaux/package.json | 4 +-
examples/react/start-counter/package.json | 4 +-
.../react/start-i18n-paraglide/package.json | 4 +-
examples/react/start-large/package.json | 4 +-
examples/react/start-material-ui/package.json | 4 +-
examples/react/start-rscs/package.json | 4 +-
.../package.json | 4 +-
.../react/start-supabase-basic/package.json | 4 +-
examples/react/start-tailwind-v4/package.json | 4 +-
examples/react/start-trellaux/package.json | 4 +-
examples/react/start-workos/package.json | 4 +-
examples/react/view-transitions/package.json | 4 +-
.../react/with-framer-motion/package.json | 2 +-
.../react/with-trpc-react-query/package.json | 4 +-
examples/react/with-trpc/package.json | 4 +-
.../package.json | 4 +-
.../solid/authenticated-routes/package.json | 4 +-
.../basic-default-search-params/package.json | 2 +-
.../solid/basic-devtools-panel/package.json | 2 +-
examples/solid/basic-file-based/package.json | 4 +-
.../basic-non-nested-devtools/package.json | 2 +-
.../basic-solid-query-file-based/package.json | 4 +-
examples/solid/basic-solid-query/package.json | 4 +-
.../solid/basic-ssr-file-based/package.json | 4 +-
.../package.json | 4 +-
.../basic-virtual-file-based/package.json | 4 +-
.../package.json | 4 +-
examples/solid/basic/package.json | 2 +-
examples/solid/deferred-data/package.json | 2 +-
examples/solid/i18n-paraglide/package.json | 4 +-
.../kitchen-sink-file-based/package.json | 4 +-
.../package.json | 4 +-
.../kitchen-sink-solid-query/package.json | 2 +-
examples/solid/kitchen-sink/package.json | 2 +-
examples/solid/large-file-based/package.json | 4 +-
examples/solid/location-masking/package.json | 2 +-
.../solid/navigation-blocking/package.json | 2 +-
.../package.json | 4 +-
.../solid/quickstart-file-based/package.json | 4 +-
.../quickstart-rspack-file-based/package.json | 4 +-
.../package.json | 4 +-
examples/solid/quickstart/package.json | 2 +-
.../router-monorepo-simple-lazy/package.json | 4 +-
.../packages/router/package.json | 4 +-
.../solid/router-monorepo-simple/package.json | 4 +-
.../packages/router/package.json | 4 +-
.../router-monorepo-solid-query/package.json | 4 +-
.../packages/router/package.json | 4 +-
.../solid/scroll-restoration/package.json | 2 +-
.../search-validator-adapters/package.json | 4 +-
examples/solid/start-basic-auth/package.json | 4 +-
.../solid/start-basic-authjs/package.json | 4 +-
.../solid/start-basic-cloudflare/package.json | 4 +-
.../solid/start-basic-netlify/package.json | 4 +-
examples/solid/start-basic-nitro/package.json | 4 +-
.../start-basic-solid-query/package.json | 4 +-
.../solid/start-basic-static/package.json | 6 +-
examples/solid/start-basic/package.json | 4 +-
examples/solid/start-bun/package.json | 6 +-
.../start-convex-better-auth/package.json | 4 +-
examples/solid/start-counter/package.json | 4 +-
.../solid/start-i18n-paraglide/package.json | 4 +-
examples/solid/start-large/package.json | 4 +-
.../package.json | 4 +-
.../solid/start-supabase-basic/package.json | 4 +-
examples/solid/start-tailwind-v4/package.json | 4 +-
examples/solid/view-transitions/package.json | 4 +-
.../solid/with-framer-motion/package.json | 2 +-
examples/solid/with-trpc/package.json | 4 +-
.../vue/basic-file-based-jsx/package.json | 4 +-
.../vue/basic-file-based-sfc/package.json | 4 +-
examples/vue/basic/package.json | 2 +-
packages/react-router/CHANGELOG.md | 7 +
packages/react-router/package.json | 2 +-
packages/react-start-client/CHANGELOG.md | 9 +
packages/react-start-client/package.json | 2 +-
packages/react-start-rsc/CHANGELOG.md | 12 +
packages/react-start-rsc/package.json | 2 +-
packages/react-start-server/CHANGELOG.md | 9 +
packages/react-start-server/package.json | 2 +-
packages/react-start/CHANGELOG.md | 13 +
packages/react-start/package.json | 2 +-
packages/router-cli/CHANGELOG.md | 7 +
packages/router-cli/package.json | 2 +-
packages/router-core/CHANGELOG.md | 10 +
packages/router-core/package.json | 2 +-
packages/router-generator/CHANGELOG.md | 7 +
packages/router-generator/package.json | 2 +-
packages/router-plugin/CHANGELOG.md | 9 +
packages/router-plugin/package.json | 2 +-
packages/router-vite-plugin/CHANGELOG.md | 7 +
packages/router-vite-plugin/package.json | 2 +-
packages/solid-router/CHANGELOG.md | 7 +
packages/solid-router/package.json | 2 +-
packages/solid-start-client/CHANGELOG.md | 9 +
packages/solid-start-client/package.json | 2 +-
packages/solid-start-server/CHANGELOG.md | 9 +
packages/solid-start-server/package.json | 2 +-
packages/solid-start/CHANGELOG.md | 12 +
packages/solid-start/package.json | 2 +-
packages/start-client-core/CHANGELOG.md | 10 +
packages/start-client-core/package.json | 2 +-
packages/start-plugin-core/CHANGELOG.md | 10 +
packages/start-plugin-core/package.json | 2 +-
packages/start-server-core/CHANGELOG.md | 9 +
packages/start-server-core/package.json | 2 +-
.../CHANGELOG.md | 9 +
.../package.json | 2 +-
packages/start-storage-context/CHANGELOG.md | 7 +
packages/start-storage-context/package.json | 2 +-
packages/vue-router/CHANGELOG.md | 7 +
packages/vue-router/package.json | 2 +-
packages/vue-start-client/CHANGELOG.md | 9 +
packages/vue-start-client/package.json | 2 +-
packages/vue-start-server/CHANGELOG.md | 9 +
packages/vue-start-server/package.json | 2 +-
packages/vue-start/CHANGELOG.md | 12 +
packages/vue-start/package.json | 2 +-
pnpm-lock.yaml | 382 +++++++++---------
165 files changed, 623 insertions(+), 448 deletions(-)
delete mode 100644 .changeset/fix-decode-path-preserve-unsafe.md
delete mode 100644 .changeset/perf-frame-decoder-zero-copy.md
diff --git a/.changeset/fix-decode-path-preserve-unsafe.md b/.changeset/fix-decode-path-preserve-unsafe.md
deleted file mode 100644
index a68ae9e3e1..0000000000
--- a/.changeset/fix-decode-path-preserve-unsafe.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-'@tanstack/router-core': patch
----
-
-fix(router-core): re-encode URL-unsafe characters in `sanitizePathSegment` to prevent infinite redirect loops
-
-`sanitizePathSegment` now re-encodes characters in the WHATWG URL "path percent-encode set" (`<`, `>`, `"`, `` ` ``, `{`, `}`) and ASCII control characters back to their percent-encoded form, instead of stripping control characters. This prevents mismatches between the original URL and the router's internal representation that previously caused infinite 307 redirect loops on paths containing these characters (e.g. `/%7B%7Btemplate%7D%7D`).
-
-Fixes #7587.
diff --git a/.changeset/perf-frame-decoder-zero-copy.md b/.changeset/perf-frame-decoder-zero-copy.md
deleted file mode 100644
index 182c81b66a..0000000000
--- a/.changeset/perf-frame-decoder-zero-copy.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@tanstack/start-client-core': patch
----
-
-perf: zero-copy frame payload extraction in the client frame decoder. When a frame's bytes are fully contained in the first buffered chunk (the common case), return a subarray view instead of allocating a new buffer and copying. This is on the hot path for decoding streamed server-function responses and `RawStream` payloads.
diff --git a/examples/react/authenticated-routes-firebase/package.json b/examples/react/authenticated-routes-firebase/package.json
index dafac3ef01..58c677ed26 100644
--- a/examples/react/authenticated-routes-firebase/package.json
+++ b/examples/react/authenticated-routes-firebase/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"firebase": "^11.4.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json
index 164e0c3b07..5517800c01 100644
--- a/examples/react/authenticated-routes/package.json
+++ b/examples/react/authenticated-routes/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json
index afda60a9de..97bfd6d8d4 100644
--- a/examples/react/basic-default-search-params/package.json
+++ b/examples/react/basic-default-search-params/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-devtools-panel/package.json b/examples/react/basic-devtools-panel/package.json
index bc5502b64a..9ff1c8fefb 100644
--- a/examples/react/basic-devtools-panel/package.json
+++ b/examples/react/basic-devtools-panel/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query-devtools": "^5.67.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json
index c9b60b4a41..bca55ff7d5 100644
--- a/examples/react/basic-file-based/package.json
+++ b/examples/react/basic-file-based/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/basic-non-nested-devtools/package.json b/examples/react/basic-non-nested-devtools/package.json
index 496bf4f428..fe6f2a04bd 100644
--- a/examples/react/basic-non-nested-devtools/package.json
+++ b/examples/react/basic-non-nested-devtools/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json
index 5e4d1c16e4..501a22dcc3 100644
--- a/examples/react/basic-react-query-file-based/package.json
+++ b/examples/react/basic-react-query-file-based/package.json
@@ -12,9 +12,9 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json
index 8afb35f672..b56851bdac 100644
--- a/examples/react/basic-react-query/package.json
+++ b/examples/react/basic-react-query/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json
index b7df3723d4..3fa61c784f 100644
--- a/examples/react/basic-ssr-file-based/package.json
+++ b/examples/react/basic-ssr-file-based/package.json
@@ -11,8 +11,8 @@
"debug": "node --inspect-brk server"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/react-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"compression": "^1.8.0",
"express": "^5.2.1",
"get-port": "^7.1.0",
diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json
index 25b6c3ba79..b24243e276 100644
--- a/examples/react/basic-ssr-streaming-file-based/package.json
+++ b/examples/react/basic-ssr-streaming-file-based/package.json
@@ -11,8 +11,8 @@
"debug": "node --inspect-brk server"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/react-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"compression": "^1.8.0",
"express": "^5.2.1",
"get-port": "^7.1.0",
diff --git a/examples/react/basic-virtual-file-based/package.json b/examples/react/basic-virtual-file-based/package.json
index b6456f16c4..922c460906 100644
--- a/examples/react/basic-virtual-file-based/package.json
+++ b/examples/react/basic-virtual-file-based/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@tanstack/virtual-file-routes": "^1.162.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic-virtual-inside-file-based/package.json b/examples/react/basic-virtual-inside-file-based/package.json
index f3b7ded6b5..a24bf519d9 100644
--- a/examples/react/basic-virtual-inside-file-based/package.json
+++ b/examples/react/basic-virtual-inside-file-based/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@tanstack/virtual-file-routes": "^1.162.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json
index 500d62838d..d245cc9f0e 100644
--- a/examples/react/basic/package.json
+++ b/examples/react/basic/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json
index ce4d0a23f9..b55fb41ccc 100644
--- a/examples/react/deferred-data/package.json
+++ b/examples/react/deferred-data/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/i18n-paraglide/package.json b/examples/react/i18n-paraglide/package.json
index ae8a8cf345..d9a9602c02 100644
--- a/examples/react/i18n-paraglide/package.json
+++ b/examples/react/i18n-paraglide/package.json
@@ -11,8 +11,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/react-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"tailwindcss": "^4.2.2"
diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json
index f52cc14be2..2e18eb4358 100644
--- a/examples/react/kitchen-sink-file-based/package.json
+++ b/examples/react/kitchen-sink-file-based/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"immer": "^10.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/kitchen-sink-react-query-file-based/package.json b/examples/react/kitchen-sink-react-query-file-based/package.json
index a31d6bfd58..f712e86a8f 100644
--- a/examples/react/kitchen-sink-react-query-file-based/package.json
+++ b/examples/react/kitchen-sink-react-query-file-based/package.json
@@ -12,9 +12,9 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"immer": "^10.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json
index bd7cc359ec..3003408f70 100644
--- a/examples/react/kitchen-sink-react-query/package.json
+++ b/examples/react/kitchen-sink-react-query/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"react": "^19.0.0",
diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json
index 40de321285..4414b457f2 100644
--- a/examples/react/kitchen-sink/package.json
+++ b/examples/react/kitchen-sink/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"react": "^19.0.0",
diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json
index 75429bd82a..16fd095515 100644
--- a/examples/react/large-file-based/package.json
+++ b/examples/react/large-file-based/package.json
@@ -13,9 +13,9 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json
index f593f59ba2..76d00658a4 100644
--- a/examples/react/location-masking/package.json
+++ b/examples/react/location-masking/package.json
@@ -12,7 +12,7 @@
"@radix-ui/react-dialog": "^1.1.6",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json
index b0255cf627..757ae9f2e6 100644
--- a/examples/react/navigation-blocking/package.json
+++ b/examples/react/navigation-blocking/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json
index 4817cba83b..300650c666 100644
--- a/examples/react/quickstart-esbuild-file-based/package.json
+++ b/examples/react/quickstart-esbuild-file-based/package.json
@@ -9,9 +9,9 @@
"start": "dev"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/quickstart-file-based/package.json b/examples/react/quickstart-file-based/package.json
index 53d26bfa71..31a70cf36c 100644
--- a/examples/react/quickstart-file-based/package.json
+++ b/examples/react/quickstart-file-based/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json
index 2ee1c96f0d..f3525defde 100644
--- a/examples/react/quickstart-rspack-file-based/package.json
+++ b/examples/react/quickstart-rspack-file-based/package.json
@@ -9,7 +9,7 @@
},
"dependencies": {
"@tailwindcss/postcss": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"postcss": "^8.5.1",
"react": "^19.0.0",
@@ -19,7 +19,7 @@
"devDependencies": {
"@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-react": "^2.0.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"typescript": "^6.0.2"
diff --git a/examples/react/quickstart-webpack-file-based/package.json b/examples/react/quickstart-webpack-file-based/package.json
index 5e16ac8b4f..4c778eab28 100644
--- a/examples/react/quickstart-webpack-file-based/package.json
+++ b/examples/react/quickstart-webpack-file-based/package.json
@@ -7,14 +7,14 @@
"build": "webpack build && tsc --noEmit"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@swc/core": "^1.15.33",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"html-webpack-plugin": "^5.6.3",
diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json
index 19bd01d412..00269e8524 100644
--- a/examples/react/quickstart/package.json
+++ b/examples/react/quickstart/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/router-monorepo-react-query/package.json b/examples/react/router-monorepo-react-query/package.json
index 25de375254..75532f61b1 100644
--- a/examples/react/router-monorepo-react-query/package.json
+++ b/examples/react/router-monorepo-react-query/package.json
@@ -12,9 +12,9 @@
"dependencies": {
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1"
diff --git a/examples/react/router-monorepo-react-query/packages/router/package.json b/examples/react/router-monorepo-react-query/packages/router/package.json
index a0ef2fabd1..136535b761 100644
--- a/examples/react/router-monorepo-react-query/packages/router/package.json
+++ b/examples/react/router-monorepo-react-query/packages/router/package.json
@@ -11,8 +11,8 @@
"@router-mono-react-query/post-query": "workspace:*",
"@tanstack/history": "^1.162.0",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/react-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/router-monorepo-simple-lazy/package.json b/examples/react/router-monorepo-simple-lazy/package.json
index e6a6b51a7d..dd06440bf3 100644
--- a/examples/react/router-monorepo-simple-lazy/package.json
+++ b/examples/react/router-monorepo-simple-lazy/package.json
@@ -8,9 +8,9 @@
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1"
diff --git a/examples/react/router-monorepo-simple-lazy/packages/router/package.json b/examples/react/router-monorepo-simple-lazy/packages/router/package.json
index 55dfce9be7..8b0b87a474 100644
--- a/examples/react/router-monorepo-simple-lazy/packages/router/package.json
+++ b/examples/react/router-monorepo-simple-lazy/packages/router/package.json
@@ -9,8 +9,8 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/history": "^1.162.0",
- "@tanstack/react-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/react-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/router-monorepo-simple/package.json b/examples/react/router-monorepo-simple/package.json
index 907bdc33a8..77cf9ecdf5 100644
--- a/examples/react/router-monorepo-simple/package.json
+++ b/examples/react/router-monorepo-simple/package.json
@@ -8,9 +8,9 @@
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1"
diff --git a/examples/react/router-monorepo-simple/packages/router/package.json b/examples/react/router-monorepo-simple/packages/router/package.json
index 755e90987e..6bdb5ec943 100644
--- a/examples/react/router-monorepo-simple/packages/router/package.json
+++ b/examples/react/router-monorepo-simple/packages/router/package.json
@@ -9,8 +9,8 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/history": "^1.162.0",
- "@tanstack/react-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/react-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json
index 0f14bafacc..7a503801e5 100644
--- a/examples/react/scroll-restoration/package.json
+++ b/examples/react/scroll-restoration/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-virtual": "^3.13.0",
"react": "^19.0.0",
diff --git a/examples/react/search-validator-adapters/package.json b/examples/react/search-validator-adapters/package.json
index f0445b6f93..234abba717 100644
--- a/examples/react/search-validator-adapters/package.json
+++ b/examples/react/search-validator-adapters/package.json
@@ -13,9 +13,9 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/arktype-adapter": "^1.167.0",
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@tanstack/valibot-adapter": "^1.167.0",
"@tanstack/zod-adapter": "^1.167.0",
"arktype": "^2.1.7",
diff --git a/examples/react/start-bare/package.json b/examples/react/start-bare/package.json
index 190186a6f4..5a15546ef6 100644
--- a/examples/react/start-bare/package.json
+++ b/examples/react/start-bare/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^4.4.3"
diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json
index 9a9f93e019..abee47dc52 100644
--- a/examples/react/start-basic-auth/package.json
+++ b/examples/react/start-basic-auth/package.json
@@ -14,9 +14,9 @@
"@libsql/client": "^0.15.15",
"@prisma/adapter-libsql": "^7.0.0",
"@prisma/client": "^7.0.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/start-basic-authjs/package.json b/examples/react/start-basic-authjs/package.json
index d1402e2c53..a28f1c313f 100644
--- a/examples/react/start-basic-authjs/package.json
+++ b/examples/react/start-basic-authjs/package.json
@@ -11,9 +11,9 @@
},
"dependencies": {
"@auth/core": "^0.41.1",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"start-authjs": "^1.0.0",
diff --git a/examples/react/start-basic-cloudflare/package.json b/examples/react/start-basic-cloudflare/package.json
index b512fe867d..f74602ab83 100644
--- a/examples/react/start-basic-cloudflare/package.json
+++ b/examples/react/start-basic-cloudflare/package.json
@@ -12,9 +12,9 @@
"postinstall": "WRANGLER_LOG=warn npm run --silent cf-typegen"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json
index dcb5b81796..40a18be142 100644
--- a/examples/react/start-basic-react-query/package.json
+++ b/examples/react/start-basic-react-query/package.json
@@ -12,10 +12,10 @@
"dependencies": {
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-router-ssr-query": "^1.167.1",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/start-basic-static/package.json b/examples/react/start-basic-static/package.json
index 9a2f888be5..8ea4c54edc 100644
--- a/examples/react/start-basic-static/package.json
+++ b/examples/react/start-basic-static/package.json
@@ -10,10 +10,10 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
- "@tanstack/start-static-server-functions": "^1.167.17",
+ "@tanstack/react-start": "^1.168.27",
+ "@tanstack/start-static-server-functions": "^1.167.18",
"@vitejs/plugin-react": "^6.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json
index 72372d7806..931df2a45d 100644
--- a/examples/react/start-basic/package.json
+++ b/examples/react/start-basic/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^3.6.0",
diff --git a/examples/react/start-bun/package.json b/examples/react/start-bun/package.json
index 7b262c6bf7..a4ab34310f 100644
--- a/examples/react/start-bun/package.json
+++ b/examples/react/start-bun/package.json
@@ -15,11 +15,11 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-devtools": "^0.7.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-router-ssr-query": "^1.167.1",
- "@tanstack/react-start": "^1.168.26",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/react-start": "^1.168.27",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"tailwindcss": "^4.2.2"
diff --git a/examples/react/start-clerk-basic/package.json b/examples/react/start-clerk-basic/package.json
index 121b04bcfe..7e15b08073 100644
--- a/examples/react/start-clerk-basic/package.json
+++ b/examples/react/start-clerk-basic/package.json
@@ -11,9 +11,9 @@
},
"dependencies": {
"@clerk/tanstack-react-start": "^0.27.14",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"@vitejs/plugin-react": "^6.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json
index 06616b8bcd..f2ec73bfe8 100644
--- a/examples/react/start-convex-trellaux/package.json
+++ b/examples/react/start-convex-trellaux/package.json
@@ -15,10 +15,10 @@
"@convex-dev/react-query": "0.0.0-alpha.8",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-router-ssr-query": "^1.167.1",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"concurrently": "^8.2.2",
"convex": "^1.19.0",
"ky": "^1.7.4",
diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json
index b9b113dcdb..a7a8397b74 100644
--- a/examples/react/start-counter/package.json
+++ b/examples/react/start-counter/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
diff --git a/examples/react/start-i18n-paraglide/package.json b/examples/react/start-i18n-paraglide/package.json
index 238ad332cf..936f5e040d 100644
--- a/examples/react/start-i18n-paraglide/package.json
+++ b/examples/react/start-i18n-paraglide/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tanstack/react-devtools": "^0.7.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json
index f26f5aa248..d7300fc815 100644
--- a/examples/react/start-large/package.json
+++ b/examples/react/start-large/package.json
@@ -13,9 +13,9 @@
},
"dependencies": {
"@tanstack/react-query": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/start-material-ui/package.json b/examples/react/start-material-ui/package.json
index b4b1fdcc18..a7623d10f0 100644
--- a/examples/react/start-material-ui/package.json
+++ b/examples/react/start-material-ui/package.json
@@ -15,9 +15,9 @@
"@emotion/styled": "11.14.0",
"@fontsource-variable/roboto": "5.2.5",
"@mui/material": "6.4.7",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^4.4.3"
diff --git a/examples/react/start-rscs/package.json b/examples/react/start-rscs/package.json
index e77685070f..980f43eff9 100644
--- a/examples/react/start-rscs/package.json
+++ b/examples/react/start-rscs/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"dexie": "^4.0.10",
"react": "^19.2.0",
"react-dom": "^19.2.0",
diff --git a/examples/react/start-streaming-data-from-server-functions/package.json b/examples/react/start-streaming-data-from-server-functions/package.json
index 149aed0d8e..e5d50b902f 100644
--- a/examples/react/start-streaming-data-from-server-functions/package.json
+++ b/examples/react/start-streaming-data-from-server-functions/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^4.4.3"
diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json
index 59aaaded53..30083cbe1d 100644
--- a/examples/react/start-supabase-basic/package.json
+++ b/examples/react/start-supabase-basic/package.json
@@ -15,9 +15,9 @@
"dependencies": {
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.48.1",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1"
diff --git a/examples/react/start-tailwind-v4/package.json b/examples/react/start-tailwind-v4/package.json
index b555b3703e..f8e537c9b4 100644
--- a/examples/react/start-tailwind-v4/package.json
+++ b/examples/react/start-tailwind-v4/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^3.6.0",
diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json
index 2de169fea6..29bf8bba30 100644
--- a/examples/react/start-trellaux/package.json
+++ b/examples/react/start-trellaux/package.json
@@ -12,10 +12,10 @@
"dependencies": {
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-router-ssr-query": "^1.167.1",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"ky": "^1.7.4",
"msw": "^2.7.0",
"react": "^19.0.0",
diff --git a/examples/react/start-workos/package.json b/examples/react/start-workos/package.json
index 4379c464b8..935c7a0a5f 100644
--- a/examples/react/start-workos/package.json
+++ b/examples/react/start-workos/package.json
@@ -14,9 +14,9 @@
"license": "MIT",
"dependencies": {
"@radix-ui/themes": "^3.3.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/react-start": "^1.168.26",
+ "@tanstack/react-start": "^1.168.27",
"@workos/authkit-tanstack-react-start": "^0.5.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
diff --git a/examples/react/view-transitions/package.json b/examples/react/view-transitions/package.json
index 3af69ed288..a5cc8bc839 100644
--- a/examples/react/view-transitions/package.json
+++ b/examples/react/view-transitions/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
diff --git a/examples/react/with-framer-motion/package.json b/examples/react/with-framer-motion/package.json
index 6dee036c9e..c4d861b689 100644
--- a/examples/react/with-framer-motion/package.json
+++ b/examples/react/with-framer-motion/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"framer-motion": "^11.18.2",
"react": "^19.0.0",
diff --git a/examples/react/with-trpc-react-query/package.json b/examples/react/with-trpc-react-query/package.json
index f849a2dc12..48e7eaa6cb 100644
--- a/examples/react/with-trpc-react-query/package.json
+++ b/examples/react/with-trpc-react-query/package.json
@@ -13,9 +13,9 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.0",
"@tanstack/react-query-devtools": "^5.90.0",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@trpc/client": "^11.4.3",
"@trpc/server": "^11.4.3",
"@trpc/tanstack-react-query": "^11.4.3",
diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json
index bad08150a1..3474e12a6b 100644
--- a/examples/react/with-trpc/package.json
+++ b/examples/react/with-trpc/package.json
@@ -11,9 +11,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/react-router": "^1.170.16",
+ "@tanstack/react-router": "^1.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@trpc/client": "^11.4.3",
"@trpc/server": "^11.4.3",
"express": "^5.2.1",
diff --git a/examples/solid/authenticated-routes-firebase/package.json b/examples/solid/authenticated-routes-firebase/package.json
index ce1178dcc3..fdb0cd4400 100644
--- a/examples/solid/authenticated-routes-firebase/package.json
+++ b/examples/solid/authenticated-routes-firebase/package.json
@@ -10,8 +10,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"firebase": "^11.4.0",
"redaxios": "^0.5.1",
diff --git a/examples/solid/authenticated-routes/package.json b/examples/solid/authenticated-routes/package.json
index 79c7b3e35b..4e3fac9dec 100644
--- a/examples/solid/authenticated-routes/package.json
+++ b/examples/solid/authenticated-routes/package.json
@@ -10,8 +10,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-default-search-params/package.json b/examples/solid/basic-default-search-params/package.json
index 7aa5a5771f..f1f806b647 100644
--- a/examples/solid/basic-default-search-params/package.json
+++ b/examples/solid/basic-default-search-params/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-devtools-panel/package.json b/examples/solid/basic-devtools-panel/package.json
index 21b0dbbb4d..6bc2dacd1f 100644
--- a/examples/solid/basic-devtools-panel/package.json
+++ b/examples/solid/basic-devtools-panel/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-file-based/package.json b/examples/solid/basic-file-based/package.json
index 3875d710ff..40e93847d0 100644
--- a/examples/solid/basic-file-based/package.json
+++ b/examples/solid/basic-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
@@ -18,7 +18,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"typescript": "^6.0.2",
"vite": "^8.0.14",
"vite-plugin-solid": "^2.11.11"
diff --git a/examples/solid/basic-non-nested-devtools/package.json b/examples/solid/basic-non-nested-devtools/package.json
index f644b324b0..4cf678441f 100644
--- a/examples/solid/basic-non-nested-devtools/package.json
+++ b/examples/solid/basic-non-nested-devtools/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/basic-solid-query-file-based/package.json b/examples/solid/basic-solid-query-file-based/package.json
index 548e6630c7..bae59ef0e5 100644
--- a/examples/solid/basic-solid-query-file-based/package.json
+++ b/examples/solid/basic-solid-query-file-based/package.json
@@ -13,7 +13,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
@@ -21,7 +21,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"typescript": "^6.0.2",
"vite": "^8.0.14",
"vite-plugin-solid": "^2.11.11"
diff --git a/examples/solid/basic-solid-query/package.json b/examples/solid/basic-solid-query/package.json
index 06a2d942fe..8efdd34c10 100644
--- a/examples/solid/basic-solid-query/package.json
+++ b/examples/solid/basic-solid-query/package.json
@@ -12,14 +12,14 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
},
"devDependencies": {
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"typescript": "^6.0.2",
"vite": "^8.0.14",
"vite-plugin-solid": "^2.11.11"
diff --git a/examples/solid/basic-ssr-file-based/package.json b/examples/solid/basic-ssr-file-based/package.json
index df458d40c7..1e8a57559f 100644
--- a/examples/solid/basic-ssr-file-based/package.json
+++ b/examples/solid/basic-ssr-file-based/package.json
@@ -11,8 +11,8 @@
"debug": "node --inspect-brk server"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/solid-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"compression": "^1.8.0",
"express": "^5.2.1",
"get-port": "^7.1.0",
diff --git a/examples/solid/basic-ssr-streaming-file-based/package.json b/examples/solid/basic-ssr-streaming-file-based/package.json
index 435d2b9337..cbdda9063b 100644
--- a/examples/solid/basic-ssr-streaming-file-based/package.json
+++ b/examples/solid/basic-ssr-streaming-file-based/package.json
@@ -12,7 +12,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"compression": "^1.8.0",
"express": "^5.2.1",
@@ -24,7 +24,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@types/express": "^5.0.6",
"typescript": "^6.0.2",
"vite": "^8.0.14",
diff --git a/examples/solid/basic-virtual-file-based/package.json b/examples/solid/basic-virtual-file-based/package.json
index 7670b540ab..17ba921082 100644
--- a/examples/solid/basic-virtual-file-based/package.json
+++ b/examples/solid/basic-virtual-file-based/package.json
@@ -10,8 +10,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/virtual-file-routes": "^1.162.0",
"redaxios": "^0.5.1",
diff --git a/examples/solid/basic-virtual-inside-file-based/package.json b/examples/solid/basic-virtual-inside-file-based/package.json
index 29f03157a0..e24fd542d1 100644
--- a/examples/solid/basic-virtual-inside-file-based/package.json
+++ b/examples/solid/basic-virtual-inside-file-based/package.json
@@ -10,8 +10,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/virtual-file-routes": "^1.162.0",
"redaxios": "^0.5.1",
diff --git a/examples/solid/basic/package.json b/examples/solid/basic/package.json
index e63f01566d..9b42dafb14 100644
--- a/examples/solid/basic/package.json
+++ b/examples/solid/basic/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/deferred-data/package.json b/examples/solid/deferred-data/package.json
index 6b2ba6437b..5286f94c06 100644
--- a/examples/solid/deferred-data/package.json
+++ b/examples/solid/deferred-data/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/i18n-paraglide/package.json b/examples/solid/i18n-paraglide/package.json
index ca853c7e08..f9506ebbdc 100644
--- a/examples/solid/i18n-paraglide/package.json
+++ b/examples/solid/i18n-paraglide/package.json
@@ -11,8 +11,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/solid-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
},
diff --git a/examples/solid/kitchen-sink-file-based/package.json b/examples/solid/kitchen-sink-file-based/package.json
index 8c02a9cd24..956a852426 100644
--- a/examples/solid/kitchen-sink-file-based/package.json
+++ b/examples/solid/kitchen-sink-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"redaxios": "^0.5.1",
@@ -19,7 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"typescript": "^6.0.2",
"vite": "^8.0.14",
"vite-plugin-solid": "^2.11.11"
diff --git a/examples/solid/kitchen-sink-solid-query-file-based/package.json b/examples/solid/kitchen-sink-solid-query-file-based/package.json
index 3e818ed243..78c95f0cdf 100644
--- a/examples/solid/kitchen-sink-solid-query-file-based/package.json
+++ b/examples/solid/kitchen-sink-solid-query-file-based/package.json
@@ -10,10 +10,10 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"redaxios": "^0.5.1",
diff --git a/examples/solid/kitchen-sink-solid-query/package.json b/examples/solid/kitchen-sink-solid-query/package.json
index c078ac2bb9..eca233210a 100644
--- a/examples/solid/kitchen-sink-solid-query/package.json
+++ b/examples/solid/kitchen-sink-solid-query/package.json
@@ -12,7 +12,7 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"redaxios": "^0.5.1",
diff --git a/examples/solid/kitchen-sink/package.json b/examples/solid/kitchen-sink/package.json
index 4223f4b840..8d588e4f27 100644
--- a/examples/solid/kitchen-sink/package.json
+++ b/examples/solid/kitchen-sink/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"immer": "^10.1.1",
"redaxios": "^0.5.1",
diff --git a/examples/solid/large-file-based/package.json b/examples/solid/large-file-based/package.json
index 17b2ba0610..a8b59cae3b 100644
--- a/examples/solid/large-file-based/package.json
+++ b/examples/solid/large-file-based/package.json
@@ -12,9 +12,9 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/location-masking/package.json b/examples/solid/location-masking/package.json
index b15d437baf..cd42e50706 100644
--- a/examples/solid/location-masking/package.json
+++ b/examples/solid/location-masking/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1",
diff --git a/examples/solid/navigation-blocking/package.json b/examples/solid/navigation-blocking/package.json
index 3a3d7c88e3..e433b54680 100644
--- a/examples/solid/navigation-blocking/package.json
+++ b/examples/solid/navigation-blocking/package.json
@@ -11,7 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1",
diff --git a/examples/solid/quickstart-esbuild-file-based/package.json b/examples/solid/quickstart-esbuild-file-based/package.json
index 8ff9be64eb..425a39e4f6 100644
--- a/examples/solid/quickstart-esbuild-file-based/package.json
+++ b/examples/solid/quickstart-esbuild-file-based/package.json
@@ -9,8 +9,8 @@
"start": "dev"
},
"dependencies": {
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/quickstart-file-based/package.json b/examples/solid/quickstart-file-based/package.json
index 16fa0bcce6..4774fa54ba 100644
--- a/examples/solid/quickstart-file-based/package.json
+++ b/examples/solid/quickstart-file-based/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
@@ -18,7 +18,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"typescript": "^6.0.2",
"vite": "^8.0.14",
"vite-plugin-solid": "^2.11.11"
diff --git a/examples/solid/quickstart-rspack-file-based/package.json b/examples/solid/quickstart-rspack-file-based/package.json
index 82e1eaec3f..290a88219a 100644
--- a/examples/solid/quickstart-rspack-file-based/package.json
+++ b/examples/solid/quickstart-rspack-file-based/package.json
@@ -9,7 +9,7 @@
},
"dependencies": {
"@tailwindcss/postcss": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"postcss": "^8.5.1",
"solid-js": "^1.9.10",
@@ -19,7 +19,7 @@
"@rsbuild/core": "^2.0.11",
"@rsbuild/plugin-babel": "^1.1.2",
"@rsbuild/plugin-solid": "^1.1.1",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"typescript": "^6.0.2"
}
}
diff --git a/examples/solid/quickstart-webpack-file-based/package.json b/examples/solid/quickstart-webpack-file-based/package.json
index 2a65a99a2a..daa70edd1b 100644
--- a/examples/solid/quickstart-webpack-file-based/package.json
+++ b/examples/solid/quickstart-webpack-file-based/package.json
@@ -7,7 +7,7 @@
"build": "webpack build && tsc --noEmit"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
@@ -15,7 +15,7 @@
"devDependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-typescript": "^7.27.1",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"babel-loader": "^10.0.0",
"babel-preset-solid": "^1.9.10",
"css-loader": "^7.1.2",
diff --git a/examples/solid/quickstart/package.json b/examples/solid/quickstart/package.json
index 310ce847a1..6cec309396 100644
--- a/examples/solid/quickstart/package.json
+++ b/examples/solid/quickstart/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
diff --git a/examples/solid/router-monorepo-simple-lazy/package.json b/examples/solid/router-monorepo-simple-lazy/package.json
index d1d6bc352a..34cdf6023f 100644
--- a/examples/solid/router-monorepo-simple-lazy/package.json
+++ b/examples/solid/router-monorepo-simple-lazy/package.json
@@ -8,9 +8,9 @@
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1"
},
diff --git a/examples/solid/router-monorepo-simple-lazy/packages/router/package.json b/examples/solid/router-monorepo-simple-lazy/packages/router/package.json
index deefbaadac..83afc30958 100644
--- a/examples/solid/router-monorepo-simple-lazy/packages/router/package.json
+++ b/examples/solid/router-monorepo-simple-lazy/packages/router/package.json
@@ -9,8 +9,8 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/history": "^1.162.0",
- "@tanstack/solid-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/solid-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"redaxios": "^0.5.1",
"zod": "^4.4.3",
"solid-js": "^1.9.10"
diff --git a/examples/solid/router-monorepo-simple/package.json b/examples/solid/router-monorepo-simple/package.json
index 9741d561b4..f9fbd5fc29 100644
--- a/examples/solid/router-monorepo-simple/package.json
+++ b/examples/solid/router-monorepo-simple/package.json
@@ -8,9 +8,9 @@
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1"
},
diff --git a/examples/solid/router-monorepo-simple/packages/router/package.json b/examples/solid/router-monorepo-simple/packages/router/package.json
index 6b5990d5c7..37fb3f8b40 100644
--- a/examples/solid/router-monorepo-simple/packages/router/package.json
+++ b/examples/solid/router-monorepo-simple/packages/router/package.json
@@ -9,8 +9,8 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/history": "^1.162.0",
- "@tanstack/solid-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/solid-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"redaxios": "^0.5.1",
"zod": "^4.4.3",
"solid-js": "^1.9.10"
diff --git a/examples/solid/router-monorepo-solid-query/package.json b/examples/solid/router-monorepo-solid-query/package.json
index 50609c03ca..a781b16bff 100644
--- a/examples/solid/router-monorepo-solid-query/package.json
+++ b/examples/solid/router-monorepo-solid-query/package.json
@@ -12,9 +12,9 @@
"dependencies": {
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1"
},
diff --git a/examples/solid/router-monorepo-solid-query/packages/router/package.json b/examples/solid/router-monorepo-solid-query/packages/router/package.json
index ffcf38ac81..dcdd421751 100644
--- a/examples/solid/router-monorepo-solid-query/packages/router/package.json
+++ b/examples/solid/router-monorepo-solid-query/packages/router/package.json
@@ -10,8 +10,8 @@
"dependencies": {
"@tanstack/history": "^1.162.0",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.16",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/solid-router": "^1.170.17",
+ "@tanstack/router-plugin": "^1.168.19",
"@router-solid-mono-solid-query/post-query": "workspace:*",
"redaxios": "^0.5.1",
"zod": "^4.4.3",
diff --git a/examples/solid/scroll-restoration/package.json b/examples/solid/scroll-restoration/package.json
index 482475cadf..562ec8e1e3 100644
--- a/examples/solid/scroll-restoration/package.json
+++ b/examples/solid/scroll-restoration/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/solid-virtual": "^3.13.0",
"solid-js": "^1.9.10",
diff --git a/examples/solid/search-validator-adapters/package.json b/examples/solid/search-validator-adapters/package.json
index ebc44256b4..9003a5ae4d 100644
--- a/examples/solid/search-validator-adapters/package.json
+++ b/examples/solid/search-validator-adapters/package.json
@@ -13,9 +13,9 @@
"@tailwindcss/vite": "^4.2.2",
"@tanstack/arktype-adapter": "^1.167.0",
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/router-plugin": "^1.168.19",
"@tanstack/valibot-adapter": "^1.167.0",
"@tanstack/zod-adapter": "^1.167.0",
"arktype": "^2.1.7",
diff --git a/examples/solid/start-basic-auth/package.json b/examples/solid/start-basic-auth/package.json
index 20f86e46b6..05f435903d 100644
--- a/examples/solid/start-basic-auth/package.json
+++ b/examples/solid/start-basic-auth/package.json
@@ -14,9 +14,9 @@
"@libsql/client": "^0.15.15",
"@prisma/adapter-libsql": "^7.0.0",
"@prisma/client": "^7.0.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-basic-authjs/package.json b/examples/solid/start-basic-authjs/package.json
index f5396a2f42..6f9bf8dc7f 100644
--- a/examples/solid/start-basic-authjs/package.json
+++ b/examples/solid/start-basic-authjs/package.json
@@ -11,9 +11,9 @@
},
"dependencies": {
"@auth/core": "^0.41.1",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"solid-js": "^1.9.10",
"start-authjs": "^1.0.0",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-basic-cloudflare/package.json b/examples/solid/start-basic-cloudflare/package.json
index b186dd9d6e..a5effede65 100644
--- a/examples/solid/start-basic-cloudflare/package.json
+++ b/examples/solid/start-basic-cloudflare/package.json
@@ -12,9 +12,9 @@
"postinstall": "WRANGLER_LOG=warn npm run --silent cf-typegen"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"solid-js": "^1.9.10"
},
"devDependencies": {
diff --git a/examples/solid/start-basic-netlify/package.json b/examples/solid/start-basic-netlify/package.json
index 302e1ca086..64f1b48d11 100644
--- a/examples/solid/start-basic-netlify/package.json
+++ b/examples/solid/start-basic-netlify/package.json
@@ -9,9 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"solid-js": "^1.9.10"
},
"devDependencies": {
diff --git a/examples/solid/start-basic-nitro/package.json b/examples/solid/start-basic-nitro/package.json
index 923396e37a..c3f4d06cbe 100644
--- a/examples/solid/start-basic-nitro/package.json
+++ b/examples/solid/start-basic-nitro/package.json
@@ -9,9 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"solid-js": "^1.9.10"
},
"devDependencies": {
diff --git a/examples/solid/start-basic-solid-query/package.json b/examples/solid/start-basic-solid-query/package.json
index 5fc9f4dd82..20855e3faa 100644
--- a/examples/solid/start-basic-solid-query/package.json
+++ b/examples/solid/start-basic-solid-query/package.json
@@ -12,10 +12,10 @@
"dependencies": {
"@tanstack/solid-query": "^5.90.9",
"@tanstack/solid-query-devtools": "^5.90.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/solid-router-ssr-query": "^1.167.1",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-basic-static/package.json b/examples/solid/start-basic-static/package.json
index e5bb50d54a..efd5a53347 100644
--- a/examples/solid/start-basic-static/package.json
+++ b/examples/solid/start-basic-static/package.json
@@ -10,10 +10,10 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
- "@tanstack/start-static-server-functions": "^1.167.17",
+ "@tanstack/solid-start": "^1.168.27",
+ "@tanstack/start-static-server-functions": "^1.167.18",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-basic/package.json b/examples/solid/start-basic/package.json
index 49bd5d5fe9..8ad81cb612 100644
--- a/examples/solid/start-basic/package.json
+++ b/examples/solid/start-basic/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0"
diff --git a/examples/solid/start-bun/package.json b/examples/solid/start-bun/package.json
index dc944ee3b1..f82dff17e6 100644
--- a/examples/solid/start-bun/package.json
+++ b/examples/solid/start-bun/package.json
@@ -15,11 +15,11 @@
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/solid-devtools": "^0.7.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"@tanstack/solid-router-ssr-query": "^1.167.1",
- "@tanstack/solid-start": "^1.168.26",
- "@tanstack/router-plugin": "^1.168.18",
+ "@tanstack/solid-start": "^1.168.27",
+ "@tanstack/router-plugin": "^1.168.19",
"solid-js": "^1.9.10",
"tailwindcss": "^4.2.2"
},
diff --git a/examples/solid/start-convex-better-auth/package.json b/examples/solid/start-convex-better-auth/package.json
index 1a787178ed..cc288155e6 100644
--- a/examples/solid/start-convex-better-auth/package.json
+++ b/examples/solid/start-convex-better-auth/package.json
@@ -13,9 +13,9 @@
"dependencies": {
"@convex-dev/better-auth": "^0.9.7",
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"better-auth": "^1.6.19",
"clsx": "^2.1.1",
"convex": "^1.28.2",
diff --git a/examples/solid/start-counter/package.json b/examples/solid/start-counter/package.json
index 6eaf05c9a1..8e4042ce00 100644
--- a/examples/solid/start-counter/package.json
+++ b/examples/solid/start-counter/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0",
diff --git a/examples/solid/start-i18n-paraglide/package.json b/examples/solid/start-i18n-paraglide/package.json
index 05a5e59bb6..8ca4adebce 100644
--- a/examples/solid/start-i18n-paraglide/package.json
+++ b/examples/solid/start-i18n-paraglide/package.json
@@ -10,9 +10,9 @@
},
"dependencies": {
"@tanstack/solid-devtools": "^0.7.0",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"solid-js": "^1.9.10"
},
"devDependencies": {
diff --git a/examples/solid/start-large/package.json b/examples/solid/start-large/package.json
index ccceb6045b..b14f2ae995 100644
--- a/examples/solid/start-large/package.json
+++ b/examples/solid/start-large/package.json
@@ -13,9 +13,9 @@
},
"dependencies": {
"@tanstack/solid-query": "^5.90.9",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0",
diff --git a/examples/solid/start-streaming-data-from-server-functions/package.json b/examples/solid/start-streaming-data-from-server-functions/package.json
index 6b62a70528..60ab77080e 100644
--- a/examples/solid/start-streaming-data-from-server-functions/package.json
+++ b/examples/solid/start-streaming-data-from-server-functions/package.json
@@ -10,9 +10,9 @@
"start": "pnpx srvx --prod -s ../client dist/server/server.js"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"solid-js": "^1.9.10",
"zod": "^4.4.3"
},
diff --git a/examples/solid/start-supabase-basic/package.json b/examples/solid/start-supabase-basic/package.json
index 5dc8d866e8..604b32cdd7 100644
--- a/examples/solid/start-supabase-basic/package.json
+++ b/examples/solid/start-supabase-basic/package.json
@@ -14,9 +14,9 @@
"dependencies": {
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.48.1",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"solid-js": "^1.9.9",
"redaxios": "^0.5.1"
},
diff --git a/examples/solid/start-tailwind-v4/package.json b/examples/solid/start-tailwind-v4/package.json
index 52caa94c8f..d53d59325c 100644
--- a/examples/solid/start-tailwind-v4/package.json
+++ b/examples/solid/start-tailwind-v4/package.json
@@ -10,9 +10,9 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
- "@tanstack/solid-start": "^1.168.26",
+ "@tanstack/solid-start": "^1.168.27",
"solid-js": "^1.9.10",
"tailwind-merge": "^3.6.0",
"zod": "^4.4.3"
diff --git a/examples/solid/view-transitions/package.json b/examples/solid/view-transitions/package.json
index c2cd643fd7..e3a702f5d5 100644
--- a/examples/solid/view-transitions/package.json
+++ b/examples/solid/view-transitions/package.json
@@ -10,8 +10,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/with-framer-motion/package.json b/examples/solid/with-framer-motion/package.json
index d0af6cf9bb..6c16858a6d 100644
--- a/examples/solid/with-framer-motion/package.json
+++ b/examples/solid/with-framer-motion/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"solid-js": "^1.9.10",
diff --git a/examples/solid/with-trpc/package.json b/examples/solid/with-trpc/package.json
index fa9dfaea02..3166804ece 100644
--- a/examples/solid/with-trpc/package.json
+++ b/examples/solid/with-trpc/package.json
@@ -11,8 +11,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/solid-router": "^1.170.16",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/solid-router": "^1.170.17",
"@tanstack/solid-router-devtools": "^1.167.0",
"@trpc/client": "^11.4.3",
"@trpc/server": "^11.4.3",
diff --git a/examples/vue/basic-file-based-jsx/package.json b/examples/vue/basic-file-based-jsx/package.json
index 36354edaec..7573628b17 100644
--- a/examples/vue/basic-file-based-jsx/package.json
+++ b/examples/vue/basic-file-based-jsx/package.json
@@ -10,8 +10,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/vue-router": "^1.170.15",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/vue-router": "^1.170.16",
"@tanstack/vue-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"tailwindcss": "^4.2.2",
diff --git a/examples/vue/basic-file-based-sfc/package.json b/examples/vue/basic-file-based-sfc/package.json
index 9a46ecc019..91581cc30d 100644
--- a/examples/vue/basic-file-based-sfc/package.json
+++ b/examples/vue/basic-file-based-sfc/package.json
@@ -10,8 +10,8 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
- "@tanstack/router-plugin": "^1.168.18",
- "@tanstack/vue-router": "^1.170.15",
+ "@tanstack/router-plugin": "^1.168.19",
+ "@tanstack/vue-router": "^1.170.16",
"@tanstack/vue-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"tailwindcss": "^4.2.2",
diff --git a/examples/vue/basic/package.json b/examples/vue/basic/package.json
index bfbd3f00bb..046be6614d 100644
--- a/examples/vue/basic/package.json
+++ b/examples/vue/basic/package.json
@@ -9,7 +9,7 @@
"start": "vite"
},
"dependencies": {
- "@tanstack/vue-router": "^1.170.15",
+ "@tanstack/vue-router": "^1.170.16",
"@tanstack/vue-router-devtools": "^1.167.0",
"redaxios": "^0.5.1",
"vue": "^3.5.13",
diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md
index 0e2cdc0de4..dc138ae4ed 100644
--- a/packages/react-router/CHANGELOG.md
+++ b/packages/react-router/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/react-router
+## 1.170.17
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+
## 1.170.16
### Patch Changes
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index ff94646687..2e63f8d973 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-router",
- "version": "1.170.16",
+ "version": "1.170.17",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/react-start-client/CHANGELOG.md b/packages/react-start-client/CHANGELOG.md
index b7fa5058dc..a3dd36024e 100644
--- a/packages/react-start-client/CHANGELOG.md
+++ b/packages/react-start-client/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/react-start-client
+## 1.168.15
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b), [`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/react-router@1.170.17
+
## 1.168.14
### Patch Changes
diff --git a/packages/react-start-client/package.json b/packages/react-start-client/package.json
index 888a9cc609..634c565af9 100644
--- a/packages/react-start-client/package.json
+++ b/packages/react-start-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-start-client",
- "version": "1.168.14",
+ "version": "1.168.15",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/react-start-rsc/CHANGELOG.md b/packages/react-start-rsc/CHANGELOG.md
index 70b1c785aa..69627dbebb 100644
--- a/packages/react-start-rsc/CHANGELOG.md
+++ b/packages/react-start-rsc/CHANGELOG.md
@@ -1,5 +1,17 @@
# @tanstack/react-start-rsc
+## 0.1.26
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b), [`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/react-router@1.170.17
+ - @tanstack/start-plugin-core@1.171.19
+ - @tanstack/start-server-core@1.169.16
+ - @tanstack/start-storage-context@1.167.16
+
## 0.1.25
### Patch Changes
diff --git a/packages/react-start-rsc/package.json b/packages/react-start-rsc/package.json
index ccaa3a1195..498a5aff80 100644
--- a/packages/react-start-rsc/package.json
+++ b/packages/react-start-rsc/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-start-rsc",
- "version": "0.1.25",
+ "version": "0.1.26",
"description": "React Server Components support for TanStack Start",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/react-start-server/CHANGELOG.md b/packages/react-start-server/CHANGELOG.md
index e78748be9b..a56f3cc481 100644
--- a/packages/react-start-server/CHANGELOG.md
+++ b/packages/react-start-server/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/react-start-server
+## 1.167.21
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/react-router@1.170.17
+ - @tanstack/start-server-core@1.169.16
+
## 1.167.20
### Patch Changes
diff --git a/packages/react-start-server/package.json b/packages/react-start-server/package.json
index 54b1b5759a..62c02c7268 100644
--- a/packages/react-start-server/package.json
+++ b/packages/react-start-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-start-server",
- "version": "1.167.20",
+ "version": "1.167.21",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/react-start/CHANGELOG.md b/packages/react-start/CHANGELOG.md
index 42ea16093d..90a0ad039f 100644
--- a/packages/react-start/CHANGELOG.md
+++ b/packages/react-start/CHANGELOG.md
@@ -1,5 +1,18 @@
# @tanstack/react-start
+## 1.168.27
+
+### Patch Changes
+
+- Updated dependencies [[`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/react-router@1.170.17
+ - @tanstack/react-start-client@1.168.15
+ - @tanstack/react-start-rsc@0.1.26
+ - @tanstack/react-start-server@1.167.21
+ - @tanstack/start-plugin-core@1.171.19
+ - @tanstack/start-server-core@1.169.16
+
## 1.168.26
### Patch Changes
diff --git a/packages/react-start/package.json b/packages/react-start/package.json
index b037e44f8f..c015ecfcf2 100644
--- a/packages/react-start/package.json
+++ b/packages/react-start/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/react-start",
- "version": "1.168.26",
+ "version": "1.168.27",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/router-cli/CHANGELOG.md b/packages/router-cli/CHANGELOG.md
index c1f9188e12..7895121dbd 100644
--- a/packages/router-cli/CHANGELOG.md
+++ b/packages/router-cli/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/router-cli
+## 1.167.18
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @tanstack/router-generator@1.167.18
+
## 1.167.17
### Patch Changes
diff --git a/packages/router-cli/package.json b/packages/router-cli/package.json
index 971aa6c629..50aa6f8d4b 100644
--- a/packages/router-cli/package.json
+++ b/packages/router-cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/router-cli",
- "version": "1.167.17",
+ "version": "1.167.18",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/router-core/CHANGELOG.md b/packages/router-core/CHANGELOG.md
index 45bd316edf..a0082099fa 100644
--- a/packages/router-core/CHANGELOG.md
+++ b/packages/router-core/CHANGELOG.md
@@ -1,5 +1,15 @@
# @tanstack/router-core
+## 1.171.14
+
+### Patch Changes
+
+- [#7695](https://github.com/TanStack/router/pull/7695) [`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b) - fix(router-core): re-encode URL-unsafe characters in `sanitizePathSegment` to prevent infinite redirect loops
+
+ `sanitizePathSegment` now re-encodes characters in the WHATWG URL "path percent-encode set" (`<`, `>`, `"`, `` ` ``, `{`, `}`) and ASCII control characters back to their percent-encoded form, instead of stripping control characters. This prevents mismatches between the original URL and the router's internal representation that previously caused infinite 307 redirect loops on paths containing these characters (e.g. `/%7B%7Btemplate%7D%7D`).
+
+ Fixes [#7587](https://github.com/TanStack/router/issues/7587).
+
## 1.171.13
### Patch Changes
diff --git a/packages/router-core/package.json b/packages/router-core/package.json
index a1ca664a70..303e7eca24 100644
--- a/packages/router-core/package.json
+++ b/packages/router-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/router-core",
- "version": "1.171.13",
+ "version": "1.171.14",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/router-generator/CHANGELOG.md b/packages/router-generator/CHANGELOG.md
index 572760d02b..cf16585672 100644
--- a/packages/router-generator/CHANGELOG.md
+++ b/packages/router-generator/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/router-generator
+## 1.167.18
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+
## 1.167.17
### Patch Changes
diff --git a/packages/router-generator/package.json b/packages/router-generator/package.json
index 466b250ab9..8abf6f6d0b 100644
--- a/packages/router-generator/package.json
+++ b/packages/router-generator/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/router-generator",
- "version": "1.167.17",
+ "version": "1.167.18",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/router-plugin/CHANGELOG.md b/packages/router-plugin/CHANGELOG.md
index ef868d8194..50dca919fa 100644
--- a/packages/router-plugin/CHANGELOG.md
+++ b/packages/router-plugin/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/router-plugin
+## 1.168.19
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/react-router@1.170.17
+ - @tanstack/router-generator@1.167.18
+
## 1.168.18
### Patch Changes
diff --git a/packages/router-plugin/package.json b/packages/router-plugin/package.json
index fa55dbe02c..117ef4bb14 100644
--- a/packages/router-plugin/package.json
+++ b/packages/router-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/router-plugin",
- "version": "1.168.18",
+ "version": "1.168.19",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/router-vite-plugin/CHANGELOG.md b/packages/router-vite-plugin/CHANGELOG.md
index 1540a5e991..dbd469a8d2 100644
--- a/packages/router-vite-plugin/CHANGELOG.md
+++ b/packages/router-vite-plugin/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/router-vite-plugin
+## 1.167.19
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @tanstack/router-plugin@1.168.19
+
## 1.167.18
### Patch Changes
diff --git a/packages/router-vite-plugin/package.json b/packages/router-vite-plugin/package.json
index bd3ef58ac6..aed06c1a13 100644
--- a/packages/router-vite-plugin/package.json
+++ b/packages/router-vite-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/router-vite-plugin",
- "version": "1.167.18",
+ "version": "1.167.19",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/solid-router/CHANGELOG.md b/packages/solid-router/CHANGELOG.md
index 23b61a6dd7..e916479ced 100644
--- a/packages/solid-router/CHANGELOG.md
+++ b/packages/solid-router/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/solid-router
+## 1.170.17
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+
## 1.170.16
### Patch Changes
diff --git a/packages/solid-router/package.json b/packages/solid-router/package.json
index 6b8e7a2949..1561e3eb4c 100644
--- a/packages/solid-router/package.json
+++ b/packages/solid-router/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/solid-router",
- "version": "1.170.16",
+ "version": "1.170.17",
"description": "Modern and scalable routing for Solid applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/solid-start-client/CHANGELOG.md b/packages/solid-start-client/CHANGELOG.md
index 3b9ebc20ce..b06ca0c723 100644
--- a/packages/solid-start-client/CHANGELOG.md
+++ b/packages/solid-start-client/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/solid-start-client
+## 1.168.15
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b), [`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/solid-router@1.170.17
+
## 1.168.14
### Patch Changes
diff --git a/packages/solid-start-client/package.json b/packages/solid-start-client/package.json
index e043dba060..10f1c33064 100644
--- a/packages/solid-start-client/package.json
+++ b/packages/solid-start-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/solid-start-client",
- "version": "1.168.14",
+ "version": "1.168.15",
"description": "Modern and scalable routing for Solid applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/solid-start-server/CHANGELOG.md b/packages/solid-start-server/CHANGELOG.md
index 433045af39..fe7eb3ce66 100644
--- a/packages/solid-start-server/CHANGELOG.md
+++ b/packages/solid-start-server/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/solid-start-server
+## 1.167.21
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/solid-router@1.170.17
+ - @tanstack/start-server-core@1.169.16
+
## 1.167.20
### Patch Changes
diff --git a/packages/solid-start-server/package.json b/packages/solid-start-server/package.json
index 8d2ed4817a..79f77b1a1b 100644
--- a/packages/solid-start-server/package.json
+++ b/packages/solid-start-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/solid-start-server",
- "version": "1.167.20",
+ "version": "1.167.21",
"description": "Modern and scalable routing for Solid applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/solid-start/CHANGELOG.md b/packages/solid-start/CHANGELOG.md
index ff2ba4779a..810d640920 100644
--- a/packages/solid-start/CHANGELOG.md
+++ b/packages/solid-start/CHANGELOG.md
@@ -1,5 +1,17 @@
# @tanstack/solid-start
+## 1.168.27
+
+### Patch Changes
+
+- Updated dependencies [[`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/solid-router@1.170.17
+ - @tanstack/solid-start-client@1.168.15
+ - @tanstack/solid-start-server@1.167.21
+ - @tanstack/start-plugin-core@1.171.19
+ - @tanstack/start-server-core@1.169.16
+
## 1.168.26
### Patch Changes
diff --git a/packages/solid-start/package.json b/packages/solid-start/package.json
index 4b11e9a219..ddbbf925ed 100644
--- a/packages/solid-start/package.json
+++ b/packages/solid-start/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/solid-start",
- "version": "1.168.26",
+ "version": "1.168.27",
"description": "Modern and scalable routing for Solid applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/start-client-core/CHANGELOG.md b/packages/start-client-core/CHANGELOG.md
index 7d5b73b16b..29696bad29 100644
--- a/packages/start-client-core/CHANGELOG.md
+++ b/packages/start-client-core/CHANGELOG.md
@@ -1,5 +1,15 @@
# @tanstack/start-client-core
+## 1.170.13
+
+### Patch Changes
+
+- [#7662](https://github.com/TanStack/router/pull/7662) [`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c) - perf: zero-copy frame payload extraction in the client frame decoder. When a frame's bytes are fully contained in the first buffered chunk (the common case), return a subarray view instead of allocating a new buffer and copying. This is on the hot path for decoding streamed server-function responses and `RawStream` payloads.
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/start-storage-context@1.167.16
+
## 1.170.12
### Patch Changes
diff --git a/packages/start-client-core/package.json b/packages/start-client-core/package.json
index edbff306fa..e999c97c80 100644
--- a/packages/start-client-core/package.json
+++ b/packages/start-client-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/start-client-core",
- "version": "1.170.12",
+ "version": "1.170.13",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/start-plugin-core/CHANGELOG.md b/packages/start-plugin-core/CHANGELOG.md
index a611c1f2d2..a25d8fc634 100644
--- a/packages/start-plugin-core/CHANGELOG.md
+++ b/packages/start-plugin-core/CHANGELOG.md
@@ -1,5 +1,15 @@
# @tanstack/start-plugin-core
+## 1.171.19
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/router-generator@1.167.18
+ - @tanstack/router-plugin@1.168.19
+ - @tanstack/start-server-core@1.169.16
+
## 1.171.18
### Patch Changes
diff --git a/packages/start-plugin-core/package.json b/packages/start-plugin-core/package.json
index b0f1c1e09d..1b60d95f05 100644
--- a/packages/start-plugin-core/package.json
+++ b/packages/start-plugin-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/start-plugin-core",
- "version": "1.171.18",
+ "version": "1.171.19",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/start-server-core/CHANGELOG.md b/packages/start-server-core/CHANGELOG.md
index 2d66cd9533..a07a1b30b7 100644
--- a/packages/start-server-core/CHANGELOG.md
+++ b/packages/start-server-core/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/start-server-core
+## 1.169.16
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b), [`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/start-storage-context@1.167.16
+
## 1.169.15
### Patch Changes
diff --git a/packages/start-server-core/package.json b/packages/start-server-core/package.json
index ce25f2ad3e..1bbb1010a7 100644
--- a/packages/start-server-core/package.json
+++ b/packages/start-server-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/start-server-core",
- "version": "1.169.15",
+ "version": "1.169.16",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/start-static-server-functions/CHANGELOG.md b/packages/start-static-server-functions/CHANGELOG.md
index b157c195d0..1191099aa4 100644
--- a/packages/start-static-server-functions/CHANGELOG.md
+++ b/packages/start-static-server-functions/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/start-static-server-functions
+## 1.167.18
+
+### Patch Changes
+
+- Updated dependencies [[`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/react-start@1.168.27
+ - @tanstack/solid-start@1.168.27
+
## 1.167.17
### Patch Changes
diff --git a/packages/start-static-server-functions/package.json b/packages/start-static-server-functions/package.json
index ffcd931ae9..61196bffdf 100644
--- a/packages/start-static-server-functions/package.json
+++ b/packages/start-static-server-functions/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/start-static-server-functions",
- "version": "1.167.17",
+ "version": "1.167.18",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/start-storage-context/CHANGELOG.md b/packages/start-storage-context/CHANGELOG.md
index 4d5abfdf0d..0d28073b4f 100644
--- a/packages/start-storage-context/CHANGELOG.md
+++ b/packages/start-storage-context/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/start-storage-context
+## 1.167.16
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+
## 1.167.15
### Patch Changes
diff --git a/packages/start-storage-context/package.json b/packages/start-storage-context/package.json
index 7d0e7e4334..0fa3359847 100644
--- a/packages/start-storage-context/package.json
+++ b/packages/start-storage-context/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/start-storage-context",
- "version": "1.167.15",
+ "version": "1.167.16",
"description": "Modern and scalable routing for React applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md
index 339496707b..6ae02b6762 100644
--- a/packages/vue-router/CHANGELOG.md
+++ b/packages/vue-router/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tanstack/vue-router
+## 1.170.16
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+
## 1.170.15
### Patch Changes
diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json
index 9e429bb0b1..c302ba4600 100644
--- a/packages/vue-router/package.json
+++ b/packages/vue-router/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/vue-router",
- "version": "1.170.15",
+ "version": "1.170.16",
"description": "Modern and scalable routing for Vue applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/vue-start-client/CHANGELOG.md b/packages/vue-start-client/CHANGELOG.md
index ec7755565f..54e1239b1f 100644
--- a/packages/vue-start-client/CHANGELOG.md
+++ b/packages/vue-start-client/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/vue-start-client
+## 1.167.18
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b), [`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/vue-router@1.170.16
+
## 1.167.17
### Patch Changes
diff --git a/packages/vue-start-client/package.json b/packages/vue-start-client/package.json
index f8e2bb2ec1..f191cb60fc 100644
--- a/packages/vue-start-client/package.json
+++ b/packages/vue-start-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/vue-start-client",
- "version": "1.167.17",
+ "version": "1.167.18",
"description": "Modern and scalable routing for Vue applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/vue-start-server/CHANGELOG.md b/packages/vue-start-server/CHANGELOG.md
index 660c89abcb..379dfdda9e 100644
--- a/packages/vue-start-server/CHANGELOG.md
+++ b/packages/vue-start-server/CHANGELOG.md
@@ -1,5 +1,14 @@
# @tanstack/vue-start-server
+## 1.167.21
+
+### Patch Changes
+
+- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]:
+ - @tanstack/router-core@1.171.14
+ - @tanstack/start-server-core@1.169.16
+ - @tanstack/vue-router@1.170.16
+
## 1.167.20
### Patch Changes
diff --git a/packages/vue-start-server/package.json b/packages/vue-start-server/package.json
index 524e54b994..bf704e93ea 100644
--- a/packages/vue-start-server/package.json
+++ b/packages/vue-start-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/vue-start-server",
- "version": "1.167.20",
+ "version": "1.167.21",
"description": "Modern and scalable routing for Vue applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/packages/vue-start/CHANGELOG.md b/packages/vue-start/CHANGELOG.md
index 9ea76e4663..1322cce2bf 100644
--- a/packages/vue-start/CHANGELOG.md
+++ b/packages/vue-start/CHANGELOG.md
@@ -1,5 +1,17 @@
# @tanstack/vue-start
+## 1.168.26
+
+### Patch Changes
+
+- Updated dependencies [[`ba52d2b`](https://github.com/TanStack/router/commit/ba52d2b8f9e95d6becd1be14c020b35f3231e02c)]:
+ - @tanstack/start-client-core@1.170.13
+ - @tanstack/start-plugin-core@1.171.19
+ - @tanstack/start-server-core@1.169.16
+ - @tanstack/vue-router@1.170.16
+ - @tanstack/vue-start-client@1.167.18
+ - @tanstack/vue-start-server@1.167.21
+
## 1.168.25
### Patch Changes
diff --git a/packages/vue-start/package.json b/packages/vue-start/package.json
index 5afd0d06a5..78e746056f 100644
--- a/packages/vue-start/package.json
+++ b/packages/vue-start/package.json
@@ -1,6 +1,6 @@
{
"name": "@tanstack/vue-start",
- "version": "1.168.25",
+ "version": "1.168.26",
"description": "Modern and scalable routing for Vue applications",
"author": "Tanner Linsley",
"license": "MIT",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7a7c3a7348..62525d6c48 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -78,7 +78,7 @@ importers:
version: 1.26.2(eslint@9.22.0(jiti@2.7.0))(ts-api-utils@2.4.0(typescript@6.0.2))(typescript@6.0.2)
'@nx/devkit':
specifier: 22.7.5
- version: 22.7.5(nx@22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23)))
+ version: 22.7.5(nx@22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23))(debug@4.4.3))
'@playwright/test':
specifier: ^1.57.0
version: 1.58.0
@@ -138,7 +138,7 @@ importers:
version: 4.0.3
nx:
specifier: 22.7.5
- version: 22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23))
+ version: 22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23))(debug@4.4.3)
prettier:
specifier: ^3.8.0
version: 3.8.1
@@ -281,7 +281,7 @@ importers:
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)
+ version: 5.5.0(debug@4.4.3)(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)
'@platformatic/flame':
specifier: ^1.6.0
version: 1.6.0
@@ -342,7 +342,7 @@ importers:
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)
+ version: 5.5.0(debug@4.4.3)(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
@@ -415,7 +415,7 @@ importers:
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)
+ version: 5.5.0(debug@4.4.3)(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
@@ -476,7 +476,7 @@ importers:
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)
+ version: 5.5.0(debug@4.4.3)(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)
'@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))
@@ -9807,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)(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))
+ version: nitro-nightly@4.0.0-20251010-091516-7cafddba(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@4.0.3)(ioredis@5.9.2)(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.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))
tailwindcss:
specifier: ^4.1.18
version: 4.2.2
@@ -10292,7 +10292,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10329,7 +10329,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10369,7 +10369,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10409,7 +10409,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10443,7 +10443,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10474,7 +10474,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10511,7 +10511,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10554,7 +10554,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10594,7 +10594,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10631,7 +10631,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
compression:
specifier: ^1.8.0
@@ -10671,7 +10671,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10726,7 +10726,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10766,7 +10766,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10803,7 +10803,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10840,7 +10840,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
solid-js:
specifier: 1.9.12
@@ -10871,7 +10871,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10908,7 +10908,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -10954,7 +10954,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11000,7 +11000,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11043,7 +11043,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11080,7 +11080,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11114,7 +11114,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11145,7 +11145,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11173,7 +11173,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11204,7 +11204,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11241,7 +11241,7 @@ importers:
specifier: ^4.2.2
version: 4.2.2
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11275,7 +11275,7 @@ importers:
examples/solid/quickstart-webpack-file-based:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11336,7 +11336,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11370,7 +11370,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11410,7 +11410,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11444,7 +11444,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11484,7 +11484,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11530,7 +11530,7 @@ importers:
examples/solid/start-basic:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11582,7 +11582,7 @@ importers:
specifier: ^7.0.0
version: 7.0.0(prisma@7.0.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11631,7 +11631,7 @@ importers:
specifier: ^0.41.1
version: 0.41.1
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11671,7 +11671,7 @@ importers:
examples/solid/start-basic-cloudflare:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11711,7 +11711,7 @@ importers:
examples/solid/start-basic-netlify:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11748,7 +11748,7 @@ importers:
examples/solid/start-basic-nitro:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11791,7 +11791,7 @@ importers:
specifier: ^5.90.0
version: 5.90.4(@tanstack/solid-query@5.99.0(solid-js@1.9.12))(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11834,7 +11834,7 @@ importers:
examples/solid/start-basic-static:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11886,7 +11886,7 @@ importers:
specifier: ^0.7.0
version: 0.7.14(csstype@3.2.3)(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -11950,7 +11950,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12005,7 +12005,7 @@ importers:
examples/solid/start-counter:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12048,7 +12048,7 @@ importers:
specifier: ^0.7.0
version: 0.7.14(csstype@3.2.3)(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12088,7 +12088,7 @@ importers:
specifier: ^5.99.0
version: 5.99.0(solid-js@1.9.12)
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12131,7 +12131,7 @@ importers:
examples/solid/start-streaming-data-from-server-functions:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12168,7 +12168,7 @@ importers:
specifier: ^2.48.1
version: 2.48.1
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12205,7 +12205,7 @@ importers:
examples/solid/start-tailwind-v4:
dependencies:
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12251,7 +12251,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12285,7 +12285,7 @@ importers:
specifier: ^4.2.2
version: 4.2.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))
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -12325,7 +12325,7 @@ importers:
specifier: workspace:*
version: link:../../../packages/router-plugin
'@tanstack/solid-router':
- specifier: ^1.170.16
+ specifier: ^1.170.17
version: link:../../../packages/solid-router
'@tanstack/solid-router-devtools':
specifier: workspace:^
@@ -21793,21 +21793,6 @@ packages:
miniflare:
optional: true
- env-runner@0.1.9:
- resolution: {integrity: sha512-W9AiZlPx0uXtghAJiTBkeZOgyQdecVvoln3cHoOEZswPq0cVMi+WBhUQjdUn+JcZFAFgOt+i5fcO7C2zniZoCg==}
- hasBin: true
- peerDependencies:
- '@netlify/runtime': ^4.1.23
- '@vercel/queue': ^0.2.0
- miniflare: ^4.20260515.0
- peerDependenciesMeta:
- '@netlify/runtime':
- optional: true
- '@vercel/queue':
- optional: true
- miniflare:
- optional: true
-
envinfo@7.14.0:
resolution: {integrity: sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==}
engines: {node: '>=4'}
@@ -22586,18 +22571,17 @@ packages:
crossws:
optional: true
- h3@2.0.1-rc.20:
- resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==}
+ h3@2.0.1-rc.2:
+ resolution: {integrity: sha512-2vS7OETzPDzGQxmmcs6ttu7p0NW25zAdkPXYOr43dn4GZf81uUljJvupa158mcpUGpsQUqIy4O4THWUQT1yVeA==}
engines: {node: '>=20.11.1'}
- hasBin: true
peerDependencies:
crossws: ^0.4.1
peerDependenciesMeta:
crossws:
optional: true
- h3@2.0.1-rc.22:
- resolution: {integrity: sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA==}
+ h3@2.0.1-rc.20:
+ resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==}
engines: {node: '>=20.11.1'}
hasBin: true
peerDependencies:
@@ -22676,9 +22660,6 @@ packages:
hookable@6.1.0:
resolution: {integrity: sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw==}
- hookable@6.1.1:
- resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==}
-
hosted-git-info@7.0.2:
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
engines: {node: ^16.14.0 || >=18.0.0}
@@ -22787,9 +22768,6 @@ packages:
httpxy@0.3.1:
resolution: {integrity: sha512-XjG/CEoofEisMrnFr0D6U6xOZ4mRfnwcYQ9qvvnT4lvnX8BoeA3x3WofB75D+vZwpaobFVkBIHrZzoK40w8XSw==}
- httpxy@0.5.3:
- resolution: {integrity: sha512-SMS9V6Sn7VWaS11lYhoAr0ceoaiolTWf4jYdJn0NJhCdKMu9R2H9Fh0LBDWBHQF6HRLI1PmaePYsjanSpE5PEw==}
-
human-id@4.1.1:
resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==}
hasBin: true
@@ -23875,45 +23853,30 @@ packages:
netlify-redirector@0.5.0:
resolution: {integrity: sha512-4zdzIP+6muqPCuE8avnrgDJ6KW/2+UpHTRcTbMXCIRxiRmyrX+IZ4WSJGZdHPWF3WmQpXpy603XxecZ9iygN7w==}
+ nf3@0.1.12:
+ resolution: {integrity: sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ==}
+
nf3@0.3.11:
resolution: {integrity: sha512-ObKp/SA3f1g1f/OMeDlRWaZmqGgk7A0NnDIbeO7c/MV4r/quMlpP/BsqMGuTi3lUlXbC1On8YH7ICM2u2bIAOw==}
- nf3@0.3.17:
- resolution: {integrity: sha512-N9zEWySuJFw+gR0lhS5863YsvNeudOdqRyFvNb+jMXbeTJOdrjDqkCpDginIZfUm0LzT1t1nCRiDeqQm/8kirQ==}
-
nf3@0.3.6:
resolution: {integrity: sha512-/XRUUILTAyuy1XunyVQuqGp8aEmZ2TfRTn8Rji+FA4xqv20qzL4jV7Reqbuey2XucKgPeRVcEYGScmJM0UnB6Q==}
- nitro-nightly@3.0.260522-beta:
- resolution: {integrity: sha512-Pm0AiQ1nLcreUFZKJcmVI4l9K/ygXoFamIPhc44XJHj9vmt25Rlqjv55frw6sS0L1qHrySpo3xyVVBmR9aTBqA==}
+ nitro-nightly@4.0.0-20251010-091516-7cafddba:
+ resolution: {integrity: sha512-biADkmoR/Nb9OmUURTfDI2BpGo2IMEt2RbsiMhjqdwz2w3tEm+tx0Hd28LXjBCwid+l8jpqyfmpwc8jzwdng3w==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
- '@vercel/queue': ^0.2.0
- dotenv: '*'
- giget: '*'
- jiti: ^2.6.1
- rollup: ^4.60.3
+ rolldown: '*'
vite: ^8.0.14
xml2js: ^0.6.2
- zephyr-agent: ^0.2.0
peerDependenciesMeta:
- '@vercel/queue':
- optional: true
- dotenv:
- optional: true
- giget:
- optional: true
- jiti:
- optional: true
- rollup:
+ rolldown:
optional: true
vite:
optional: true
xml2js:
optional: true
- zephyr-agent:
- optional: true
nitro@3.0.1-alpha.2:
resolution: {integrity: sha512-YviDY5J/trS821qQ1fpJtpXWIdPYiOizC/meHavlm1Hfuhx//H+Egd1+4C5SegJRgtWMnRPW9n//6Woaw81cTQ==}
@@ -24117,9 +24080,6 @@ packages:
ocache@0.1.2:
resolution: {integrity: sha512-lI34wjM7cahEdrq2I5obbF7MEdE97vULf6vNj6ZCzwEadzyXO1w7QOl2qzzG4IL8cyO7wDtXPj9CqW/aG3mn7g==}
- ocache@0.1.4:
- resolution: {integrity: sha512-e7geNdWjxSnvsSgvLuPvgKgu7ubM10ZmTPOgpr7mz2BXYtvjMKTiLhjFi/gWU8chkuP6hNkZBsa9LzOusyaqkQ==}
-
ofetch@1.5.1:
resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
@@ -24923,6 +24883,10 @@ packages:
renderkid@3.0.0:
resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==}
+ rendu@0.0.6:
+ resolution: {integrity: sha512-nZ512Dw0MxKiIYfCVv8DPe6ig4m0Qt3FOYBJEXrammjIYBBPuHaudc0AGfYx+iyOw2q0itAtPywiVZXtTFCsig==}
+ hasBin: true
+
repeat-string@1.6.1:
resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
engines: {node: '>=0.10'}
@@ -25432,6 +25396,11 @@ packages:
engines: {node: '>=20.16.0'}
hasBin: true
+ srvx@0.8.16:
+ resolution: {integrity: sha512-hmcGW4CgroeSmzgF1Ihwgl+Ths0JqAJ7HwjP2X7e3JzY7u4IydLMcdnlqGQiQGUswz+PO9oh/KtCpOISIvs9QQ==}
+ engines: {node: '>=20.16.0'}
+ hasBin: true
+
stable-hash-x@0.2.0:
resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==}
engines: {node: '>=12.0.0'}
@@ -25843,6 +25812,7 @@ packages:
tsconfck@3.1.4:
resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==}
engines: {node: ^18 || >=20}
+ deprecated: unmaintained
hasBin: true
peerDependencies:
typescript: ^5.0.0
@@ -26005,6 +25975,9 @@ packages:
resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==}
engines: {node: '>=20.18.1'}
+ unenv@2.0.0-rc.21:
+ resolution: {integrity: sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==}
+
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
@@ -26120,32 +26093,32 @@ packages:
uploadthing:
optional: true
- unstorage@2.0.0-alpha.5:
- resolution: {integrity: sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g==}
+ unstorage@2.0.0-alpha.3:
+ resolution: {integrity: sha512-BeoqISVh8jxqnPseHH7/92twe2VkQztrudXg8RFZVbXb4ckkFdpLk1LnNvsUndDltyodBMVxgI6V7JcbJYt2VQ==}
peerDependencies:
- '@azure/app-configuration': ^1.9.0
- '@azure/cosmos': ^4.7.0
- '@azure/data-tables': ^13.3.1
- '@azure/identity': ^4.13.0
- '@azure/keyvault-secrets': ^4.10.0
- '@azure/storage-blob': ^12.29.1
+ '@azure/app-configuration': ^1.8.0
+ '@azure/cosmos': ^4.2.0
+ '@azure/data-tables': ^13.3.0
+ '@azure/identity': ^4.6.0
+ '@azure/keyvault-secrets': ^4.9.0
+ '@azure/storage-blob': ^12.26.0
'@capacitor/preferences': ^6.0.3 || ^7.0.0
- '@deno/kv': '>=0.12.0'
+ '@deno/kv': '>=0.9.0'
'@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0
'@planetscale/database': ^1.19.0
- '@upstash/redis': ^1.35.6
- '@vercel/blob': '>=0.27.3'
+ '@upstash/redis': ^1.34.3
+ '@vercel/blob': '>=0.27.1'
'@vercel/functions': ^2.2.12 || ^3.0.0
'@vercel/kv': ^1.0.1
aws4fetch: ^1.0.20
- chokidar: ^4 || ^5
- db0: '>=0.3.4'
- idb-keyval: ^6.2.2
- ioredis: ^5.8.2
+ chokidar: ^4.0.3
+ db0: '>=0.2.1'
+ idb-keyval: ^6.2.1
+ ioredis: ^5.4.2
lru-cache: ^11.2.2
- mongodb: ^6 || ^7
- ofetch: '*'
- uploadthing: ^7.7.4
+ mongodb: ^6.20.0
+ ofetch: ^1.4.1
+ uploadthing: ^7.4.4
peerDependenciesMeta:
'@azure/app-configuration':
optional: true
@@ -26194,20 +26167,20 @@ packages:
uploadthing:
optional: true
- unstorage@2.0.0-alpha.6:
- resolution: {integrity: sha512-w5vLYCJtnSx3OBtDk7cG4c1p3dfAnHA4WSZq9Xsurjbl2wMj7zqfOIjaHQI1Bl7yKzUxXAi+kbMr8iO2RhJmBA==}
+ unstorage@2.0.0-alpha.5:
+ resolution: {integrity: sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g==}
peerDependencies:
- '@azure/app-configuration': ^1.11.0
- '@azure/cosmos': ^4.9.1
- '@azure/data-tables': ^13.3.2
+ '@azure/app-configuration': ^1.9.0
+ '@azure/cosmos': ^4.7.0
+ '@azure/data-tables': ^13.3.1
'@azure/identity': ^4.13.0
'@azure/keyvault-secrets': ^4.10.0
- '@azure/storage-blob': ^12.31.0
- '@capacitor/preferences': ^6 || ^7 || ^8
- '@deno/kv': '>=0.13.0'
+ '@azure/storage-blob': ^12.29.1
+ '@capacitor/preferences': ^6.0.3 || ^7.0.0
+ '@deno/kv': '>=0.12.0'
'@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0
'@planetscale/database': ^1.19.0
- '@upstash/redis': ^1.36.2
+ '@upstash/redis': ^1.35.6
'@vercel/blob': '>=0.27.3'
'@vercel/functions': ^2.2.12 || ^3.0.0
'@vercel/kv': ^1.0.1
@@ -26215,8 +26188,8 @@ packages:
chokidar: ^4 || ^5
db0: '>=0.3.4'
idb-keyval: ^6.2.2
- ioredis: ^5.9.3
- lru-cache: ^11.2.6
+ ioredis: ^5.8.2
+ lru-cache: ^11.2.2
mongodb: ^6 || ^7
ofetch: '*'
uploadthing: ^7.7.4
@@ -26268,8 +26241,8 @@ packages:
uploadthing:
optional: true
- unstorage@2.0.0-alpha.7:
- resolution: {integrity: sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog==}
+ unstorage@2.0.0-alpha.6:
+ resolution: {integrity: sha512-w5vLYCJtnSx3OBtDk7cG4c1p3dfAnHA4WSZq9Xsurjbl2wMj7zqfOIjaHQI1Bl7yKzUxXAi+kbMr8iO2RhJmBA==}
peerDependencies:
'@azure/app-configuration': ^1.11.0
'@azure/cosmos': ^4.9.1
@@ -27700,7 +27673,7 @@ snapshots:
'@bramus/specificity@2.4.2':
dependencies:
- css-tree: 3.1.0
+ css-tree: 3.2.1
'@bundled-es-modules/cookie@2.0.1':
dependencies:
@@ -27708,7 +27681,7 @@ snapshots:
'@bundled-es-modules/statuses@1.0.1':
dependencies:
- statuses: 2.0.1
+ statuses: 2.0.2
'@bundled-es-modules/tough-cookie@0.1.6':
dependencies:
@@ -28019,9 +27992,9 @@ snapshots:
'@cloudflare/workerd-windows-64@1.20260317.1':
optional: true
- '@codspeed/core@5.5.0':
+ '@codspeed/core@5.5.0(debug@4.4.3)':
dependencies:
- axios: 1.17.0
+ axios: 1.17.0(debug@4.4.3)
find-up: 6.3.0
form-data: 4.0.5
node-gyp-build: 4.8.4
@@ -28030,9 +28003,9 @@ snapshots:
- debug
- supports-color
- '@codspeed/vitest-plugin@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)':
+ '@codspeed/vitest-plugin@5.5.0(debug@4.4.3)(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)':
dependencies:
- '@codspeed/core': 5.5.0
+ '@codspeed/core': 5.5.0(debug@4.4.3)
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@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))
@@ -30236,13 +30209,13 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.0
- '@nx/devkit@22.7.5(nx@22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23)))':
+ '@nx/devkit@22.7.5(nx@22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23))(debug@4.4.3))':
dependencies:
'@zkochan/js-yaml': 0.0.7
ejs: 5.0.1
enquirer: 2.3.6
minimatch: 10.2.5
- nx: 22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23))
+ nx: 22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23))(debug@4.4.3)
semver: 7.8.2
tslib: 2.8.1
yargs-parser: 21.1.1
@@ -33551,7 +33524,7 @@ snapshots:
debug: 4.4.3
minimatch: 10.2.5
semver: 7.8.2
- tinyglobby: 0.2.15
+ tinyglobby: 0.2.16
ts-api-utils: 2.4.0(typescript@6.0.2)
typescript: 6.0.2
transitivePeerDependencies:
@@ -34050,7 +34023,7 @@ snapshots:
'@vue/compiler-core@3.5.25':
dependencies:
- '@babel/parser': 7.28.5
+ '@babel/parser': 7.29.2
'@vue/shared': 3.5.25
entities: 4.5.0
estree-walker: 2.0.2
@@ -34063,14 +34036,14 @@ snapshots:
'@vue/compiler-sfc@3.5.25':
dependencies:
- '@babel/parser': 7.28.5
+ '@babel/parser': 7.29.2
'@vue/compiler-core': 3.5.25
'@vue/compiler-dom': 3.5.25
'@vue/compiler-ssr': 3.5.25
'@vue/shared': 3.5.25
estree-walker: 2.0.2
magic-string: 0.30.21
- postcss: 8.5.8
+ postcss: 8.5.15
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.25':
@@ -34597,7 +34570,7 @@ snapshots:
aws-ssl-profiles@1.1.2: {}
- axios@1.16.0:
+ axios@1.16.0(debug@4.4.3):
dependencies:
follow-redirects: 1.16.0(debug@4.4.3)
form-data: 4.0.5
@@ -34605,7 +34578,7 @@ snapshots:
transitivePeerDependencies:
- debug
- axios@1.17.0:
+ axios@1.17.0(debug@4.4.3):
dependencies:
follow-redirects: 1.16.0(debug@4.4.3)
form-data: 4.0.5
@@ -35366,6 +35339,11 @@ snapshots:
crossws@0.4.5(srvx@0.11.15):
optionalDependencies:
srvx: 0.11.15
+ optional: true
+
+ crossws@0.4.5(srvx@0.8.16):
+ optionalDependencies:
+ srvx: 0.8.16
css-loader@7.1.2(webpack@5.97.1):
dependencies:
@@ -35826,13 +35804,6 @@ snapshots:
optionalDependencies:
miniflare: 4.20260317.0
- env-runner@0.1.9:
- dependencies:
- crossws: 0.4.5(srvx@0.11.15)
- exsolve: 1.0.8
- httpxy: 0.5.3
- srvx: 0.11.15
-
envinfo@7.14.0: {}
environment@1.1.0: {}
@@ -36946,14 +36917,16 @@ snapshots:
optionalDependencies:
crossws: 0.4.4(srvx@0.11.15)
- h3@2.0.1-rc.20(crossws@0.4.5(srvx@0.11.15)):
+ h3@2.0.1-rc.2(crossws@0.4.5(srvx@0.8.16)):
dependencies:
- rou3: 0.8.1
- srvx: 0.11.15
+ cookie-es: 2.0.0
+ fetchdts: 0.1.7
+ rou3: 0.7.12
+ srvx: 0.8.16
optionalDependencies:
- crossws: 0.4.5(srvx@0.11.15)
+ crossws: 0.4.5(srvx@0.8.16)
- h3@2.0.1-rc.22(crossws@0.4.5(srvx@0.11.15)):
+ h3@2.0.1-rc.20(crossws@0.4.5(srvx@0.11.15)):
dependencies:
rou3: 0.8.1
srvx: 0.11.15
@@ -37014,8 +36987,6 @@ snapshots:
hookable@6.1.0: {}
- hookable@6.1.1: {}
-
hosted-git-info@7.0.2:
dependencies:
lru-cache: 10.4.3
@@ -37166,8 +37137,6 @@ snapshots:
httpxy@0.3.1: {}
- httpxy@0.5.3: {}
-
human-id@4.1.1: {}
human-signals@5.0.0: {}
@@ -37281,7 +37250,7 @@ snapshots:
pathe: 2.0.3
sharp: 0.34.4
svgo: 4.0.0
- ufo: 1.6.1
+ ufo: 1.6.3
unstorage: 1.17.4(@netlify/blobs@10.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2)
xss: 1.0.15
transitivePeerDependencies:
@@ -38241,32 +38210,33 @@ snapshots:
netlify-redirector@0.5.0: {}
- nf3@0.3.11: {}
+ nf3@0.1.12: {}
- nf3@0.3.17: {}
+ nf3@0.3.11: {}
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)(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)):
+ nitro-nightly@4.0.0-20251010-091516-7cafddba(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@4.0.3)(ioredis@5.9.2)(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.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:
consola: 3.4.2
- crossws: 0.4.5(srvx@0.11.15)
+ cookie-es: 2.0.0
+ crossws: 0.4.5(srvx@0.8.16)
db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3)
- env-runner: 0.1.9
- h3: 2.0.1-rc.22(crossws@0.4.5(srvx@0.11.15))
- hookable: 6.1.1
- nf3: 0.3.17
- ocache: 0.1.4
- ofetch: 2.0.0-alpha.3
+ esbuild: 0.25.10
+ fetchdts: 0.1.7
+ h3: 2.0.1-rc.2(crossws@0.4.5(srvx@0.8.16))
+ jiti: 2.7.0
+ nf3: 0.1.12
+ ofetch: 1.5.1
ohash: 2.0.11
- 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))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3)
+ rendu: 0.0.6
+ rollup: 4.56.0
+ srvx: 0.8.16
+ undici: 7.27.2
+ unenv: 2.0.0-rc.21
+ unstorage: 2.0.0-alpha.3(@netlify/blobs@10.1.0)(chokidar@4.0.3)(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@1.5.1)
optionalDependencies:
- dotenv: 17.4.2
- giget: 2.0.0
- jiti: 2.7.0
+ rolldown: 1.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:
- '@azure/app-configuration'
@@ -38280,7 +38250,6 @@ snapshots:
- '@electric-sql/pglite'
- '@libsql/client'
- '@netlify/blobs'
- - '@netlify/runtime'
- '@planetscale/database'
- '@upstash/redis'
- '@vercel/blob'
@@ -38293,7 +38262,6 @@ snapshots:
- idb-keyval
- ioredis
- lru-cache
- - miniflare
- mongodb
- mysql2
- sqlite3
@@ -38585,7 +38553,7 @@ snapshots:
nwsapi@2.2.16: {}
- nx@22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23)):
+ nx@22.7.5(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.23))(@swc/types@0.1.26)(typescript@6.0.2))(@swc/core@1.15.33(@swc/helpers@0.5.23))(debug@4.4.3):
dependencies:
'@emnapi/core': 1.4.5
'@emnapi/runtime': 1.4.5
@@ -38600,7 +38568,7 @@ snapshots:
ansi-styles: 4.3.0
argparse: 2.0.1
asynckit: 0.4.0
- axios: 1.16.0
+ axios: 1.16.0(debug@4.4.3)
balanced-match: 4.0.3
base64-js: 1.5.1
bl: 4.1.0
@@ -38751,10 +38719,6 @@ snapshots:
dependencies:
ohash: 2.0.11
- ocache@0.1.4:
- dependencies:
- ohash: 2.0.11
-
ofetch@1.5.1:
dependencies:
destr: 2.0.5
@@ -39697,6 +39661,11 @@ snapshots:
lodash: 4.17.21
strip-ansi: 6.0.1
+ rendu@0.0.6:
+ dependencies:
+ cookie-es: 2.0.0
+ srvx: 0.8.16
+
repeat-string@1.6.1: {}
require-directory@2.1.1: {}
@@ -40329,6 +40298,8 @@ snapshots:
srvx@0.11.15: {}
+ srvx@0.8.16: {}
+
stable-hash-x@0.2.0: {}
stack-trace@0.0.10: {}
@@ -40850,6 +40821,14 @@ snapshots:
undici@7.27.2: {}
+ unenv@2.0.0-rc.21:
+ dependencies:
+ defu: 6.1.4
+ exsolve: 1.0.8
+ ohash: 2.0.11
+ pathe: 2.0.3
+ ufo: 1.6.3
+
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
@@ -40951,24 +40930,25 @@ 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.5.1)(ofetch@2.0.0-alpha.3):
+ unstorage@2.0.0-alpha.3(@netlify/blobs@10.1.0)(chokidar@4.0.3)(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@1.5.1):
optionalDependencies:
'@netlify/blobs': 10.1.0
- chokidar: 5.0.0
+ chokidar: 4.0.3
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
+ ofetch: 1.5.1
- 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):
+ 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.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))(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))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3):
optionalDependencies:
'@netlify/blobs': 10.1.0
chokidar: 5.0.0
From a6337fb98e62e499b8743a86d8ff06db9e0beb87 Mon Sep 17 00:00:00 2001
From: Flo
Date: Thu, 2 Jul 2026 21:44:55 +0200
Subject: [PATCH 19/25] bench(memory): stabilize CodSpeed memory benchmarks
(#7730)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* bench(memory): stabilize CodSpeed memory benchmarks
Memory flags on PRs were ~85% noise, from two causes:
1. Stale baselines (dominant): the Benchmarks workflow only ran on main
pushes touching packages/** or benchmarks/**. Main commits outside
those paths had no CodSpeed run, so PRs were compared against an old
base run ([^unexpected-base] on every recent codspeed-hq comment; six
late-June PRs all compared to the same June-23 run). One outlier base
measurement then replays into every PR until the next eligible push.
Fix: drop the paths filter for the push trigger + weekly cron backstop.
2. Wall-clock timers racing the single measured run: aborted-requests
(solid/vue) resolved deferred loader data on 500/750ms timeouts raced
against abort, and drained cancellation with at most one setTimeout(0);
streaming-peak staggered deferred sections by milliseconds. Under
variable runner load and instrumentation slowdown the interleaving —
and therefore the allocation sequence — differed run to run. Fix:
count 0ms timers-phase hops (event-loop turns) instead of milliseconds
everywhere async ordering must be staged, and drain aborts with a
fixed 8-turn settlement barrier.
Local before/after evidence (8 fresh-process runs per config, CodSpeed V8
determinism flags, idle + CPU-stressed; kernel peak RSS spread):
aborted-requests/solid idle 11.2MB -> 1.8MB (GC counts bit-identical)
aborted-requests/solid stress 7.7MB -> 2.5MB
streaming-peak/solid idle 6.2MB -> 2.5MB
request-churn/solid (untouched control): unchanged, confirming harness
streaming-peak/vue keeps ~2% proxy-level variance at any tick count; its
PR flags were the identical -3.2% in all three occurrences, i.e. the
stale-base artifact rather than per-run jitter.
Bench names and workload shapes are unchanged; absolute values will step
once on the CodSpeed dashboard from the removed cross-iteration overlap
(e.g. aborted-requests/solid retained 15.6MB -> 2.5MB per invocation).
Co-Authored-By: Claude Fable 5
* bench(memory): exclude docs/examples/e2e from main benchmark runs, drop cron
paths-ignore instead of no filter: docs/examples/e2e-only pushes cannot
affect benchmark results, and falling back across such a commit is safe
because the previous run's benchmark-relevant code is identical. The
weekly cron backstop is unnecessary for the same reason.
Co-Authored-By: Claude Fable 5
* bench(memory): pin GC between iterations in peak-shape scenarios
A/A runs of the real memory instrument showed the two big-payload peak
scenarios were still unstable with no timers involved: whether V8
collects iteration i's garbage before iteration i+1 allocates its
payload is not reproducible run to run, so the measured peak flipped by
a whole payload (serialization-payload solid: 6.8-9.2 MB across
identical runs; peak-large-page: 3.5-3.7 MB).
Peak scenarios measure the footprint of a single request, not
accumulation, so pin the collection points: settle two event-loop turns
(so trailing renderer/stream teardown is included) and force a GC after
each iteration. Peak then deterministically measures
max(single-request footprint). Churn scenarios are unchanged — their
signal is accumulation, which a forced GC would mask; the README rule
now spells out both sides.
A/A validation (3 identical instrumented runs, solid suite):
serialization-payload 6.8-9.2 MB -> 3.5-3.6 MB
peak-large-page 3.5-3.7 MB -> 754-757 KB (0.45% spread)
streaming-peak 38.5-39.5 -> 30.3/30.3/30.3 MB
churn scenarios unchanged (aborted-requests 1.4 MB x3)
Absolute values step because cross-iteration garbage no longer inflates
the peak; the new values are the per-request footprint the scenarios
were designed to track.
Co-Authored-By: Claude Fable 5
---------
Co-authored-by: Claude Fable 5
---
.github/workflows/client-nav-benchmarks.yml | 13 +++--
benchmarks/memory/README.md | 30 ++++++++---
benchmarks/memory/server/bench-utils.ts | 26 +++++++++-
.../scenarios/aborted-requests/shared.ts | 22 ++++----
.../solid/src/routes/stream.$id.tsx | 50 ++++++++++++-------
.../vue/src/routes/stream.$id.tsx | 50 ++++++++++++-------
.../scenarios/peak-large-page/shared.ts | 1 +
.../scenarios/serialization-payload/shared.ts | 1 +
.../react/src/routes/stream.$id.tsx | 27 +++++++---
.../server/scenarios/streaming-peak/shared.ts | 1 +
.../solid/src/routes/stream.$id.tsx | 20 +++++++-
.../vue/src/routes/stream.$id.tsx | 20 +++++++-
12 files changed, 194 insertions(+), 67 deletions(-)
diff --git a/.github/workflows/client-nav-benchmarks.yml b/.github/workflows/client-nav-benchmarks.yml
index eae3cd7448..a56bb70334 100644
--- a/.github/workflows/client-nav-benchmarks.yml
+++ b/.github/workflows/client-nav-benchmarks.yml
@@ -3,11 +3,18 @@ name: Benchmarks
on:
push:
+ # Broad on purpose: CodSpeed compares each PR against the run on its
+ # merge-base commit. Skipping main pushes that don't touch packages/**
+ # (pnpm config, CI, root tooling) leaves those commits without a baseline
+ # run, and CodSpeed silently falls back to an older, possibly outlier run —
+ # which then flags phantom regressions on every PR until the next eligible
+ # push. Only paths that cannot affect benchmark results are excluded.
branches:
- 'main'
- paths:
- - 'packages/**'
- - 'benchmarks/**'
+ paths-ignore:
+ - 'docs/**'
+ - 'examples/**'
+ - 'e2e/**'
pull_request:
paths:
- 'packages/**'
diff --git a/benchmarks/memory/README.md b/benchmarks/memory/README.md
index d9fb24f752..cf871f3439 100644
--- a/benchmarks/memory/README.md
+++ b/benchmarks/memory/README.md
@@ -2,7 +2,13 @@
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:
+`.github/workflows/client-nav-benchmarks.yml`). The workflow runs on every
+push to `main` except docs/examples/e2e-only pushes: CodSpeed compares a PR
+against the run on its merge-base commit, and a main commit without a run makes
+CodSpeed silently fall back to an older base — a single outlier base run then
+flags phantom regressions on every PR until the next run lands. (Falling back
+across a docs-only commit is safe: the previous run's benchmark-relevant code
+is identical.) Two separate benchmarks:
- `server/` (`@benchmarks/memory-server`) — React/Solid/Vue Start apps, requests against
the built server handler (`handler.fetch`), Node environment.
@@ -45,9 +51,15 @@ same workload through the Flame profiler.
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.
+ `--no-opt`). Never call `global.gc()` manually in **churn** scenarios —
+ their signal is accumulation across iterations, which a forced collection
+ masks. **Peak** scenarios do the opposite: they set
+ `pinGcBetweenIterations` on the request loop so a collection runs between
+ iterations. Their signal is the footprint of a single request, and without
+ pinned GC points the measured peak flips by a whole payload depending on
+ whether iteration i's garbage is collected before iteration i+1 allocates.
+ 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.
@@ -92,9 +104,13 @@ same workload through the Flame profiler.
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.
+ `Date.now`, or timers with a nonzero delay — anywhere async ordering must be
+ staged (`streaming-peak`'s deferred sections, `aborted-requests`' deferred
+ loader data and post-abort drain), chain **0ms `setTimeout` hops** and count
+ event-loop turns instead. A wall-clock delay races renderer work differently
+ depending on runner load and instrumentation overhead, which makes the
+ single measured run non-reproducible; 0ms hops fire in registration order in
+ the timers phase, so the interleaving is a pure function of the schedule.
- 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
diff --git a/benchmarks/memory/server/bench-utils.ts b/benchmarks/memory/server/bench-utils.ts
index f1f5dadea0..bc167c6379 100644
--- a/benchmarks/memory/server/bench-utils.ts
+++ b/benchmarks/memory/server/bench-utils.ts
@@ -17,6 +17,15 @@ export type RunSequentialRequestLoopOptions =
iterations?: number
buildRequest: (random: () => number, index: number) => Request
validateResponse?: (response: Response, request: Request) => void
+ // For peak-shape scenarios only. Their signal is the footprint of a
+ // single request, but whether V8 collects iteration i's garbage before
+ // iteration i+1 allocates its payload is not reproducible run to run —
+ // the measured peak flips by a whole payload depending on GC timing.
+ // Forcing a collection between iterations pins the GC points so peak
+ // deterministically measures max(single-request footprint). Churn
+ // scenarios must never set this: their signal is accumulation across
+ // iterations, which a forced GC would mask.
+ pinGcBetweenIterations?: boolean
}
export const memoryBenchOptions = {
@@ -64,7 +73,12 @@ export async function runSequentialRequestLoop(
handler: StartRequestHandler,
options: RunSequentialRequestLoopOptions,
) {
- const { iterations = 10, buildRequest, validateResponse } = options
+ const {
+ iterations = 10,
+ buildRequest,
+ validateResponse,
+ pinGcBetweenIterations = false,
+ } = options
const random =
options.seed !== undefined
? createDeterministicRandom(options.seed)
@@ -86,5 +100,15 @@ export async function runSequentialRequestLoop(
validate(response, request)
await drainResponse(response)
+
+ if (pinGcBetweenIterations) {
+ // Two 0ms timer hops first, so trailing renderer/stream teardown
+ // scheduled for the next turns runs before the collection — otherwise
+ // its garbage survives into the next iteration's measurements.
+ await new Promise((resolve) => setTimeout(resolve, 0))
+ await new Promise((resolve) => setTimeout(resolve, 0))
+ // Present under CodSpeed (--expose-gc); absent in plain smoke runs.
+ ;(globalThis as { gc?: () => void }).gc?.()
+ }
}
}
diff --git a/benchmarks/memory/server/scenarios/aborted-requests/shared.ts b/benchmarks/memory/server/scenarios/aborted-requests/shared.ts
index 9b71c37617..00a5353e67 100644
--- a/benchmarks/memory/server/scenarios/aborted-requests/shared.ts
+++ b/benchmarks/memory/server/scenarios/aborted-requests/shared.ts
@@ -6,12 +6,10 @@ 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
@@ -29,17 +27,14 @@ 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',
},
}
@@ -188,12 +183,15 @@ async function cancelReader(
await reader.cancel()
}
-async function drainCancellation(mode: AbortedRequestDrainMode) {
- await Promise.resolve()
- await Promise.resolve()
+const cancellationDrainTicks = 8
- if (mode === 'tasks') {
- await new Promise((resolve) => setTimeout(resolve, 0))
+// Fixed-count settlement barrier: each 0ms timer hop runs one full event-loop
+// turn (microtasks flush between hops), so post-abort renderer teardown
+// finishes within a deterministic number of turns instead of bleeding a
+// load-dependent amount of work into later iterations of the measured run.
+async function drainCancellation() {
+ for (let tick = 0; tick < cancellationDrainTicks; tick++) {
+ await new Promise((resolve) => setTimeout(resolve, 0))
}
}
@@ -233,7 +231,7 @@ async function assertAbortedRequestsSanity(
// does not observe Request.signal for this in-process request.
controller.abort()
await cancelReader(reader, mode.cancelMode)
- await drainCancellation(mode.drainMode)
+ await drainCancellation()
}
async function runAbortedRequestLoop(
@@ -255,7 +253,7 @@ async function runAbortedRequestLoop(
)
controller.abort()
await cancelReader(reader, mode.cancelMode)
- await drainCancellation(mode.drainMode)
+ await drainCancellation()
}
}
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
index 95d69ad91d..b47f25a405 100644
--- 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
@@ -6,25 +6,32 @@ import {
type RecordGroup,
} from '../../../deferred-records'
-const alphaDelayMs = 50
-const betaDelayMs = 75
-const abortProbeAlphaDelayMs = 500
-const abortProbeBetaDelayMs = 750
+const alphaResolveTicks = 4
+const betaResolveTicks = 6
+const abortProbeAlphaResolveTicks = 40
+const abortProbeBetaResolveTicks = 60
function isAbortProbeId(id: string) {
return id === 'sanity-mid-stream' || id.startsWith('abort-')
}
-function getDelay(id: string, group: RecordGroup) {
+function getResolveTicks(id: string, group: RecordGroup) {
if (isAbortProbeId(id)) {
- return group === 'alpha' ? abortProbeAlphaDelayMs : abortProbeBetaDelayMs
+ return group === 'alpha'
+ ? abortProbeAlphaResolveTicks
+ : abortProbeBetaResolveTicks
}
- return group === 'alpha' ? alphaDelayMs : betaDelayMs
+ return group === 'alpha' ? alphaResolveTicks : betaResolveTicks
}
-function resolveAfterDelay(
- delayMs: number,
+// One tick = one 0ms timers-phase hop. Counting event-loop turns instead of
+// milliseconds keeps the resolve/abort interleaving a pure function of the
+// event-loop schedule: a wall-clock delay races the abort differently
+// depending on runner load and instrumentation overhead, which made this
+// benchmark's single measured run swing between runs.
+function resolveAfterTicks(
+ ticks: number,
signal: AbortSignal,
value: () => T,
abortedValue: () => T,
@@ -35,17 +42,28 @@ function resolveAfterDelay(
return
}
- const timeoutId = setTimeout(() => {
- signal.removeEventListener('abort', onAbort)
- resolve(value())
- }, delayMs)
+ let remaining = ticks
+ let timeoutId: ReturnType
const onAbort = () => {
clearTimeout(timeoutId)
resolve(abortedValue())
}
+ const step = () => {
+ remaining -= 1
+
+ if (remaining <= 0) {
+ signal.removeEventListener('abort', onAbort)
+ resolve(value())
+ return
+ }
+
+ timeoutId = setTimeout(step, 0)
+ }
+
signal.addEventListener('abort', onAbort, { once: true })
+ timeoutId = setTimeout(step, 0)
})
}
@@ -54,10 +72,8 @@ function makeDeferredRecords(
group: RecordGroup,
signal: AbortSignal,
) {
- const delayMs = getDelay(id, group)
-
- return resolveAfterDelay(
- delayMs,
+ return resolveAfterTicks(
+ getResolveTicks(id, group),
signal,
() => makeAbortedRequestRecords(id, group),
() => [],
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
index d064bf75e9..b723c7f69e 100644
--- 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
@@ -6,25 +6,32 @@ import {
type RecordGroup,
} from '../../../deferred-records'
-const alphaDelayMs = 50
-const betaDelayMs = 75
-const abortProbeAlphaDelayMs = 500
-const abortProbeBetaDelayMs = 750
+const alphaResolveTicks = 4
+const betaResolveTicks = 6
+const abortProbeAlphaResolveTicks = 40
+const abortProbeBetaResolveTicks = 60
function isAbortProbeId(id: string) {
return id === 'sanity-mid-stream' || id.startsWith('abort-')
}
-function getDelay(id: string, group: RecordGroup) {
+function getResolveTicks(id: string, group: RecordGroup) {
if (isAbortProbeId(id)) {
- return group === 'alpha' ? abortProbeAlphaDelayMs : abortProbeBetaDelayMs
+ return group === 'alpha'
+ ? abortProbeAlphaResolveTicks
+ : abortProbeBetaResolveTicks
}
- return group === 'alpha' ? alphaDelayMs : betaDelayMs
+ return group === 'alpha' ? alphaResolveTicks : betaResolveTicks
}
-function resolveAfterDelay(
- delayMs: number,
+// One tick = one 0ms timers-phase hop. Counting event-loop turns instead of
+// milliseconds keeps the resolve/abort interleaving a pure function of the
+// event-loop schedule: a wall-clock delay races the abort differently
+// depending on runner load and instrumentation overhead, which made this
+// benchmark's single measured run swing between runs.
+function resolveAfterTicks(
+ ticks: number,
signal: AbortSignal,
value: () => T,
abortedValue: () => T,
@@ -35,17 +42,28 @@ function resolveAfterDelay(
return
}
- const timeoutId = setTimeout(() => {
- signal.removeEventListener('abort', onAbort)
- resolve(value())
- }, delayMs)
+ let remaining = ticks
+ let timeoutId: ReturnType
const onAbort = () => {
clearTimeout(timeoutId)
resolve(abortedValue())
}
+ const step = () => {
+ remaining -= 1
+
+ if (remaining <= 0) {
+ signal.removeEventListener('abort', onAbort)
+ resolve(value())
+ return
+ }
+
+ timeoutId = setTimeout(step, 0)
+ }
+
signal.addEventListener('abort', onAbort, { once: true })
+ timeoutId = setTimeout(step, 0)
})
}
@@ -54,10 +72,8 @@ function makeDeferredRecords(
group: RecordGroup,
signal: AbortSignal,
) {
- const delayMs = getDelay(id, group)
-
- return resolveAfterDelay(
- delayMs,
+ return resolveAfterTicks(
+ getResolveTicks(id, group),
signal,
() => makeAbortedRequestRecords(id, group),
() => [],
diff --git a/benchmarks/memory/server/scenarios/peak-large-page/shared.ts b/benchmarks/memory/server/scenarios/peak-large-page/shared.ts
index 36a91fa49d..b09c16e667 100644
--- a/benchmarks/memory/server/scenarios/peak-large-page/shared.ts
+++ b/benchmarks/memory/server/scenarios/peak-large-page/shared.ts
@@ -54,6 +54,7 @@ export function createWorkloadGroup(
iterations: peakLargePageIterations,
buildRequest: buildPeakLargePageRequest,
validateResponse: validatePeakLargePageResponse,
+ pinGcBetweenIterations: true,
})
return {
diff --git a/benchmarks/memory/server/scenarios/serialization-payload/shared.ts b/benchmarks/memory/server/scenarios/serialization-payload/shared.ts
index c02990bbf9..c6809bee0a 100644
--- a/benchmarks/memory/server/scenarios/serialization-payload/shared.ts
+++ b/benchmarks/memory/server/scenarios/serialization-payload/shared.ts
@@ -61,6 +61,7 @@ export function createWorkloadGroup(
iterations: serializationPayloadIterations,
buildRequest: buildPayloadRequest,
validateResponse: validatePayloadResponse,
+ pinGcBetweenIterations: true,
})
return {
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
index aa8ba5e865..886cb64d45 100644
--- 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
@@ -5,7 +5,7 @@ import {
type DeferredSectionPayload,
} from '../../../deferred-section-data'
-const fallbackFlushDelayMs = 1
+const fallbackFlushTicks = 4
export const Route = createFileRoute('/stream/$id')({
loader: ({ params }) => ({
@@ -23,13 +23,28 @@ export const Route = createFileRoute('/stream/$id')({
// 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.
+// stops exercising multi-flush streaming. Timers-phase callbacks reliably
+// lose that race, but a millisecond delay makes the loss margin wall-clock
+// dependent, so this chains 0ms hops instead: each hop yields one full
+// event-loop turn (immediates and microtasks included), making the flush
+// ordering a function of turn count, not runner speed. Distinct hop counts
+// keep section ordering deterministic.
function afterFallbackFlush(sectionIndex: number) {
return new Promise((resolve) => {
- setTimeout(resolve, fallbackFlushDelayMs + sectionIndex)
+ let remaining = fallbackFlushTicks + sectionIndex
+
+ const step = () => {
+ remaining -= 1
+
+ if (remaining <= 0) {
+ resolve()
+ return
+ }
+
+ setTimeout(step, 0)
+ }
+
+ setTimeout(step, 0)
})
}
diff --git a/benchmarks/memory/server/scenarios/streaming-peak/shared.ts b/benchmarks/memory/server/scenarios/streaming-peak/shared.ts
index a82c43b5b4..f01217fdd2 100644
--- a/benchmarks/memory/server/scenarios/streaming-peak/shared.ts
+++ b/benchmarks/memory/server/scenarios/streaming-peak/shared.ts
@@ -98,6 +98,7 @@ export function createWorkloadGroup(
iterations: streamingPeakIterations,
buildRequest: buildStreamingRequest,
validateResponse: validateStreamingResponse,
+ pinGcBetweenIterations: true,
})
return {
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
index 0a3f64ffd4..977f43061e 100644
--- 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
@@ -5,7 +5,7 @@ import {
type DeferredSectionPayload,
} from '../../../deferred-section-data'
-const fallbackFlushDelayMs = 25
+const fallbackFlushTicks = 12
export const Route = createFileRoute('/stream/$id')({
loader: ({ params }) => ({
@@ -20,9 +20,25 @@ export const Route = createFileRoute('/stream/$id')({
// Deferred sections must settle after the framework shell flush so the bench
// continues exercising multi-flush streaming with visible fallback chunks.
+// Chained 0ms timers-phase hops give the renderer a deterministic number of
+// full event-loop turns to flush, instead of a wall-clock delay whose margin
+// varies with runner load; distinct hop counts keep section ordering stable.
function afterFallbackFlush(sectionIndex: number) {
return new Promise((resolve) => {
- setTimeout(resolve, fallbackFlushDelayMs + sectionIndex)
+ let remaining = fallbackFlushTicks + sectionIndex
+
+ const step = () => {
+ remaining -= 1
+
+ if (remaining <= 0) {
+ resolve()
+ return
+ }
+
+ setTimeout(step, 0)
+ }
+
+ setTimeout(step, 0)
})
}
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
index 963782403b..0c71a8d3a0 100644
--- 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
@@ -5,7 +5,7 @@ import {
type DeferredSectionPayload,
} from '../../../deferred-section-data'
-const fallbackFlushDelayMs = 1
+const fallbackFlushTicks = 20
export const Route = createFileRoute('/stream/$id')({
loader: ({ params }) => ({
@@ -20,9 +20,25 @@ export const Route = createFileRoute('/stream/$id')({
// Deferred sections must settle strictly AFTER Vue's shell has a chance to
// flush, so fallbacks are emitted before deferred section content.
+// Chained 0ms timers-phase hops give the renderer a deterministic number of
+// full event-loop turns to flush, instead of a wall-clock delay whose margin
+// varies with runner load; distinct hop counts keep section ordering stable.
function afterFallbackFlush(sectionIndex: number) {
return new Promise((resolve) => {
- setTimeout(resolve, fallbackFlushDelayMs + sectionIndex)
+ let remaining = fallbackFlushTicks + sectionIndex
+
+ const step = () => {
+ remaining -= 1
+
+ if (remaining <= 0) {
+ resolve()
+ return
+ }
+
+ setTimeout(step, 0)
+ }
+
+ setTimeout(step, 0)
})
}
From 208100b7c0a8491fc93c26358ffe30badec4b138 Mon Sep 17 00:00:00 2001
From: Flo
Date: Fri, 3 Jul 2026 08:18:08 +0200
Subject: [PATCH 20/25] feat(benchmarks): client-side CPU benchmark scenario
suite (#7732)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(benchmarks): add client-side CPU benchmark scenario suite
Add 11 client-side CPU benchmark scenarios to benchmarks/client-nav, each
implemented identically across react/solid/vue and tracked by CodSpeed
through the existing Benchmarks workflow (no CI changes needed):
async-pipeline, control-flow, head, history, links, loaders, mount,
nested-params, preload, route-tree-scale, search-params
Each scenario is an isolated file-based-routing app (route trees generated
by @tanstack/router-plugin) built for production and driven in jsdom by a
shared harness (scenarios/harness.ts): circular step sequences of real
clicks (plus hover/preload/history-traversal/invalidate steps),
each synchronized on the router's onRendered event, with a warm-up lap
that asserts every step's observable output. Determinism rules: no
wall-clock timers (counted 0ms hops only), staleTime/gcTime pinned to 0 or
1e9, deterministic preload staleness, stationary DOM and history depth.
The existing baseline apps and bench names are untouched for CodSpeed
continuity; baseline vite configs only gain an explicit root/setupFiles so
they resolve identically under the new per-framework aggregate configs.
Notable constraints encoded in the scenarios:
- Component-level Await/Suspense is excluded from async-pipeline: React 19
throttles every Suspense reveal by ~300ms wall-clock, which is
inherently non-deterministic to benchmark.
- control-flow/react silences React 19's onCaughtError reporting, which
otherwise dominates the measured loop with console I/O.
- Vue route components must be defined before their createFileRoute call
(bundled var hoisting silently yields component: undefined otherwise).
Co-Authored-By: Claude Fable 5
* ci: apply automated fixes
* chore(benchmarks): prefix client-nav bench names with "client-"
All benches in @benchmarks/client-nav now start with "client" so they are
easy to identify on the CodSpeed dashboard (the baseline already did).
Co-Authored-By: Claude Fable 5
* fix(benchmarks): address review findings on client-nav scenario stability
Fixes from an adversarial review of the scenario suite:
- mount: create the browser history explicitly and destroy() it on unmount.
The default per-router createBrowserHistory() monkey-patches
window.history.pushState/replaceState, chaining one wrapper per router —
per-mount cost degraded 1.8ms -> 6ms over 3000 mounts (O(N^2) drift in
the cold-start bench). Verified flat (~1.86ms) after the fix.
- links: replace the five components with setup-scoped
useMatchRoute probes in all three apps. vue-router's MatchRoute calls
useMatchRoute() inside its render function, leaking one undisposed
watcher per render — the Vue bench accumulated ~5 dead subscribers per
navigation. Verified flat per-tick cost over 2000 ticks after the fix.
- all scenarios: bound the scroll-restoration cache with
getScrollRestorationKey: (location) => location.pathname. The default
key is a fresh random per-entry location key, so the module-level cache
grew one entry per push navigation for the whole run.
- harness hover step: dispatch a single mouseover instead of
mouseover+mouseenter. Solid/Vue attach intent-preload handlers to BOTH
events, so the old double dispatch ran the preload pipeline twice for
them but once for React. Verified all three adapters preload exactly
once per hover via cached-matches probes.
- README: correct the loaders row (the invalidate step means cached routes
re-run once per lap), the links row, and document the session-history
growth characteristics.
Co-Authored-By: Claude Fable 5
* feat(benchmarks): cover client rewrites, lazy route chunks, encoded params
Closes the three coverage gaps identified in the scenario-suite analysis:
- new `rewrites` scenario (react/solid/vue): router basepath '/app' composed
with a locale input/output rewrite pair — the client analog of the SSR
rewrites scenario. Every href build runs the output rewrite and every
committed location runs the input rewrite; assertions check both the
internal router pathname and the external window pathname. The harness
gained an `initialUrl` option since a basepath app cannot start at '/'.
- route-tree-scale now builds with `autoCodeSplitting: true`, so its
navigations also exercise lazy route-chunk resolution — the default shape
of real file-based apps, previously uncovered.
- nested-params and route-tree-scale param sets now include characters that
need percent-encoding (spaces, `&`, `%`, `+`, unicode) so the segment
encode/decode paths run on every navigation.
Co-Authored-By: Claude Fable 5
* fix(benchmarks): reduce syscall time in measured loops, lengthen mount bench
CodSpeed excludes syscall time from simulation measures, and past a
threshold it warns ("cannot be consistently instrumented") and skips the
benchmark; the exclusion is also inconsistent run-to-run, which was the
root cause of the two same-code CI swings (route-tree-scale react 13%,
async-pipeline react 3.6%) and of skipped runs on hop-heavy benches like
async-pipeline solid (579 calls / 12.1ms).
- Replace all counted setTimeout(0) hops with setImmediate hops (harness
timerHop + async-pipeline hopDelay). Both are deterministic event-loop
turns that yield to timers and the React Node scheduler, but a timer
costs ~3-4 syscalls (timerfd + epoll) per hop vs ~1 for an immediate.
Same-machine instrumented before/after: preload solid 39.8ms -> 1.2ms of
syscall time, preload vue 30.1 -> 1.1, async-pipeline solid 23.5 -> 1.1,
async-pipeline vue 17.9 -> 0.9. head/route-tree-scale are unchanged
(their residual syscalls are GC/scheduler, and their measures were
already stable); history's syscalls come from jsdom's internal
timer-queued traversal and are not reachable from bench code (its
measured values are stable regardless).
- mount: 6 ticks per iteration instead of 2. A single mount simulates to
only ~8ms and very short measures amplify allocator/GC quantization;
the bench now measures ~50ms+ of simulated CPU.
All 39 benches pass; full nx typecheck green.
Co-Authored-By: Claude Fable 5
* fix(benchmarks): stop history benches sleeping in jsdom's traversal timers
The history benches were skip-warned by CodSpeed for excessive syscall
time (5-26ms, highly variable across runs). Root cause: jsdom delivers
history traversals through two nested window.setTimeout(0) tasks, and Node
clamps zero timers to 1ms of wall time — so while awaiting onRendered
after back/forward/go, the event loop blocked in epoll_wait until each
timer expired. That blocked wall-time is recorded as syscall time, and its
duration depends on host timer behavior, hence the variability.
The harness now reroutes zero-delay window.setTimeout calls onto
setImmediate for the duration of a benchmark (non-zero delays pass
through; clearTimeout handles both). Zero-delay timeouts carry no ordering
semantics a check-phase immediate doesn't satisfy, and the suite's
conventions already forbid real-delay timers in measured code.
Instrumented A/A verification (2 runs per framework): history syscall time
react 23-26ms -> 2.5ms, solid 14-23ms -> 1.1ms, vue 11-12ms -> 1.2ms, with
bit-identical syscall counts across runs and measured values stable to
0.06%. The benches also stopped measuring sleep: locally an iteration
dropped from ~42-49ms to ~5-11ms of actual CPU work. All 39 benches pass.
Co-Authored-By: Claude Fable 5
---------
Co-authored-by: Claude Fable 5
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
benchmarks/client-nav/README.md | 86 +-
benchmarks/client-nav/jsdom.ts | 6 +
benchmarks/client-nav/package.json | 101 +-
benchmarks/client-nav/react/vite.config.ts | 8 +-
.../async-pipeline/react/project.json | 40 +
.../scenarios/async-pipeline/react/setup.ts | 17 +
.../async-pipeline/react/speed.bench.ts | 34 +
.../async-pipeline/react/speed.flame.ts | 18 +
.../async-pipeline/react/src/main.tsx | 37 +
.../async-pipeline/react/src/routeTree.gen.ts | 140 +++
.../react/src/routes/__root.tsx | 50 +
.../react/src/routes/ctx.$id.tsx | 29 +
.../async-pipeline/react/src/routes/index.tsx | 13 +
.../react/src/routes/nested.$id.tsx | 18 +
.../react/src/routes/nested.tsx | 23 +
.../react/src/routes/slow.$id.tsx | 18 +
.../async-pipeline/react/tsconfig.json | 21 +
.../async-pipeline/react/vite.config.ts | 40 +
.../scenarios/async-pipeline/shared.ts | 113 +++
.../async-pipeline/solid/project.json | 40 +
.../scenarios/async-pipeline/solid/setup.ts | 17 +
.../async-pipeline/solid/speed.bench.ts | 34 +
.../async-pipeline/solid/speed.flame.ts | 18 +
.../async-pipeline/solid/src/main.tsx | 34 +
.../async-pipeline/solid/src/routeTree.gen.ts | 140 +++
.../solid/src/routes/__root.tsx | 46 +
.../solid/src/routes/ctx.$id.tsx | 29 +
.../async-pipeline/solid/src/routes/index.tsx | 13 +
.../solid/src/routes/nested.$id.tsx | 18 +
.../solid/src/routes/nested.tsx | 23 +
.../solid/src/routes/slow.$id.tsx | 18 +
.../async-pipeline/solid/tsconfig.json | 21 +
.../async-pipeline/solid/vite.config.ts | 48 +
.../scenarios/async-pipeline/vue/project.json | 40 +
.../scenarios/async-pipeline/vue/setup.ts | 17 +
.../async-pipeline/vue/speed.bench.ts | 34 +
.../async-pipeline/vue/speed.flame.ts | 18 +
.../scenarios/async-pipeline/vue/src/main.tsx | 41 +
.../async-pipeline/vue/src/routeTree.gen.ts | 140 +++
.../async-pipeline/vue/src/routes/__root.tsx | 53 ++
.../async-pipeline/vue/src/routes/ctx.$id.tsx | 32 +
.../async-pipeline/vue/src/routes/index.tsx | 16 +
.../vue/src/routes/nested.$id.tsx | 21 +
.../async-pipeline/vue/src/routes/nested.tsx | 26 +
.../vue/src/routes/slow.$id.tsx | 21 +
.../async-pipeline/vue/tsconfig.json | 21 +
.../async-pipeline/vue/vite.config.ts | 42 +
.../scenarios/control-flow/react/project.json | 40 +
.../scenarios/control-flow/react/setup.ts | 17 +
.../control-flow/react/speed.bench.ts | 34 +
.../control-flow/react/speed.flame.ts | 18 +
.../scenarios/control-flow/react/src/main.tsx | 37 +
.../control-flow/react/src/routeTree.gen.ts | 156 ++++
.../control-flow/react/src/routes/__root.tsx | 52 ++
.../control-flow/react/src/routes/broken.tsx | 16 +
.../control-flow/react/src/routes/hop1.tsx | 9 +
.../control-flow/react/src/routes/hop2.tsx | 9 +
.../control-flow/react/src/routes/index.tsx | 9 +
.../react/src/routes/missing.$id.tsx | 23 +
.../control-flow/react/src/routes/target.tsx | 9 +
.../control-flow/react/tsconfig.json | 21 +
.../control-flow/react/vite.config.ts | 40 +
.../scenarios/control-flow/shared.ts | 91 ++
.../scenarios/control-flow/solid/project.json | 40 +
.../scenarios/control-flow/solid/setup.ts | 17 +
.../control-flow/solid/speed.bench.ts | 34 +
.../control-flow/solid/speed.flame.ts | 18 +
.../scenarios/control-flow/solid/src/main.tsx | 32 +
.../control-flow/solid/src/routeTree.gen.ts | 156 ++++
.../control-flow/solid/src/routes/__root.tsx | 52 ++
.../control-flow/solid/src/routes/broken.tsx | 16 +
.../control-flow/solid/src/routes/hop1.tsx | 9 +
.../control-flow/solid/src/routes/hop2.tsx | 9 +
.../control-flow/solid/src/routes/index.tsx | 9 +
.../solid/src/routes/missing.$id.tsx | 23 +
.../control-flow/solid/src/routes/target.tsx | 9 +
.../control-flow/solid/tsconfig.json | 21 +
.../control-flow/solid/vite.config.ts | 48 +
.../scenarios/control-flow/vue/project.json | 40 +
.../scenarios/control-flow/vue/setup.ts | 17 +
.../scenarios/control-flow/vue/speed.bench.ts | 34 +
.../scenarios/control-flow/vue/speed.flame.ts | 18 +
.../scenarios/control-flow/vue/src/main.tsx | 39 +
.../control-flow/vue/src/routeTree.gen.ts | 156 ++++
.../control-flow/vue/src/routes/__root.tsx | 57 ++
.../control-flow/vue/src/routes/broken.tsx | 21 +
.../control-flow/vue/src/routes/hop1.tsx | 9 +
.../control-flow/vue/src/routes/hop2.tsx | 9 +
.../control-flow/vue/src/routes/index.tsx | 12 +
.../vue/src/routes/missing.$id.tsx | 28 +
.../control-flow/vue/src/routes/target.tsx | 12 +
.../scenarios/control-flow/vue/tsconfig.json | 21 +
.../scenarios/control-flow/vue/vite.config.ts | 42 +
benchmarks/client-nav/scenarios/harness.ts | 371 ++++++++
.../scenarios/head/react/project.json | 40 +
.../client-nav/scenarios/head/react/setup.ts | 17 +
.../scenarios/head/react/speed.bench.ts | 34 +
.../scenarios/head/react/speed.flame.ts | 18 +
.../scenarios/head/react/src/main.tsx | 35 +
.../scenarios/head/react/src/routeTree.gen.ts | 145 +++
.../head/react/src/routes/__root.tsx | 59 ++
.../head/react/src/routes/articles.$id.tsx | 18 +
.../head/react/src/routes/docs.$section.tsx | 18 +
.../scenarios/head/react/src/routes/docs.tsx | 16 +
.../scenarios/head/react/src/routes/index.tsx | 16 +
.../head/react/src/routes/settings.tsx | 16 +
.../scenarios/head/react/tsconfig.json | 21 +
.../scenarios/head/react/vite.config.ts | 40 +
.../client-nav/scenarios/head/shared.ts | 152 +++
.../scenarios/head/solid/project.json | 40 +
.../client-nav/scenarios/head/solid/setup.ts | 17 +
.../scenarios/head/solid/speed.bench.ts | 34 +
.../scenarios/head/solid/speed.flame.ts | 18 +
.../scenarios/head/solid/src/main.tsx | 32 +
.../scenarios/head/solid/src/routeTree.gen.ts | 145 +++
.../head/solid/src/routes/__root.tsx | 58 ++
.../head/solid/src/routes/articles.$id.tsx | 18 +
.../head/solid/src/routes/docs.$section.tsx | 18 +
.../scenarios/head/solid/src/routes/docs.tsx | 16 +
.../scenarios/head/solid/src/routes/index.tsx | 16 +
.../head/solid/src/routes/settings.tsx | 16 +
.../scenarios/head/solid/tsconfig.json | 21 +
.../scenarios/head/solid/vite.config.ts | 48 +
.../scenarios/head/vue/project.json | 40 +
.../client-nav/scenarios/head/vue/setup.ts | 17 +
.../scenarios/head/vue/speed.bench.ts | 34 +
.../scenarios/head/vue/speed.flame.ts | 18 +
.../scenarios/head/vue/src/main.tsx | 39 +
.../scenarios/head/vue/src/routeTree.gen.ts | 145 +++
.../scenarios/head/vue/src/routes/__root.tsx | 58 ++
.../head/vue/src/routes/articles.$id.tsx | 21 +
.../head/vue/src/routes/docs.$section.tsx | 21 +
.../scenarios/head/vue/src/routes/docs.tsx | 19 +
.../scenarios/head/vue/src/routes/index.tsx | 19 +
.../head/vue/src/routes/settings.tsx | 19 +
.../scenarios/head/vue/tsconfig.json | 21 +
.../scenarios/head/vue/vite.config.ts | 42 +
.../scenarios/history/react/project.json | 40 +
.../scenarios/history/react/setup.ts | 17 +
.../scenarios/history/react/speed.bench.ts | 34 +
.../scenarios/history/react/speed.flame.ts | 18 +
.../scenarios/history/react/src/main.tsx | 35 +
.../history/react/src/routeTree.gen.ts | 113 +++
.../history/react/src/routes/__root.tsx | 66 ++
.../history/react/src/routes/gallery.tsx | 13 +
.../history/react/src/routes/index.tsx | 13 +
.../history/react/src/routes/pages.$n.tsx | 16 +
.../react/src/routes/photos.$photoId.tsx | 16 +
.../scenarios/history/react/tsconfig.json | 21 +
.../scenarios/history/react/vite.config.ts | 40 +
.../client-nav/scenarios/history/shared.ts | 89 ++
.../scenarios/history/solid/project.json | 40 +
.../scenarios/history/solid/setup.ts | 17 +
.../scenarios/history/solid/speed.bench.ts | 34 +
.../scenarios/history/solid/speed.flame.ts | 18 +
.../scenarios/history/solid/src/main.tsx | 32 +
.../history/solid/src/routeTree.gen.ts | 113 +++
.../history/solid/src/routes/__root.tsx | 66 ++
.../history/solid/src/routes/gallery.tsx | 13 +
.../history/solid/src/routes/index.tsx | 13 +
.../history/solid/src/routes/pages.$n.tsx | 16 +
.../solid/src/routes/photos.$photoId.tsx | 16 +
.../scenarios/history/solid/tsconfig.json | 21 +
.../scenarios/history/solid/vite.config.ts | 48 +
.../scenarios/history/vue/project.json | 40 +
.../client-nav/scenarios/history/vue/setup.ts | 17 +
.../scenarios/history/vue/speed.bench.ts | 34 +
.../scenarios/history/vue/speed.flame.ts | 18 +
.../scenarios/history/vue/src/main.tsx | 39 +
.../history/vue/src/routeTree.gen.ts | 113 +++
.../history/vue/src/routes/__root.tsx | 69 ++
.../history/vue/src/routes/gallery.tsx | 16 +
.../history/vue/src/routes/index.tsx | 16 +
.../history/vue/src/routes/pages.$n.tsx | 19 +
.../vue/src/routes/photos.$photoId.tsx | 19 +
.../scenarios/history/vue/tsconfig.json | 21 +
.../scenarios/history/vue/vite.config.ts | 42 +
.../scenarios/links/react/project.json | 40 +
.../client-nav/scenarios/links/react/setup.ts | 17 +
.../scenarios/links/react/speed.bench.ts | 34 +
.../scenarios/links/react/speed.flame.ts | 18 +
.../scenarios/links/react/src/main.tsx | 35 +
.../links/react/src/routeTree.gen.ts | 95 ++
.../links/react/src/routes/__root.tsx | 104 +++
.../links/react/src/routes/about.tsx | 14 +
.../links/react/src/routes/index.tsx | 14 +
.../links/react/src/routes/items.$id.tsx | 16 +
.../scenarios/links/react/tsconfig.json | 21 +
.../scenarios/links/react/vite.config.ts | 40 +
.../client-nav/scenarios/links/shared.ts | 133 +++
.../scenarios/links/solid/project.json | 40 +
.../client-nav/scenarios/links/solid/setup.ts | 17 +
.../scenarios/links/solid/speed.bench.ts | 34 +
.../scenarios/links/solid/speed.flame.ts | 18 +
.../scenarios/links/solid/src/main.tsx | 32 +
.../links/solid/src/routeTree.gen.ts | 95 ++
.../links/solid/src/routes/__root.tsx | 119 +++
.../links/solid/src/routes/about.tsx | 14 +
.../links/solid/src/routes/index.tsx | 14 +
.../links/solid/src/routes/items.$id.tsx | 16 +
.../scenarios/links/solid/tsconfig.json | 21 +
.../scenarios/links/solid/vite.config.ts | 48 +
.../scenarios/links/vue/project.json | 40 +
.../client-nav/scenarios/links/vue/setup.ts | 17 +
.../scenarios/links/vue/speed.bench.ts | 34 +
.../scenarios/links/vue/speed.flame.ts | 18 +
.../scenarios/links/vue/src/main.tsx | 39 +
.../scenarios/links/vue/src/routeTree.gen.ts | 95 ++
.../scenarios/links/vue/src/routes/__root.tsx | 120 +++
.../scenarios/links/vue/src/routes/about.tsx | 17 +
.../scenarios/links/vue/src/routes/index.tsx | 17 +
.../links/vue/src/routes/items.$id.tsx | 19 +
.../scenarios/links/vue/tsconfig.json | 21 +
.../scenarios/links/vue/vite.config.ts | 42 +
.../scenarios/loaders/react/project.json | 40 +
.../scenarios/loaders/react/setup.ts | 17 +
.../scenarios/loaders/react/speed.bench.ts | 34 +
.../scenarios/loaders/react/speed.flame.ts | 18 +
.../scenarios/loaders/react/src/main.tsx | 35 +
.../loaders/react/src/routeTree.gen.ts | 113 +++
.../loaders/react/src/routes/__root.tsx | 70 ++
.../loaders/react/src/routes/cached.$id.tsx | 55 ++
.../loaders/react/src/routes/deps.tsx | 65 ++
.../loaders/react/src/routes/fresh.$id.tsx | 55 ++
.../loaders/react/src/routes/index.tsx | 14 +
.../scenarios/loaders/react/tsconfig.json | 21 +
.../scenarios/loaders/react/vite.config.ts | 40 +
.../client-nav/scenarios/loaders/shared.ts | 134 +++
.../scenarios/loaders/solid/project.json | 40 +
.../scenarios/loaders/solid/setup.ts | 17 +
.../scenarios/loaders/solid/speed.bench.ts | 34 +
.../scenarios/loaders/solid/speed.flame.ts | 18 +
.../scenarios/loaders/solid/src/main.tsx | 32 +
.../loaders/solid/src/routeTree.gen.ts | 113 +++
.../loaders/solid/src/routes/__root.tsx | 66 ++
.../loaders/solid/src/routes/cached.$id.tsx | 62 ++
.../loaders/solid/src/routes/deps.tsx | 72 ++
.../loaders/solid/src/routes/fresh.$id.tsx | 62 ++
.../loaders/solid/src/routes/index.tsx | 14 +
.../scenarios/loaders/solid/tsconfig.json | 21 +
.../scenarios/loaders/solid/vite.config.ts | 48 +
.../scenarios/loaders/vue/project.json | 40 +
.../client-nav/scenarios/loaders/vue/setup.ts | 17 +
.../scenarios/loaders/vue/speed.bench.ts | 34 +
.../scenarios/loaders/vue/speed.flame.ts | 18 +
.../scenarios/loaders/vue/src/main.tsx | 39 +
.../loaders/vue/src/routeTree.gen.ts | 113 +++
.../loaders/vue/src/routes/__root.tsx | 69 ++
.../loaders/vue/src/routes/cached.$id.tsx | 68 ++
.../scenarios/loaders/vue/src/routes/deps.tsx | 78 ++
.../loaders/vue/src/routes/fresh.$id.tsx | 68 ++
.../loaders/vue/src/routes/index.tsx | 17 +
.../scenarios/loaders/vue/tsconfig.json | 21 +
.../scenarios/loaders/vue/vite.config.ts | 42 +
.../scenarios/mount/react/project.json | 40 +
.../client-nav/scenarios/mount/react/setup.ts | 17 +
.../scenarios/mount/react/speed.bench.ts | 34 +
.../scenarios/mount/react/speed.flame.ts | 18 +
.../scenarios/mount/react/src/main.tsx | 47 +
.../mount/react/src/routeTree.gen.ts | 264 ++++++
.../mount/react/src/routes/__root.tsx | 41 +
.../mount/react/src/routes/about.tsx | 9 +
.../mount/react/src/routes/blog.$slug.tsx | 13 +
.../mount/react/src/routes/blog.index.tsx | 9 +
.../scenarios/mount/react/src/routes/blog.tsx | 16 +
.../mount/react/src/routes/index.tsx | 22 +
.../mount/react/src/routes/search.tsx | 16 +
.../mount/react/src/routes/settings.tsx | 9 +
.../react/src/routes/shop.$productId.tsx | 13 +
.../mount/react/src/routes/shop.index.tsx | 9 +
.../scenarios/mount/react/src/routes/shop.tsx | 16 +
.../scenarios/mount/react/tsconfig.json | 21 +
.../scenarios/mount/react/vite.config.ts | 40 +
.../client-nav/scenarios/mount/shared.ts | 75 ++
.../scenarios/mount/solid/project.json | 40 +
.../client-nav/scenarios/mount/solid/setup.ts | 17 +
.../scenarios/mount/solid/speed.bench.ts | 34 +
.../scenarios/mount/solid/speed.flame.ts | 18 +
.../scenarios/mount/solid/src/main.tsx | 46 +
.../mount/solid/src/routeTree.gen.ts | 264 ++++++
.../mount/solid/src/routes/__root.tsx | 41 +
.../mount/solid/src/routes/about.tsx | 9 +
.../mount/solid/src/routes/blog.$slug.tsx | 13 +
.../mount/solid/src/routes/blog.index.tsx | 9 +
.../scenarios/mount/solid/src/routes/blog.tsx | 16 +
.../mount/solid/src/routes/index.tsx | 23 +
.../mount/solid/src/routes/search.tsx | 16 +
.../mount/solid/src/routes/settings.tsx | 9 +
.../solid/src/routes/shop.$productId.tsx | 13 +
.../mount/solid/src/routes/shop.index.tsx | 9 +
.../scenarios/mount/solid/src/routes/shop.tsx | 16 +
.../scenarios/mount/solid/tsconfig.json | 21 +
.../scenarios/mount/solid/vite.config.ts | 48 +
.../scenarios/mount/vue/project.json | 40 +
.../client-nav/scenarios/mount/vue/setup.ts | 17 +
.../scenarios/mount/vue/speed.bench.ts | 34 +
.../scenarios/mount/vue/speed.flame.ts | 18 +
.../scenarios/mount/vue/src/main.tsx | 51 +
.../scenarios/mount/vue/src/routeTree.gen.ts | 264 ++++++
.../scenarios/mount/vue/src/routes/__root.tsx | 44 +
.../scenarios/mount/vue/src/routes/about.tsx | 12 +
.../mount/vue/src/routes/blog.$slug.tsx | 18 +
.../mount/vue/src/routes/blog.index.tsx | 12 +
.../scenarios/mount/vue/src/routes/blog.tsx | 19 +
.../scenarios/mount/vue/src/routes/index.tsx | 27 +
.../scenarios/mount/vue/src/routes/search.tsx | 21 +
.../mount/vue/src/routes/settings.tsx | 12 +
.../mount/vue/src/routes/shop.$productId.tsx | 18 +
.../mount/vue/src/routes/shop.index.tsx | 12 +
.../scenarios/mount/vue/src/routes/shop.tsx | 19 +
.../scenarios/mount/vue/tsconfig.json | 21 +
.../scenarios/mount/vue/vite.config.ts | 42 +
.../nested-params/react/project.json | 40 +
.../scenarios/nested-params/react/setup.ts | 17 +
.../nested-params/react/speed.bench.ts | 34 +
.../nested-params/react/speed.flame.ts | 18 +
.../nested-params/react/src/main.tsx | 35 +
.../nested-params/react/src/routeTree.gen.ts | 291 ++++++
.../nested-params/react/src/routes/__root.tsx | 50 +
.../nested-params/react/src/routes/index.tsx | 9 +
.../src/routes/l.$a.$b.$c.$d.$e.$f.$g.$h.tsx | 63 ++
.../src/routes/l.$a.$b.$c.$d.$e.$f.$g.tsx | 51 +
.../react/src/routes/l.$a.$b.$c.$d.$e.$f.tsx | 51 +
.../react/src/routes/l.$a.$b.$c.$d.$e.tsx | 51 +
.../react/src/routes/l.$a.$b.$c.$d.tsx | 55 ++
.../react/src/routes/l.$a.$b.$c.tsx | 51 +
.../react/src/routes/l.$a.$b.tsx | 51 +
.../nested-params/react/src/routes/l.$a.tsx | 51 +
.../nested-params/react/tsconfig.json | 21 +
.../nested-params/react/vite.config.ts | 40 +
.../scenarios/nested-params/shared.ts | 143 +++
.../nested-params/solid/project.json | 40 +
.../scenarios/nested-params/solid/setup.ts | 17 +
.../nested-params/solid/speed.bench.ts | 34 +
.../nested-params/solid/speed.flame.ts | 18 +
.../nested-params/solid/src/main.tsx | 32 +
.../nested-params/solid/src/routeTree.gen.ts | 291 ++++++
.../nested-params/solid/src/routes/__root.tsx | 50 +
.../nested-params/solid/src/routes/index.tsx | 9 +
.../src/routes/l.$a.$b.$c.$d.$e.$f.$g.$h.tsx | 72 ++
.../src/routes/l.$a.$b.$c.$d.$e.$f.$g.tsx | 60 ++
.../solid/src/routes/l.$a.$b.$c.$d.$e.$f.tsx | 60 ++
.../solid/src/routes/l.$a.$b.$c.$d.$e.tsx | 60 ++
.../solid/src/routes/l.$a.$b.$c.$d.tsx | 64 ++
.../solid/src/routes/l.$a.$b.$c.tsx | 60 ++
.../solid/src/routes/l.$a.$b.tsx | 60 ++
.../nested-params/solid/src/routes/l.$a.tsx | 60 ++
.../nested-params/solid/tsconfig.json | 21 +
.../nested-params/solid/vite.config.ts | 48 +
.../scenarios/nested-params/vue/project.json | 40 +
.../scenarios/nested-params/vue/setup.ts | 17 +
.../nested-params/vue/speed.bench.ts | 34 +
.../nested-params/vue/speed.flame.ts | 18 +
.../scenarios/nested-params/vue/src/main.tsx | 39 +
.../nested-params/vue/src/routeTree.gen.ts | 291 ++++++
.../nested-params/vue/src/routes/__root.tsx | 53 ++
.../nested-params/vue/src/routes/index.tsx | 12 +
.../src/routes/l.$a.$b.$c.$d.$e.$f.$g.$h.tsx | 86 ++
.../vue/src/routes/l.$a.$b.$c.$d.$e.$f.$g.tsx | 74 ++
.../vue/src/routes/l.$a.$b.$c.$d.$e.$f.tsx | 74 ++
.../vue/src/routes/l.$a.$b.$c.$d.$e.tsx | 74 ++
.../vue/src/routes/l.$a.$b.$c.$d.tsx | 83 ++
.../vue/src/routes/l.$a.$b.$c.tsx | 74 ++
.../nested-params/vue/src/routes/l.$a.$b.tsx | 74 ++
.../nested-params/vue/src/routes/l.$a.tsx | 74 ++
.../scenarios/nested-params/vue/tsconfig.json | 21 +
.../nested-params/vue/vite.config.ts | 42 +
.../scenarios/preload/react/project.json | 40 +
.../scenarios/preload/react/setup.ts | 17 +
.../scenarios/preload/react/speed.bench.ts | 34 +
.../scenarios/preload/react/speed.flame.ts | 18 +
.../scenarios/preload/react/src/main.tsx | 38 +
.../preload/react/src/routeTree.gen.ts | 95 ++
.../preload/react/src/routes/__root.tsx | 32 +
.../preload/react/src/routes/docs.tsx | 14 +
.../preload/react/src/routes/index.tsx | 14 +
.../react/src/routes/sections.$section.tsx | 32 +
.../scenarios/preload/react/tsconfig.json | 21 +
.../scenarios/preload/react/vite.config.ts | 40 +
.../client-nav/scenarios/preload/shared.ts | 119 +++
.../scenarios/preload/solid/project.json | 40 +
.../scenarios/preload/solid/setup.ts | 17 +
.../scenarios/preload/solid/speed.bench.ts | 34 +
.../scenarios/preload/solid/speed.flame.ts | 18 +
.../scenarios/preload/solid/src/main.tsx | 35 +
.../preload/solid/src/routeTree.gen.ts | 95 ++
.../preload/solid/src/routes/__root.tsx | 34 +
.../preload/solid/src/routes/docs.tsx | 14 +
.../preload/solid/src/routes/index.tsx | 14 +
.../solid/src/routes/sections.$section.tsx | 33 +
.../scenarios/preload/solid/tsconfig.json | 21 +
.../scenarios/preload/solid/vite.config.ts | 48 +
.../scenarios/preload/vue/project.json | 40 +
.../client-nav/scenarios/preload/vue/setup.ts | 17 +
.../scenarios/preload/vue/speed.bench.ts | 34 +
.../scenarios/preload/vue/speed.flame.ts | 18 +
.../scenarios/preload/vue/src/main.tsx | 42 +
.../preload/vue/src/routeTree.gen.ts | 95 ++
.../preload/vue/src/routes/__root.tsx | 35 +
.../scenarios/preload/vue/src/routes/docs.tsx | 17 +
.../preload/vue/src/routes/index.tsx | 17 +
.../vue/src/routes/sections.$section.tsx | 37 +
.../scenarios/preload/vue/tsconfig.json | 21 +
.../scenarios/preload/vue/vite.config.ts | 42 +
.../scenarios/rewrites/react/project.json | 40 +
.../scenarios/rewrites/react/setup.ts | 18 +
.../scenarios/rewrites/react/speed.bench.ts | 34 +
.../scenarios/rewrites/react/speed.flame.ts | 18 +
.../scenarios/rewrites/react/src/main.tsx | 38 +
.../rewrites/react/src/routeTree.gen.ts | 103 ++
.../rewrites/react/src/routes/__root.tsx | 61 ++
.../rewrites/react/src/routes/index.tsx | 13 +
.../rewrites/react/src/routes/p.$a.$b.tsx | 16 +
.../rewrites/react/src/routes/p.$a.tsx | 17 +
.../scenarios/rewrites/react/tsconfig.json | 21 +
.../scenarios/rewrites/react/vite.config.ts | 40 +
.../client-nav/scenarios/rewrites/shared.ts | 101 ++
.../scenarios/rewrites/solid/project.json | 40 +
.../scenarios/rewrites/solid/setup.ts | 18 +
.../scenarios/rewrites/solid/speed.bench.ts | 34 +
.../scenarios/rewrites/solid/speed.flame.ts | 18 +
.../scenarios/rewrites/solid/src/main.tsx | 35 +
.../rewrites/solid/src/routeTree.gen.ts | 103 ++
.../rewrites/solid/src/routes/__root.tsx | 61 ++
.../rewrites/solid/src/routes/index.tsx | 13 +
.../rewrites/solid/src/routes/p.$a.$b.tsx | 16 +
.../rewrites/solid/src/routes/p.$a.tsx | 17 +
.../scenarios/rewrites/solid/tsconfig.json | 21 +
.../scenarios/rewrites/solid/vite.config.ts | 48 +
.../scenarios/rewrites/vue/project.json | 40 +
.../scenarios/rewrites/vue/setup.ts | 18 +
.../scenarios/rewrites/vue/speed.bench.ts | 34 +
.../scenarios/rewrites/vue/speed.flame.ts | 18 +
.../scenarios/rewrites/vue/src/main.tsx | 42 +
.../rewrites/vue/src/routeTree.gen.ts | 103 ++
.../rewrites/vue/src/routes/__root.tsx | 64 ++
.../rewrites/vue/src/routes/index.tsx | 16 +
.../rewrites/vue/src/routes/p.$a.$b.tsx | 19 +
.../rewrites/vue/src/routes/p.$a.tsx | 20 +
.../scenarios/rewrites/vue/tsconfig.json | 21 +
.../scenarios/rewrites/vue/vite.config.ts | 42 +
.../route-tree-scale/react/project.json | 40 +
.../scenarios/route-tree-scale/react/setup.ts | 17 +
.../route-tree-scale/react/speed.bench.ts | 34 +
.../route-tree-scale/react/speed.flame.ts | 18 +
.../route-tree-scale/react/src/main.tsx | 35 +
.../react/src/routeTree.gen.ts | 881 ++++++++++++++++++
.../react/src/routes/(marketing)/pricing.tsx | 9 +
.../react/src/routes/(marketing)/promo.tsx | 9 +
.../react/src/routes/__root.tsx | 58 ++
.../react/src/routes/_layout.alpha.tsx | 9 +
.../react/src/routes/_layout.beta.tsx | 9 +
.../react/src/routes/_layout.tsx | 9 +
.../react/src/routes/files.$.tsx | 11 +
.../react/src/routes/index.tsx | 9 +
.../react/src/routes/release.v{$version}.tsx | 11 +
.../react/src/routes/sec-a.$id.tsx | 11 +
.../react/src/routes/sec-a.about.tsx | 9 +
.../react/src/routes/sec-a.index.tsx | 9 +
.../react/src/routes/sec-a.settings.tsx | 9 +
.../react/src/routes/sec-a.tsx | 9 +
.../react/src/routes/sec-b.$id.tsx | 11 +
.../react/src/routes/sec-b.about.tsx | 9 +
.../react/src/routes/sec-b.index.tsx | 9 +
.../react/src/routes/sec-b.settings.tsx | 9 +
.../react/src/routes/sec-b.tsx | 9 +
.../react/src/routes/sec-c.$id.tsx | 11 +
.../react/src/routes/sec-c.about.tsx | 9 +
.../react/src/routes/sec-c.index.tsx | 9 +
.../react/src/routes/sec-c.settings.tsx | 9 +
.../react/src/routes/sec-c.tsx | 9 +
.../react/src/routes/sec-d.$id.tsx | 11 +
.../react/src/routes/sec-d.about.tsx | 9 +
.../react/src/routes/sec-d.index.tsx | 9 +
.../react/src/routes/sec-d.settings.tsx | 9 +
.../react/src/routes/sec-d.tsx | 9 +
.../react/src/routes/sec-e.$id.tsx | 11 +
.../react/src/routes/sec-e.about.tsx | 9 +
.../react/src/routes/sec-e.index.tsx | 9 +
.../react/src/routes/sec-e.settings.tsx | 9 +
.../react/src/routes/sec-e.tsx | 9 +
.../react/src/routes/sec-f.$id.tsx | 11 +
.../react/src/routes/sec-f.about.tsx | 9 +
.../react/src/routes/sec-f.index.tsx | 9 +
.../react/src/routes/sec-f.settings.tsx | 9 +
.../react/src/routes/sec-f.tsx | 9 +
.../route-tree-scale/react/tsconfig.json | 21 +
.../route-tree-scale/react/vite.config.ts | 43 +
.../scenarios/route-tree-scale/shared.ts | 64 ++
.../route-tree-scale/solid/project.json | 40 +
.../scenarios/route-tree-scale/solid/setup.ts | 17 +
.../route-tree-scale/solid/speed.bench.ts | 34 +
.../route-tree-scale/solid/speed.flame.ts | 18 +
.../route-tree-scale/solid/src/main.tsx | 32 +
.../solid/src/routeTree.gen.ts | 881 ++++++++++++++++++
.../solid/src/routes/(marketing)/pricing.tsx | 9 +
.../solid/src/routes/(marketing)/promo.tsx | 9 +
.../solid/src/routes/__root.tsx | 58 ++
.../solid/src/routes/_layout.alpha.tsx | 9 +
.../solid/src/routes/_layout.beta.tsx | 9 +
.../solid/src/routes/_layout.tsx | 9 +
.../solid/src/routes/files.$.tsx | 11 +
.../solid/src/routes/index.tsx | 9 +
.../solid/src/routes/release.v{$version}.tsx | 11 +
.../solid/src/routes/sec-a.$id.tsx | 11 +
.../solid/src/routes/sec-a.about.tsx | 9 +
.../solid/src/routes/sec-a.index.tsx | 9 +
.../solid/src/routes/sec-a.settings.tsx | 9 +
.../solid/src/routes/sec-a.tsx | 9 +
.../solid/src/routes/sec-b.$id.tsx | 11 +
.../solid/src/routes/sec-b.about.tsx | 9 +
.../solid/src/routes/sec-b.index.tsx | 9 +
.../solid/src/routes/sec-b.settings.tsx | 9 +
.../solid/src/routes/sec-b.tsx | 9 +
.../solid/src/routes/sec-c.$id.tsx | 11 +
.../solid/src/routes/sec-c.about.tsx | 9 +
.../solid/src/routes/sec-c.index.tsx | 9 +
.../solid/src/routes/sec-c.settings.tsx | 9 +
.../solid/src/routes/sec-c.tsx | 9 +
.../solid/src/routes/sec-d.$id.tsx | 11 +
.../solid/src/routes/sec-d.about.tsx | 9 +
.../solid/src/routes/sec-d.index.tsx | 9 +
.../solid/src/routes/sec-d.settings.tsx | 9 +
.../solid/src/routes/sec-d.tsx | 9 +
.../solid/src/routes/sec-e.$id.tsx | 11 +
.../solid/src/routes/sec-e.about.tsx | 9 +
.../solid/src/routes/sec-e.index.tsx | 9 +
.../solid/src/routes/sec-e.settings.tsx | 9 +
.../solid/src/routes/sec-e.tsx | 9 +
.../solid/src/routes/sec-f.$id.tsx | 11 +
.../solid/src/routes/sec-f.about.tsx | 9 +
.../solid/src/routes/sec-f.index.tsx | 9 +
.../solid/src/routes/sec-f.settings.tsx | 9 +
.../solid/src/routes/sec-f.tsx | 9 +
.../route-tree-scale/solid/tsconfig.json | 21 +
.../route-tree-scale/solid/vite.config.ts | 51 +
.../route-tree-scale/vue/project.json | 40 +
.../scenarios/route-tree-scale/vue/setup.ts | 17 +
.../route-tree-scale/vue/speed.bench.ts | 34 +
.../route-tree-scale/vue/speed.flame.ts | 18 +
.../route-tree-scale/vue/src/main.tsx | 39 +
.../route-tree-scale/vue/src/routeTree.gen.ts | 881 ++++++++++++++++++
.../vue/src/routes/(marketing)/pricing.tsx | 12 +
.../vue/src/routes/(marketing)/promo.tsx | 12 +
.../vue/src/routes/__root.tsx | 61 ++
.../vue/src/routes/_layout.alpha.tsx | 12 +
.../vue/src/routes/_layout.beta.tsx | 12 +
.../vue/src/routes/_layout.tsx | 12 +
.../vue/src/routes/files.$.tsx | 16 +
.../route-tree-scale/vue/src/routes/index.tsx | 12 +
.../vue/src/routes/release.v{$version}.tsx | 16 +
.../vue/src/routes/sec-a.$id.tsx | 16 +
.../vue/src/routes/sec-a.about.tsx | 12 +
.../vue/src/routes/sec-a.index.tsx | 12 +
.../vue/src/routes/sec-a.settings.tsx | 12 +
.../route-tree-scale/vue/src/routes/sec-a.tsx | 12 +
.../vue/src/routes/sec-b.$id.tsx | 16 +
.../vue/src/routes/sec-b.about.tsx | 12 +
.../vue/src/routes/sec-b.index.tsx | 12 +
.../vue/src/routes/sec-b.settings.tsx | 12 +
.../route-tree-scale/vue/src/routes/sec-b.tsx | 12 +
.../vue/src/routes/sec-c.$id.tsx | 16 +
.../vue/src/routes/sec-c.about.tsx | 12 +
.../vue/src/routes/sec-c.index.tsx | 12 +
.../vue/src/routes/sec-c.settings.tsx | 12 +
.../route-tree-scale/vue/src/routes/sec-c.tsx | 12 +
.../vue/src/routes/sec-d.$id.tsx | 16 +
.../vue/src/routes/sec-d.about.tsx | 12 +
.../vue/src/routes/sec-d.index.tsx | 12 +
.../vue/src/routes/sec-d.settings.tsx | 12 +
.../route-tree-scale/vue/src/routes/sec-d.tsx | 12 +
.../vue/src/routes/sec-e.$id.tsx | 16 +
.../vue/src/routes/sec-e.about.tsx | 12 +
.../vue/src/routes/sec-e.index.tsx | 12 +
.../vue/src/routes/sec-e.settings.tsx | 12 +
.../route-tree-scale/vue/src/routes/sec-e.tsx | 12 +
.../vue/src/routes/sec-f.$id.tsx | 16 +
.../vue/src/routes/sec-f.about.tsx | 12 +
.../vue/src/routes/sec-f.index.tsx | 12 +
.../vue/src/routes/sec-f.settings.tsx | 12 +
.../route-tree-scale/vue/src/routes/sec-f.tsx | 12 +
.../route-tree-scale/vue/tsconfig.json | 21 +
.../route-tree-scale/vue/vite.config.ts | 45 +
.../search-params/react/project.json | 40 +
.../scenarios/search-params/react/setup.ts | 17 +
.../search-params/react/speed.bench.ts | 34 +
.../search-params/react/speed.flame.ts | 18 +
.../search-params/react/src/main.tsx | 36 +
.../search-params/react/src/routeTree.gen.ts | 95 ++
.../search-params/react/src/routes/__root.tsx | 116 +++
.../react/src/routes/catalog.tsx | 42 +
.../search-params/react/src/routes/index.tsx | 14 +
.../react/src/routes/products.tsx | 91 ++
.../search-params/react/tsconfig.json | 21 +
.../search-params/react/vite.config.ts | 40 +
.../scenarios/search-params/shared.ts | 200 ++++
.../search-params/solid/project.json | 40 +
.../scenarios/search-params/solid/setup.ts | 17 +
.../search-params/solid/speed.bench.ts | 34 +
.../search-params/solid/speed.flame.ts | 18 +
.../search-params/solid/src/main.tsx | 33 +
.../search-params/solid/src/routeTree.gen.ts | 95 ++
.../search-params/solid/src/routes/__root.tsx | 120 +++
.../solid/src/routes/catalog.tsx | 50 +
.../search-params/solid/src/routes/index.tsx | 14 +
.../solid/src/routes/products.tsx | 97 ++
.../search-params/solid/tsconfig.json | 21 +
.../search-params/solid/vite.config.ts | 48 +
.../scenarios/search-params/vue/project.json | 40 +
.../scenarios/search-params/vue/setup.ts | 17 +
.../search-params/vue/speed.bench.ts | 34 +
.../search-params/vue/speed.flame.ts | 18 +
.../scenarios/search-params/vue/src/main.tsx | 40 +
.../search-params/vue/src/routeTree.gen.ts | 95 ++
.../search-params/vue/src/routes/__root.tsx | 114 +++
.../search-params/vue/src/routes/catalog.tsx | 49 +
.../search-params/vue/src/routes/index.tsx | 17 +
.../search-params/vue/src/routes/products.tsx | 108 +++
.../scenarios/search-params/vue/tsconfig.json | 21 +
.../search-params/vue/vite.config.ts | 42 +
benchmarks/client-nav/solid/vite.config.ts | 8 +-
benchmarks/client-nav/vitest.config.ts | 3 +
benchmarks/client-nav/vitest.react.config.ts | 8 +
benchmarks/client-nav/vitest.solid.config.ts | 8 +
benchmarks/client-nav/vitest.vue.config.ts | 8 +
benchmarks/client-nav/vue/vite.config.ts | 8 +-
pnpm-lock.yaml | 3 +
627 files changed, 25216 insertions(+), 13 deletions(-)
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/ctx.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/nested.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/nested.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/slow.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/ctx.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/nested.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/nested.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/slow.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/ctx.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/nested.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/nested.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/slow.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/async-pipeline/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/routes/broken.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/routes/hop1.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/routes/hop2.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/routes/missing.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/src/routes/target.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/control-flow/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/routes/broken.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/routes/hop1.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/routes/hop2.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/routes/missing.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/src/routes/target.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/control-flow/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/routes/broken.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/routes/hop1.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/routes/hop2.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/routes/missing.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/src/routes/target.tsx
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/control-flow/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/harness.ts
create mode 100644 benchmarks/client-nav/scenarios/head/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/head/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/head/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/head/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/head/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/head/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/react/src/routes/articles.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/react/src/routes/docs.$section.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/react/src/routes/docs.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/react/src/routes/settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/head/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/head/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/head/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/head/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/head/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/head/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/head/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/head/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/solid/src/routes/articles.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/solid/src/routes/docs.$section.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/solid/src/routes/docs.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/solid/src/routes/settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/head/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/head/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/head/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/head/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/head/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/head/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/head/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/vue/src/routes/articles.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/vue/src/routes/docs.$section.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/vue/src/routes/docs.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/vue/src/routes/settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/head/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/head/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/history/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/history/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/history/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/history/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/history/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/history/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/react/src/routes/gallery.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/react/src/routes/pages.$n.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/react/src/routes/photos.$photoId.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/history/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/history/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/history/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/history/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/history/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/history/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/history/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/history/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/solid/src/routes/gallery.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/solid/src/routes/pages.$n.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/solid/src/routes/photos.$photoId.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/history/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/history/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/history/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/history/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/history/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/history/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/history/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/vue/src/routes/gallery.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/vue/src/routes/pages.$n.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/vue/src/routes/photos.$photoId.tsx
create mode 100644 benchmarks/client-nav/scenarios/history/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/history/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/links/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/links/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/links/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/links/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/links/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/links/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/react/src/routes/about.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/react/src/routes/items.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/links/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/links/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/links/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/links/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/links/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/links/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/links/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/links/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/solid/src/routes/about.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/solid/src/routes/items.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/links/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/links/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/links/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/links/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/links/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/links/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/links/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/vue/src/routes/about.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/vue/src/routes/items.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/links/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/links/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/src/routes/cached.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/src/routes/deps.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/src/routes/fresh.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/loaders/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/src/routes/cached.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/src/routes/deps.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/src/routes/fresh.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/loaders/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/src/routes/cached.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/src/routes/deps.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/src/routes/fresh.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/loaders/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/mount/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/about.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/blog.$slug.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/blog.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/blog.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/search.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/shop.$productId.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/shop.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/src/routes/shop.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/mount/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/about.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/blog.$slug.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/blog.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/blog.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/search.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/shop.$productId.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/shop.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/src/routes/shop.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/mount/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/about.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/blog.$slug.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/blog.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/blog.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/search.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/shop.$productId.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/shop.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/src/routes/shop.tsx
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/mount/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/l.$a.$b.$c.$d.$e.$f.$g.$h.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/l.$a.$b.$c.$d.$e.$f.$g.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/l.$a.$b.$c.$d.$e.$f.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/l.$a.$b.$c.$d.$e.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/l.$a.$b.$c.$d.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/l.$a.$b.$c.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/l.$a.$b.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/src/routes/l.$a.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/nested-params/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/l.$a.$b.$c.$d.$e.$f.$g.$h.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/l.$a.$b.$c.$d.$e.$f.$g.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/l.$a.$b.$c.$d.$e.$f.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/l.$a.$b.$c.$d.$e.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/l.$a.$b.$c.$d.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/l.$a.$b.$c.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/l.$a.$b.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/src/routes/l.$a.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/nested-params/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/l.$a.$b.$c.$d.$e.$f.$g.$h.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/l.$a.$b.$c.$d.$e.$f.$g.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/l.$a.$b.$c.$d.$e.$f.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/l.$a.$b.$c.$d.$e.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/l.$a.$b.$c.$d.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/l.$a.$b.$c.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/l.$a.$b.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/src/routes/l.$a.tsx
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/nested-params/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/preload/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/react/src/routes/docs.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/react/src/routes/sections.$section.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/preload/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/src/routes/docs.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/src/routes/sections.$section.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/preload/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/src/routes/docs.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/src/routes/sections.$section.tsx
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/preload/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/src/routes/p.$a.$b.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/src/routes/p.$a.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/rewrites/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/src/routes/p.$a.$b.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/src/routes/p.$a.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/rewrites/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/src/routes/p.$a.$b.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/src/routes/p.$a.tsx
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/rewrites/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/(marketing)/pricing.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/(marketing)/promo.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/_layout.alpha.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/_layout.beta.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/_layout.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/files.$.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/release.v{$version}.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-a.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-a.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-a.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-a.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-a.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-b.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-b.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-b.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-b.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-b.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-c.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-c.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-c.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-c.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-c.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-d.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-d.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-d.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-d.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-d.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-e.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-e.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-e.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-e.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-e.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-f.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-f.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-f.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-f.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/src/routes/sec-f.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/(marketing)/pricing.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/(marketing)/promo.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/_layout.alpha.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/_layout.beta.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/_layout.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/files.$.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/release.v{$version}.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-a.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-a.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-a.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-a.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-a.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-b.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-b.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-b.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-b.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-b.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-c.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-c.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-c.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-c.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-c.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-d.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-d.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-d.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-d.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-d.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-e.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-e.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-e.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-e.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-e.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-f.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-f.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-f.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-f.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/src/routes/sec-f.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/(marketing)/pricing.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/(marketing)/promo.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/_layout.alpha.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/_layout.beta.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/_layout.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/files.$.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/release.v{$version}.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-a.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-a.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-a.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-a.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-a.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-b.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-b.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-b.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-b.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-b.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-c.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-c.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-c.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-c.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-c.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-d.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-d.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-d.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-d.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-d.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-e.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-e.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-e.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-e.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-e.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-f.$id.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-f.about.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-f.index.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-f.settings.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/src/routes/sec-f.tsx
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/route-tree-scale/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/project.json
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/src/routes/catalog.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/src/routes/products.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/search-params/react/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/shared.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/project.json
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/src/routes/catalog.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/src/routes/products.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/search-params/solid/vite.config.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/project.json
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/setup.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/speed.bench.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/speed.flame.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/src/main.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/src/routeTree.gen.ts
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/src/routes/__root.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/src/routes/catalog.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/src/routes/index.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/src/routes/products.tsx
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/tsconfig.json
create mode 100644 benchmarks/client-nav/scenarios/search-params/vue/vite.config.ts
create mode 100644 benchmarks/client-nav/vitest.react.config.ts
create mode 100644 benchmarks/client-nav/vitest.solid.config.ts
create mode 100644 benchmarks/client-nav/vitest.vue.config.ts
diff --git a/benchmarks/client-nav/README.md b/benchmarks/client-nav/README.md
index 44b58107e3..6179c7f0b8 100644
--- a/benchmarks/client-nav/README.md
+++ b/benchmarks/client-nav/README.md
@@ -1,16 +1,80 @@
# Client Navigation Benchmarks
-Cross-framework client-side navigation benchmarks for:
+Cross-framework client-side CPU benchmarks for:
- `@tanstack/react-router`
- `@tanstack/solid-router`
- `@tanstack/vue-router`
+The benchmarks run in jsdom against production builds of real apps, and are
+tracked in CI by CodSpeed (simulation mode).
+
+> **Scope:** these benchmarks cover the standalone client router only. The
+> client side of TanStack Start (hydration of a server-rendered document,
+> streamed payload consumption, server-function calls from the client, ...) is
+> not covered here; the server side of Start is covered by `benchmarks/ssr`.
+
## Layout
-- `react/` - React benchmark + Vitest config
-- `solid/` - Solid benchmark + Vitest config
-- `vue/` - Vue benchmark + Vitest config
+- `react/`, `solid/`, `vue/` - baseline benchmark (mixed navigation loop) + Vitest config
+- `vitest.react.config.ts`, `vitest.solid.config.ts`, `vitest.vue.config.ts` - per-framework aggregate configs that run the baseline first, then scenario projects
+- `scenarios/harness.ts` - shared scenario runner (mount, link-click steps, `onRendered` synchronization)
+- `scenarios//shared.ts` - framework-agnostic scenario definition (workload data, step sequence, assertions, bench options)
+- `scenarios///` - isolated scenario apps
+
+Scenario app layout:
+
+```text
+scenarios///
+ vite.config.ts
+ speed.bench.ts
+ speed.flame.ts
+ setup.ts
+ project.json
+ tsconfig.json
+ src/
+ main.tsx
+ routeTree.gen.ts
+ routes/
+```
+
+Scenario apps use file-based routing (`@tanstack/router-plugin`) with a
+generated `routeTree.gen.ts`, like a regular user app. Each scenario uses one
+app per framework instead of sharing routes in the baseline app. This keeps
+route-tree size and router options isolated so one scenario cannot shift
+another scenario's numbers. The existing baseline apps and bench names stay
+stable for CodSpeed continuity.
+
+## Scenario Responsibilities
+
+Each scenario isolates one client-side responsibility so benchmark changes can
+be attributed to a specific feature area.
+
+| Scenario | Client-side responsibility |
+| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `react/`, `solid/`, `vue/` baseline apps | Mixed navigation loop: path params, search params, route context, and `useParams`/`useSearch`/`useLoaderData` selector subscriptions. |
+| `async-pipeline` | The router's async pipeline via counted 0ms timer hops: async loaders (transition-held navigation), async `beforeLoad` context, and parallel nested async loaders. Component-level `Await`/Suspense is excluded: React 19 throttles Suspense reveals by ~300ms wall-clock, which is inherently non-deterministic to benchmark. |
+| `control-flow` | Loader-thrown `redirect` (including a 2-hop chain), `notFound()` with `notFoundComponent`, loader errors with `errorComponent`, and boundary reset on recovery navigation. |
+| `head` | `HeadContent` per-navigation work: nested route `head()` evaluation, title/meta/link dedupe across matches, and head tag DOM updates during navigation. |
+| `history` | History push/replace/back/forward traversal, location masking, registered-but-never-blocking `useBlocker`, and `useCanGoBack`/`useLocation` subscriptions. |
+| `links` | Per-navigation cost of ~200 mounted ``s: link prop building, active-state recompute across `activeOptions` variants, `activeProps` swaps, and `useMatchRoute` probes (the `MatchRoute` component is avoided: vue-router's implementation leaks one subscription per render). |
+| `loaders` | Client loader dispatch: always-stale re-runs (`staleTime: 0`), cached revisits (re-run once per lap by the `invalidate` step), `loaderDeps`-keyed caching, `router.invalidate()`, and `useLoaderData` selectors. |
+| `mount` | Cold start: `createRouter` (route-tree processing) + first render + initial `router.load()` + unmount, with a fresh router per mount. The only scenario measuring router creation. |
+| `nested-params` | Deep nesting (8 dynamic levels): per-level `params.parse`/`stringify`, `beforeLoad` context accumulation across matches, and per-level `useParams`/`useRouteContext` subscriptions. Param values include characters requiring percent-encoding (as do `route-tree-scale`'s), so segment encode/decode paths run on every navigation. |
+| `preload` | Intent preloading from hover events, programmatic `router.preloadRoute`, deterministic preload cache behavior (`defaultPreloadStaleTime: 0`), and commit-time cache maintenance. |
+| `rewrites` | Composed client-side location rewrites: router `basepath` plus a locale input/output rewrite pair, running on every href build and location parse (the client analog of the SSR `rewrites` scenario). |
+| `route-tree-scale` | Route matching and link-target resolution on a wide (~40 route) tree mixing static, dynamic, prefixed-param, splat, pathless-layout, and route-group paths, with `autoCodeSplitting` enabled so navigations also resolve lazy route chunks. |
+| `search-params` | `validateSearch` execution, search middlewares (`retainSearchParams`/`stripSearchParams`), functional search updaters, structural sharing, and `useSearch` selector subscriptions. |
+
+## Conventions
+
+- Apps are built with `NODE_ENV=production` (`minify: false`) into `dist/app.js`; benches import the built bundle, so production package builds and production JSX output are measured, not dev transforms.
+- Scenarios behave like a real user app: navigation happens through `` clicks dispatched on real anchor elements (unless a scenario specifically measures the imperative API), the router uses the default browser history, and `scrollRestoration` is enabled.
+- Each benchmark iteration advances a fixed, circular sequence of steps; every step awaits the router's `onRendered` event, so render work is included and steps cannot overlap. No two consecutive steps may target the same location, and the sequence ends back on the initial route.
+- Setup runs one warm-up lap through the sequence and asserts each step's observable output (e.g. `document.title`), so a scenario that silently stops doing its work fails instead of reporting a fast time.
+- Determinism: no wall-clock timers (async work is resolved promises or counted `setTimeout(0)` hops), `staleTime`/`gcTime` only ever `0` or effectively-infinite, no `Math.random`/`Date.now` in the measured loop. Scroll restoration uses `getScrollRestorationKey: (location) => location.pathname` so its cache stays bounded (the default random per-entry key grows one cache entry per push navigation). The `mount` scenario opts out of `scrollRestoration` entirely and destroys its history on unmount — both register page-lifetime globals that would leak across its mount/unmount loop.
+- Push-only step laps grow jsdom's session-history entry list over a run (jsdom never truncates entries behind the current index). Per-push cost stays O(1) so timings are unaffected; only the `history` scenario needs — and has — a lap that is depth-stationary.
+- Calibration: pick per-iteration step counts so a bench's `vitest bench` run stays roughly between 8 and 30 seconds (long enough to average out, short enough for CI).
## Run
@@ -20,7 +84,7 @@ Run all benchmarks through Nx so dependency builds are part of the graph:
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf --outputStyle=stream --skipRemoteCache
```
-Run framework-specific benchmarks:
+Run framework-specific benchmarks (baseline + all scenarios):
```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf:react --outputStyle=stream --skipRemoteCache
@@ -28,15 +92,25 @@ CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf:solid --output
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf:vue --outputStyle=stream --skipRemoteCache
```
+Run a single scenario app manually (after building it through Nx):
+
+```bash
+CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav--:build:client --outputStyle=stream --skipRemoteCache
+cd benchmarks/client-nav && NODE_ENV=production vitest bench --config ./scenarios///vite.config.ts
+```
+
Run framework-specific flame benchmarks (10 second loop, profiled with `@platformatic/flame`, forced to `NODE_ENV=production`):
```bash
+# baseline
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:flame:react --outputStyle=stream --skipRemoteCache
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:flame:solid --outputStyle=stream --skipRemoteCache
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:flame:vue --outputStyle=stream --skipRemoteCache
+# scenarios
+CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav--:test:flame --outputStyle=stream --skipRemoteCache
```
-Typecheck benchmark sources:
+Typecheck benchmark sources (baseline + scenarios):
```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:types --outputStyle=stream --skipRemoteCache
diff --git a/benchmarks/client-nav/jsdom.ts b/benchmarks/client-nav/jsdom.ts
index 05535e693d..855c2a2316 100644
--- a/benchmarks/client-nav/jsdom.ts
+++ b/benchmarks/client-nav/jsdom.ts
@@ -30,6 +30,11 @@ setGlobal('MutationObserver', window.MutationObserver)
setGlobal('sessionStorage', window.sessionStorage)
setGlobal('localStorage', window.localStorage)
setGlobal('getComputedStyle', window.getComputedStyle.bind(window))
+setGlobal('addEventListener', window.addEventListener.bind(window))
+setGlobal('removeEventListener', window.removeEventListener.bind(window))
+setGlobal('dispatchEvent', window.dispatchEvent.bind(window))
+setGlobal('CustomEvent', window.CustomEvent)
+setGlobal('Event', window.Event)
setGlobal(
'requestAnimationFrame',
@@ -45,5 +50,6 @@ setGlobal(
)
window.scrollTo = () => {}
+setGlobal('scrollTo', () => {})
export { window }
diff --git a/benchmarks/client-nav/package.json b/benchmarks/client-nav/package.json
index cb86e70654..701e068b49 100644
--- a/benchmarks/client-nav/package.json
+++ b/benchmarks/client-nav/package.json
@@ -10,9 +10,9 @@
"test:flame:solid": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./solid/speed.flame.ts",
"test:flame:vue": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./vue/speed.flame.ts",
"test:perf": "NODE_ENV=production vitest bench --config ./vitest.config.ts",
- "test:perf:react": "NODE_ENV=production vitest bench --config ./react/vite.config.ts ./react/speed.bench.ts",
- "test:perf:solid": "NODE_ENV=production vitest bench --config ./solid/vite.config.ts ./solid/speed.bench.ts",
- "test:perf:vue": "NODE_ENV=production vitest bench --config ./vue/vite.config.ts ./vue/speed.bench.ts",
+ "test:perf:react": "NODE_ENV=production vitest bench --config ./vitest.react.config.ts",
+ "test:perf:solid": "NODE_ENV=production vitest bench --config ./vitest.solid.config.ts",
+ "test:perf:vue": "NODE_ENV=production vitest bench --config ./vitest.vue.config.ts",
"test:types": "pnpm run test:types:react && pnpm run test:types:solid && pnpm run test:types:vue",
"test:types:react": "tsc -p ./react/tsconfig.json --noEmit",
"test:types:solid": "tsc -p ./solid/tsconfig.json --noEmit",
@@ -31,6 +31,7 @@
"devDependencies": {
"@platformatic/flame": "^1.6.0",
"@codspeed/vitest-plugin": "^5.5.0",
+ "@tanstack/router-plugin": "workspace:^",
"@testing-library/react": "^16.2.0",
"@vitejs/plugin-react": "^6.0.1",
"@vitejs/plugin-vue": "^6.0.5",
@@ -51,6 +52,23 @@
"@tanstack/react-router"
],
"target": "build"
+ },
+ {
+ "projects": [
+ "@benchmarks/client-nav-async-pipeline-react",
+ "@benchmarks/client-nav-control-flow-react",
+ "@benchmarks/client-nav-head-react",
+ "@benchmarks/client-nav-history-react",
+ "@benchmarks/client-nav-links-react",
+ "@benchmarks/client-nav-loaders-react",
+ "@benchmarks/client-nav-mount-react",
+ "@benchmarks/client-nav-nested-params-react",
+ "@benchmarks/client-nav-preload-react",
+ "@benchmarks/client-nav-rewrites-react",
+ "@benchmarks/client-nav-route-tree-scale-react",
+ "@benchmarks/client-nav-search-params-react"
+ ],
+ "target": "build:client"
}
]
},
@@ -62,6 +80,23 @@
"@tanstack/solid-router"
],
"target": "build"
+ },
+ {
+ "projects": [
+ "@benchmarks/client-nav-async-pipeline-solid",
+ "@benchmarks/client-nav-control-flow-solid",
+ "@benchmarks/client-nav-head-solid",
+ "@benchmarks/client-nav-history-solid",
+ "@benchmarks/client-nav-links-solid",
+ "@benchmarks/client-nav-loaders-solid",
+ "@benchmarks/client-nav-mount-solid",
+ "@benchmarks/client-nav-nested-params-solid",
+ "@benchmarks/client-nav-preload-solid",
+ "@benchmarks/client-nav-rewrites-solid",
+ "@benchmarks/client-nav-route-tree-scale-solid",
+ "@benchmarks/client-nav-search-params-solid"
+ ],
+ "target": "build:client"
}
]
},
@@ -73,6 +108,23 @@
"@tanstack/vue-router"
],
"target": "build"
+ },
+ {
+ "projects": [
+ "@benchmarks/client-nav-async-pipeline-vue",
+ "@benchmarks/client-nav-control-flow-vue",
+ "@benchmarks/client-nav-head-vue",
+ "@benchmarks/client-nav-history-vue",
+ "@benchmarks/client-nav-links-vue",
+ "@benchmarks/client-nav-loaders-vue",
+ "@benchmarks/client-nav-mount-vue",
+ "@benchmarks/client-nav-nested-params-vue",
+ "@benchmarks/client-nav-preload-vue",
+ "@benchmarks/client-nav-rewrites-vue",
+ "@benchmarks/client-nav-route-tree-scale-vue",
+ "@benchmarks/client-nav-search-params-vue"
+ ],
+ "target": "build:client"
}
]
},
@@ -123,7 +175,48 @@
"test:types": {
"cache": false,
"dependsOn": [
- "^build"
+ "^build",
+ {
+ "projects": [
+ "@benchmarks/client-nav-async-pipeline-react",
+ "@benchmarks/client-nav-async-pipeline-solid",
+ "@benchmarks/client-nav-async-pipeline-vue",
+ "@benchmarks/client-nav-control-flow-react",
+ "@benchmarks/client-nav-control-flow-solid",
+ "@benchmarks/client-nav-control-flow-vue",
+ "@benchmarks/client-nav-head-react",
+ "@benchmarks/client-nav-head-solid",
+ "@benchmarks/client-nav-head-vue",
+ "@benchmarks/client-nav-history-react",
+ "@benchmarks/client-nav-history-solid",
+ "@benchmarks/client-nav-history-vue",
+ "@benchmarks/client-nav-links-react",
+ "@benchmarks/client-nav-links-solid",
+ "@benchmarks/client-nav-links-vue",
+ "@benchmarks/client-nav-loaders-react",
+ "@benchmarks/client-nav-loaders-solid",
+ "@benchmarks/client-nav-loaders-vue",
+ "@benchmarks/client-nav-mount-react",
+ "@benchmarks/client-nav-mount-solid",
+ "@benchmarks/client-nav-mount-vue",
+ "@benchmarks/client-nav-nested-params-react",
+ "@benchmarks/client-nav-nested-params-solid",
+ "@benchmarks/client-nav-nested-params-vue",
+ "@benchmarks/client-nav-preload-react",
+ "@benchmarks/client-nav-preload-solid",
+ "@benchmarks/client-nav-preload-vue",
+ "@benchmarks/client-nav-rewrites-react",
+ "@benchmarks/client-nav-route-tree-scale-react",
+ "@benchmarks/client-nav-rewrites-solid",
+ "@benchmarks/client-nav-route-tree-scale-solid",
+ "@benchmarks/client-nav-rewrites-vue",
+ "@benchmarks/client-nav-route-tree-scale-vue",
+ "@benchmarks/client-nav-search-params-react",
+ "@benchmarks/client-nav-search-params-solid",
+ "@benchmarks/client-nav-search-params-vue"
+ ],
+ "target": "test:types:client"
+ }
]
}
}
diff --git a/benchmarks/client-nav/react/vite.config.ts b/benchmarks/client-nav/react/vite.config.ts
index ffa1d41be8..adc31ddd64 100644
--- a/benchmarks/client-nav/react/vite.config.ts
+++ b/benchmarks/client-nav/react/vite.config.ts
@@ -1,8 +1,14 @@
+import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import codspeedPlugin from '@codspeed/vitest-plugin'
+// Anchor the project root to the package directory so this config resolves
+// identically when run directly and as part of an aggregate `projects` config.
+const rootDir = fileURLToPath(new URL('..', import.meta.url))
+
export default defineConfig({
+ root: rootDir,
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
@@ -25,6 +31,6 @@ export default defineConfig({
name: '@benchmarks/client-nav (react)',
watch: false,
environment: 'jsdom',
- setupFiles: ['./vitest.setup.ts'],
+ setupFiles: [`${rootDir}vitest.setup.ts`],
},
})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/project.json b/benchmarks/client-nav/scenarios/async-pipeline/react/project.json
new file mode 100644
index 0000000000..44ab55ccfe
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-async-pipeline-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/async-pipeline/react/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/setup.ts b/benchmarks/client-nav/scenarios/async-pipeline/react/setup.ts
new file mode 100644
index 0000000000..130583b190
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'React',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/speed.bench.ts b/benchmarks/client-nav/scenarios/async-pipeline/react/speed.bench.ts
new file mode 100644
index 0000000000..68c5f97c2f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-async-pipeline', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-async-pipeline navigation loop (react)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/speed.flame.ts b/benchmarks/client-nav/scenarios/async-pipeline/react/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/main.tsx b/benchmarks/client-nav/scenarios/async-pipeline/react/src/main.tsx
new file mode 100644
index 0000000000..4b141859e6
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/main.tsx
@@ -0,0 +1,37 @@
+import { RouterProvider, createRouter } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ defaultPendingMs: 0,
+ defaultPendingMinMs: 0,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const reactRoot = createRoot(container)
+ reactRoot.render()
+
+ return {
+ router,
+ unmount() {
+ reactRoot.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..2b3336859a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routeTree.gen.ts
@@ -0,0 +1,140 @@
+/* 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 NestedRouteImport } from './routes/nested'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as SlowIdRouteImport } from './routes/slow.$id'
+import { Route as NestedIdRouteImport } from './routes/nested.$id'
+import { Route as CtxIdRouteImport } from './routes/ctx.$id'
+
+const NestedRoute = NestedRouteImport.update({
+ id: '/nested',
+ path: '/nested',
+ getParentRoute: () => rootRouteImport,
+} as any)
+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 NestedIdRoute = NestedIdRouteImport.update({
+ id: '/$id',
+ path: '/$id',
+ getParentRoute: () => NestedRoute,
+} as any)
+const CtxIdRoute = CtxIdRouteImport.update({
+ id: '/ctx/$id',
+ path: '/ctx/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ id: '__root__' | '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ NestedRoute: typeof NestedRouteWithChildren
+ CtxIdRoute: typeof CtxIdRoute
+ SlowIdRoute: typeof SlowIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/nested': {
+ id: '/nested'
+ path: '/nested'
+ fullPath: '/nested'
+ preLoaderRoute: typeof NestedRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ 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
+ }
+ '/nested/$id': {
+ id: '/nested/$id'
+ path: '/$id'
+ fullPath: '/nested/$id'
+ preLoaderRoute: typeof NestedIdRouteImport
+ parentRoute: typeof NestedRoute
+ }
+ '/ctx/$id': {
+ id: '/ctx/$id'
+ path: '/ctx/$id'
+ fullPath: '/ctx/$id'
+ preLoaderRoute: typeof CtxIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+interface NestedRouteChildren {
+ NestedIdRoute: typeof NestedIdRoute
+}
+
+const NestedRouteChildren: NestedRouteChildren = {
+ NestedIdRoute: NestedIdRoute,
+}
+
+const NestedRouteWithChildren =
+ NestedRoute._addFileChildren(NestedRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ NestedRoute: NestedRouteWithChildren,
+ CtxIdRoute: CtxIdRoute,
+ SlowIdRoute: SlowIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..1cc8566678
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/__root.tsx
@@ -0,0 +1,50 @@
+import { Link, Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+const ids = ['1', '2']
+
+function RootComponent() {
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/ctx.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/ctx.$id.tsx
new file mode 100644
index 0000000000..d644376b7b
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/ctx.$id.tsx
@@ -0,0 +1,29 @@
+import { createFileRoute } from '@tanstack/react-router'
+import {
+ ctxSeedValue,
+ ctxStateValue,
+ deferredHops,
+ hopDelay,
+} from '../../../shared'
+
+export const Route = createFileRoute('/ctx/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ beforeLoad: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { ctxSeed: ctxSeedValue(params.id) }
+ },
+ loader: ({ params, context }) => {
+ if (context.ctxSeed !== ctxSeedValue(params.id)) {
+ throw new Error('beforeLoad context missing in loader')
+ }
+ return { value: ctxStateValue(params.id) }
+ },
+ component: CtxPage,
+})
+
+function CtxPage() {
+ const data = Route.useLoaderData()
+
+ return {data.value}
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/index.tsx b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/index.tsx
new file mode 100644
index 0000000000..5b6d70e744
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ home
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/nested.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/nested.$id.tsx
new file mode 100644
index 0000000000..bc002c303f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/nested.$id.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { deferredHops, hopDelay, nestedStateValue } from '../../../shared'
+
+export const Route = createFileRoute('/nested/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { value: nestedStateValue(params.id) }
+ },
+ component: NestedPage,
+})
+
+function NestedPage() {
+ const data = Route.useLoaderData()
+
+ return {data.value}
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/nested.tsx b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/nested.tsx
new file mode 100644
index 0000000000..443f592373
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/nested.tsx
@@ -0,0 +1,23 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import { deferredHops, hopDelay, nestedLayoutValue } from '../../../shared'
+
+export const Route = createFileRoute('/nested')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async () => {
+ await hopDelay(deferredHops)
+ return { value: nestedLayoutValue() }
+ },
+ component: NestedLayout,
+})
+
+function NestedLayout() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/slow.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/slow.$id.tsx
new file mode 100644
index 0000000000..4839b0ca6d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routes/slow.$id.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { deferredHops, hopDelay, slowStateValue } from '../../../shared'
+
+export const Route = createFileRoute('/slow/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { value: slowStateValue(params.id) }
+ },
+ component: SlowPage,
+})
+
+function SlowPage() {
+ const data = Route.useLoaderData()
+
+ return {data.value}
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/tsconfig.json b/benchmarks/client-nav/scenarios/async-pipeline/react/tsconfig.json
new file mode 100644
index 0000000000..1d59cbb450
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/vite.config.ts b/benchmarks/client-nav/scenarios/async-pipeline/react/vite.config.ts
new file mode 100644
index 0000000000..7161c9a13d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/react/vite.config.ts
@@ -0,0 +1,40 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'react',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav async-pipeline (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/shared.ts b/benchmarks/client-nav/scenarios/async-pipeline/shared.ts
new file mode 100644
index 0000000000..02123309b2
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/shared.ts
@@ -0,0 +1,113 @@
+/**
+ * Shared definition of the `async-pipeline` scenario: the router's async
+ * load pipeline — fully async loaders (transition-held navigations), async
+ * `beforeLoad` context building, and parallel nested async loaders. All async
+ * work resolves through counted 0ms timer hops so the workload is
+ * deterministic. The three framework apps consume these builders so the
+ * workload is identical modulo the rendering layer.
+ *
+ * Note: component-level deferred consumption (`Await`/Suspense) is
+ * intentionally NOT covered. React throttles every Suspense reveal (with or
+ * without a fallback, including `pendingComponent`) by ~300ms of wall-clock
+ * time, which cannot be made deterministic; the async work is exercised
+ * through the router's own pipeline instead.
+ */
+import type { ScenarioStep } from '../harness'
+
+/**
+ * Resolves after exactly `hops` chained macrotask turns. Uses `setImmediate`
+ * rather than `setTimeout(0)`: both are counted, deterministic event-loop
+ * turns, but timers cost ~3-4 syscalls per hop (timerfd + epoll) and CodSpeed
+ * excludes syscall time from the measure — inconsistently past a threshold —
+ * which destabilized the hop-heavy benches.
+ */
+export function hopDelay(hops: number): Promise {
+ let promise = Promise.resolve()
+ for (let i = 0; i < hops; i++) {
+ promise = promise.then(
+ () => new Promise((resolve) => setImmediate(() => resolve())),
+ )
+ }
+ return promise
+}
+
+/** Number of 0ms hops every async loader/beforeLoad takes before resolving. */
+export const deferredHops = 2
+
+function checksum(seed: number) {
+ let value = seed | 0
+ for (let index = 0; index < 20; index++) {
+ value = (value * 1664525 + 1013904223 + index) >>> 0
+ }
+ return value
+}
+
+export function slowStateValue(id: string) {
+ return `state-${id}-${checksum(Number(id) * 71 + 3)}`
+}
+
+export function ctxSeedValue(id: string) {
+ return checksum(Number(id) * 13 + 1)
+}
+
+export function ctxStateValue(id: string) {
+ return `ctx-${id}-${checksum(ctxSeedValue(id) + 5)}`
+}
+
+export function nestedLayoutValue() {
+ return `layout-${checksum(97)}`
+}
+
+export function nestedStateValue(id: string) {
+ return `nested-${id}-${checksum(Number(id) * 41 + 11)}`
+}
+
+function markerText(testId: string) {
+ return (
+ document.querySelector(`[data-testid="${testId}"]`)?.textContent ??
+ undefined
+ )
+}
+
+function step(testId: string, isSettled: () => boolean): ScenarioStep {
+ return { type: 'click', testId, isSettled }
+}
+
+export const steps: ReadonlyArray = [
+ step('go-slow-1', () => markerText('slow-state') === slowStateValue('1')),
+ step('go-ctx-1', () => markerText('ctx-state') === ctxStateValue('1')),
+ step(
+ 'go-nested-1',
+ () =>
+ markerText('nested-layout') === nestedLayoutValue() &&
+ markerText('nested-state') === nestedStateValue('1'),
+ ),
+ step('go-slow-2', () => markerText('slow-state') === slowStateValue('2')),
+ step('go-ctx-2', () => markerText('ctx-state') === ctxStateValue('2')),
+ step(
+ 'go-nested-2',
+ () => markerText('nested-state') === nestedStateValue('2'),
+ ),
+ step('go-home', () => markerText('home-state') === 'home'),
+]
+
+export function assertStepResult(stepIndex: number) {
+ const current = steps[stepIndex]!
+ if (typeof current === 'string' || !('isSettled' in current)) {
+ return
+ }
+ if (!current.isSettled!()) {
+ throw new Error(
+ `Unexpected document state after async-pipeline step ${stepIndex}`,
+ )
+ }
+}
+
+// Two laps through the 7-step sequence per benchmark iteration.
+export const ticksPerIteration = 14
+
+export const benchOptions = {
+ warmupIterations: 100,
+ time: 10_000,
+ throws: true,
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/project.json b/benchmarks/client-nav/scenarios/async-pipeline/solid/project.json
new file mode 100644
index 0000000000..2fce426b1e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-async-pipeline-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/async-pipeline/solid/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/setup.ts b/benchmarks/client-nav/scenarios/async-pipeline/solid/setup.ts
new file mode 100644
index 0000000000..6a46971603
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Solid',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/speed.bench.ts b/benchmarks/client-nav/scenarios/async-pipeline/solid/speed.bench.ts
new file mode 100644
index 0000000000..d1f620f447
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-async-pipeline', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-async-pipeline navigation loop (solid)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/speed.flame.ts b/benchmarks/client-nav/scenarios/async-pipeline/solid/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/src/main.tsx b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/main.tsx
new file mode 100644
index 0000000000..92dcc74d0a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/main.tsx
@@ -0,0 +1,34 @@
+import { render } from 'solid-js/web'
+import { RouterProvider, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ defaultPendingMs: 0,
+ defaultPendingMinMs: 0,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const unmount = render(() => , container)
+
+ return {
+ router,
+ unmount,
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..7c15baf4e9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routeTree.gen.ts
@@ -0,0 +1,140 @@
+/* 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 NestedRouteImport } from './routes/nested'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as SlowIdRouteImport } from './routes/slow.$id'
+import { Route as NestedIdRouteImport } from './routes/nested.$id'
+import { Route as CtxIdRouteImport } from './routes/ctx.$id'
+
+const NestedRoute = NestedRouteImport.update({
+ id: '/nested',
+ path: '/nested',
+ getParentRoute: () => rootRouteImport,
+} as any)
+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 NestedIdRoute = NestedIdRouteImport.update({
+ id: '/$id',
+ path: '/$id',
+ getParentRoute: () => NestedRoute,
+} as any)
+const CtxIdRoute = CtxIdRouteImport.update({
+ id: '/ctx/$id',
+ path: '/ctx/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ id: '__root__' | '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ NestedRoute: typeof NestedRouteWithChildren
+ CtxIdRoute: typeof CtxIdRoute
+ SlowIdRoute: typeof SlowIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/nested': {
+ id: '/nested'
+ path: '/nested'
+ fullPath: '/nested'
+ preLoaderRoute: typeof NestedRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ 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
+ }
+ '/nested/$id': {
+ id: '/nested/$id'
+ path: '/$id'
+ fullPath: '/nested/$id'
+ preLoaderRoute: typeof NestedIdRouteImport
+ parentRoute: typeof NestedRoute
+ }
+ '/ctx/$id': {
+ id: '/ctx/$id'
+ path: '/ctx/$id'
+ fullPath: '/ctx/$id'
+ preLoaderRoute: typeof CtxIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+interface NestedRouteChildren {
+ NestedIdRoute: typeof NestedIdRoute
+}
+
+const NestedRouteChildren: NestedRouteChildren = {
+ NestedIdRoute: NestedIdRoute,
+}
+
+const NestedRouteWithChildren =
+ NestedRoute._addFileChildren(NestedRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ NestedRoute: NestedRouteWithChildren,
+ CtxIdRoute: CtxIdRoute,
+ SlowIdRoute: SlowIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..7d3647fee8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/__root.tsx
@@ -0,0 +1,46 @@
+import { For } from 'solid-js'
+import { Link, Outlet, createRootRoute } from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+const ids = ['1', '2']
+
+function RootComponent() {
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/ctx.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/ctx.$id.tsx
new file mode 100644
index 0000000000..e757b57b9d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/ctx.$id.tsx
@@ -0,0 +1,29 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import {
+ ctxSeedValue,
+ ctxStateValue,
+ deferredHops,
+ hopDelay,
+} from '../../../shared'
+
+export const Route = createFileRoute('/ctx/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ beforeLoad: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { ctxSeed: ctxSeedValue(params.id) }
+ },
+ loader: ({ params, context }) => {
+ if (context.ctxSeed !== ctxSeedValue(params.id)) {
+ throw new Error('beforeLoad context missing in loader')
+ }
+ return { value: ctxStateValue(params.id) }
+ },
+ component: CtxPage,
+})
+
+function CtxPage() {
+ const data = Route.useLoaderData()
+
+ return {data().value}
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/index.tsx b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..12907d440d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ home
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/nested.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/nested.$id.tsx
new file mode 100644
index 0000000000..5518ccf498
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/nested.$id.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { deferredHops, hopDelay, nestedStateValue } from '../../../shared'
+
+export const Route = createFileRoute('/nested/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { value: nestedStateValue(params.id) }
+ },
+ component: NestedPage,
+})
+
+function NestedPage() {
+ const data = Route.useLoaderData()
+
+ return {data().value}
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/nested.tsx b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/nested.tsx
new file mode 100644
index 0000000000..fdc939dc95
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/nested.tsx
@@ -0,0 +1,23 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import { deferredHops, hopDelay, nestedLayoutValue } from '../../../shared'
+
+export const Route = createFileRoute('/nested')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async () => {
+ await hopDelay(deferredHops)
+ return { value: nestedLayoutValue() }
+ },
+ component: NestedLayout,
+})
+
+function NestedLayout() {
+ const data = Route.useLoaderData()
+
+ return (
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/slow.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/slow.$id.tsx
new file mode 100644
index 0000000000..4c229407e1
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/src/routes/slow.$id.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { deferredHops, hopDelay, slowStateValue } from '../../../shared'
+
+export const Route = createFileRoute('/slow/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { value: slowStateValue(params.id) }
+ },
+ component: SlowPage,
+})
+
+function SlowPage() {
+ const data = Route.useLoaderData()
+
+ return {data().value}
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/tsconfig.json b/benchmarks/client-nav/scenarios/async-pipeline/solid/tsconfig.json
new file mode 100644
index 0000000000..98d76ad6b8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/solid/vite.config.ts b/benchmarks/client-nav/scenarios/async-pipeline/solid/vite.config.ts
new file mode 100644
index 0000000000..5107c1f056
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/solid/vite.config.ts
@@ -0,0 +1,48 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import solid from 'vite-plugin-solid'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'solid',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ resolve: {
+ conditions: ['solid', 'browser'],
+ },
+ test: {
+ name: '@benchmarks/client-nav async-pipeline (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/project.json b/benchmarks/client-nav/scenarios/async-pipeline/vue/project.json
new file mode 100644
index 0000000000..911bb61e38
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-async-pipeline-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/async-pipeline/vue/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/setup.ts b/benchmarks/client-nav/scenarios/async-pipeline/vue/setup.ts
new file mode 100644
index 0000000000..97b74b3892
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Vue',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/speed.bench.ts b/benchmarks/client-nav/scenarios/async-pipeline/vue/speed.bench.ts
new file mode 100644
index 0000000000..eeb568323c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-async-pipeline', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-async-pipeline navigation loop (vue)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/speed.flame.ts b/benchmarks/client-nav/scenarios/async-pipeline/vue/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/src/main.tsx b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/main.tsx
new file mode 100644
index 0000000000..d8e659fb09
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/main.tsx
@@ -0,0 +1,41 @@
+import * as Vue from 'vue'
+import { RouterProvider, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ defaultPendingMs: 0,
+ defaultPendingMinMs: 0,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const component =
+ const app = Vue.createApp({
+ render: () => component,
+ })
+
+ app.mount(container)
+
+ return {
+ router,
+ unmount() {
+ app.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..ce26dc62d1
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routeTree.gen.ts
@@ -0,0 +1,140 @@
+/* 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 NestedRouteImport } from './routes/nested'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as SlowIdRouteImport } from './routes/slow.$id'
+import { Route as NestedIdRouteImport } from './routes/nested.$id'
+import { Route as CtxIdRouteImport } from './routes/ctx.$id'
+
+const NestedRoute = NestedRouteImport.update({
+ id: '/nested',
+ path: '/nested',
+ getParentRoute: () => rootRouteImport,
+} as any)
+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 NestedIdRoute = NestedIdRouteImport.update({
+ id: '/$id',
+ path: '/$id',
+ getParentRoute: () => NestedRoute,
+} as any)
+const CtxIdRoute = CtxIdRouteImport.update({
+ id: '/ctx/$id',
+ path: '/ctx/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/nested': typeof NestedRouteWithChildren
+ '/ctx/$id': typeof CtxIdRoute
+ '/nested/$id': typeof NestedIdRoute
+ '/slow/$id': typeof SlowIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ id: '__root__' | '/' | '/nested' | '/ctx/$id' | '/nested/$id' | '/slow/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ NestedRoute: typeof NestedRouteWithChildren
+ CtxIdRoute: typeof CtxIdRoute
+ SlowIdRoute: typeof SlowIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/nested': {
+ id: '/nested'
+ path: '/nested'
+ fullPath: '/nested'
+ preLoaderRoute: typeof NestedRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ 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
+ }
+ '/nested/$id': {
+ id: '/nested/$id'
+ path: '/$id'
+ fullPath: '/nested/$id'
+ preLoaderRoute: typeof NestedIdRouteImport
+ parentRoute: typeof NestedRoute
+ }
+ '/ctx/$id': {
+ id: '/ctx/$id'
+ path: '/ctx/$id'
+ fullPath: '/ctx/$id'
+ preLoaderRoute: typeof CtxIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+interface NestedRouteChildren {
+ NestedIdRoute: typeof NestedIdRoute
+}
+
+const NestedRouteChildren: NestedRouteChildren = {
+ NestedIdRoute: NestedIdRoute,
+}
+
+const NestedRouteWithChildren =
+ NestedRoute._addFileChildren(NestedRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ NestedRoute: NestedRouteWithChildren,
+ CtxIdRoute: CtxIdRoute,
+ SlowIdRoute: SlowIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..c477ab7e68
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/__root.tsx
@@ -0,0 +1,53 @@
+import * as Vue from 'vue'
+import { Link, Outlet, createRootRoute } from '@tanstack/vue-router'
+
+const ids = ['1', '2']
+
+const RootComponent = Vue.defineComponent({
+ setup() {
+ return () => (
+ <>
+
+
+ >
+ )
+ },
+})
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/ctx.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/ctx.$id.tsx
new file mode 100644
index 0000000000..d995f1ff6c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/ctx.$id.tsx
@@ -0,0 +1,32 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import {
+ ctxSeedValue,
+ ctxStateValue,
+ deferredHops,
+ hopDelay,
+} from '../../../shared'
+
+const CtxPage = Vue.defineComponent({
+ setup() {
+ const data = Route.useLoaderData()
+
+ return () => {data.value.value}
+ },
+})
+
+export const Route = createFileRoute('/ctx/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ beforeLoad: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { ctxSeed: ctxSeedValue(params.id) }
+ },
+ loader: ({ params, context }) => {
+ if (context.ctxSeed !== ctxSeedValue(params.id)) {
+ throw new Error('beforeLoad context missing in loader')
+ }
+ return { value: ctxStateValue(params.id) }
+ },
+ component: CtxPage,
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/index.tsx b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..128e132190
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/index.tsx
@@ -0,0 +1,16 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+
+const HomePage = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ home
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/nested.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/nested.$id.tsx
new file mode 100644
index 0000000000..bbeaaa7dd7
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/nested.$id.tsx
@@ -0,0 +1,21 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { deferredHops, hopDelay, nestedStateValue } from '../../../shared'
+
+const NestedPage = Vue.defineComponent({
+ setup() {
+ const data = Route.useLoaderData()
+
+ return () => {data.value.value}
+ },
+})
+
+export const Route = createFileRoute('/nested/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { value: nestedStateValue(params.id) }
+ },
+ component: NestedPage,
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/nested.tsx b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/nested.tsx
new file mode 100644
index 0000000000..763657dfc6
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/nested.tsx
@@ -0,0 +1,26 @@
+import * as Vue from 'vue'
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import { deferredHops, hopDelay, nestedLayoutValue } from '../../../shared'
+
+const NestedLayout = Vue.defineComponent({
+ setup() {
+ const data = Route.useLoaderData()
+
+ return () => (
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/nested')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async () => {
+ await hopDelay(deferredHops)
+ return { value: nestedLayoutValue() }
+ },
+ component: NestedLayout,
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/slow.$id.tsx b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/slow.$id.tsx
new file mode 100644
index 0000000000..c9afccba97
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/src/routes/slow.$id.tsx
@@ -0,0 +1,21 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { deferredHops, hopDelay, slowStateValue } from '../../../shared'
+
+const SlowPage = Vue.defineComponent({
+ setup() {
+ const data = Route.useLoaderData()
+
+ return () => {data.value.value}
+ },
+})
+
+export const Route = createFileRoute('/slow/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: async ({ params }) => {
+ await hopDelay(deferredHops)
+ return { value: slowStateValue(params.id) }
+ },
+ component: SlowPage,
+})
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/tsconfig.json b/benchmarks/client-nav/scenarios/async-pipeline/vue/tsconfig.json
new file mode 100644
index 0000000000..7786fc3da9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/async-pipeline/vue/vite.config.ts b/benchmarks/client-nav/scenarios/async-pipeline/vue/vite.config.ts
new file mode 100644
index 0000000000..e16a8ee828
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/async-pipeline/vue/vite.config.ts
@@ -0,0 +1,42 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'vue',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav async-pipeline (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/project.json b/benchmarks/client-nav/scenarios/control-flow/react/project.json
new file mode 100644
index 0000000000..e3e580911f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-control-flow-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/control-flow/react/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/setup.ts b/benchmarks/client-nav/scenarios/control-flow/react/setup.ts
new file mode 100644
index 0000000000..130583b190
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'React',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/speed.bench.ts b/benchmarks/client-nav/scenarios/control-flow/react/speed.bench.ts
new file mode 100644
index 0000000000..55a2224d3a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-control-flow', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-control-flow navigation loop (react)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/speed.flame.ts b/benchmarks/client-nav/scenarios/control-flow/react/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/main.tsx b/benchmarks/client-nav/scenarios/control-flow/react/src/main.tsx
new file mode 100644
index 0000000000..9a0c3d3b50
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/main.tsx
@@ -0,0 +1,37 @@
+import { RouterProvider, createRouter } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ // The scenario intentionally throws loader errors every lap; keep React 19's
+ // default per-caught-error console reporting out of the measured loop.
+ const reactRoot = createRoot(container, { onCaughtError: () => {} })
+ reactRoot.render()
+
+ return {
+ router,
+ unmount() {
+ reactRoot.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/control-flow/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..86edcc8472
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routeTree.gen.ts
@@ -0,0 +1,156 @@
+/* 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 TargetRouteImport } from './routes/target'
+import { Route as Hop2RouteImport } from './routes/hop2'
+import { Route as Hop1RouteImport } from './routes/hop1'
+import { Route as BrokenRouteImport } from './routes/broken'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as MissingIdRouteImport } from './routes/missing.$id'
+
+const TargetRoute = TargetRouteImport.update({
+ id: '/target',
+ path: '/target',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const Hop2Route = Hop2RouteImport.update({
+ id: '/hop2',
+ path: '/hop2',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const Hop1Route = Hop1RouteImport.update({
+ id: '/hop1',
+ path: '/hop1',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const BrokenRoute = BrokenRouteImport.update({
+ id: '/broken',
+ path: '/broken',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const MissingIdRoute = MissingIdRouteImport.update({
+ id: '/missing/$id',
+ path: '/missing/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/broken' | '/hop1' | '/hop2' | '/target' | '/missing/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/broken' | '/hop1' | '/hop2' | '/target' | '/missing/$id'
+ id:
+ | '__root__'
+ | '/'
+ | '/broken'
+ | '/hop1'
+ | '/hop2'
+ | '/target'
+ | '/missing/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ BrokenRoute: typeof BrokenRoute
+ Hop1Route: typeof Hop1Route
+ Hop2Route: typeof Hop2Route
+ TargetRoute: typeof TargetRoute
+ MissingIdRoute: typeof MissingIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/target': {
+ id: '/target'
+ path: '/target'
+ fullPath: '/target'
+ preLoaderRoute: typeof TargetRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/hop2': {
+ id: '/hop2'
+ path: '/hop2'
+ fullPath: '/hop2'
+ preLoaderRoute: typeof Hop2RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/hop1': {
+ id: '/hop1'
+ path: '/hop1'
+ fullPath: '/hop1'
+ preLoaderRoute: typeof Hop1RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/broken': {
+ id: '/broken'
+ path: '/broken'
+ fullPath: '/broken'
+ preLoaderRoute: typeof BrokenRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/missing/$id': {
+ id: '/missing/$id'
+ path: '/missing/$id'
+ fullPath: '/missing/$id'
+ preLoaderRoute: typeof MissingIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ BrokenRoute: BrokenRoute,
+ Hop1Route: Hop1Route,
+ Hop2Route: Hop2Route,
+ TargetRoute: TargetRoute,
+ MissingIdRoute: MissingIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..63d5feb901
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/__root.tsx
@@ -0,0 +1,52 @@
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useLocation,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function LocationMarker() {
+ const pathname = useLocation({ select: (location) => location.pathname })
+ return {pathname}
+}
+
+function RootComponent() {
+ return (
+ <>
+
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routes/broken.tsx b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/broken.tsx
new file mode 100644
index 0000000000..2dc4a26486
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/broken.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { errorMessage } from '../../../shared'
+import type { ErrorComponentProps } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/broken')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw new Error(errorMessage)
+ },
+ errorComponent: BrokenError,
+})
+
+function BrokenError(props: ErrorComponentProps) {
+ return {props.error.message}
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routes/hop1.tsx b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/hop1.tsx
new file mode 100644
index 0000000000..aacba39384
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/hop1.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/hop1')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw redirect({ to: '/hop2' })
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routes/hop2.tsx b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/hop2.tsx
new file mode 100644
index 0000000000..db31abe9f3
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/hop2.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/hop2')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw redirect({ to: '/target' })
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routes/index.tsx b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/index.tsx
new file mode 100644
index 0000000000..24e856ea15
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return home
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routes/missing.$id.tsx b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/missing.$id.tsx
new file mode 100644
index 0000000000..0c807bb2bc
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/missing.$id.tsx
@@ -0,0 +1,23 @@
+import { createFileRoute, notFound } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/missing/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: ({ params }) => {
+ if (params.id === 'gone') {
+ throw notFound()
+ }
+ return { id: params.id }
+ },
+ component: MissingPage,
+ notFoundComponent: MissingNotFound,
+})
+
+function MissingPage() {
+ const data = Route.useLoaderData()
+ return {data.id}
+}
+
+function MissingNotFound() {
+ return missing not found
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routes/target.tsx b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/target.tsx
new file mode 100644
index 0000000000..280f65835e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routes/target.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/target')({
+ component: TargetPage,
+})
+
+function TargetPage() {
+ return target
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/tsconfig.json b/benchmarks/client-nav/scenarios/control-flow/react/tsconfig.json
new file mode 100644
index 0000000000..1d59cbb450
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/react/vite.config.ts b/benchmarks/client-nav/scenarios/control-flow/react/vite.config.ts
new file mode 100644
index 0000000000..1aa2c1fa35
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/react/vite.config.ts
@@ -0,0 +1,40 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'react',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav control-flow (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/shared.ts b/benchmarks/client-nav/scenarios/control-flow/shared.ts
new file mode 100644
index 0000000000..c4f368bee1
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/shared.ts
@@ -0,0 +1,91 @@
+/**
+ * Shared definition of the `control-flow` scenario: loader-thrown redirects
+ * (including a 2-hop chain), notFound() rendering, loader errors through the
+ * route error boundary, and recovery navigation resetting the boundary.
+ */
+import type { ScenarioStep } from '../harness'
+
+export const errorMessage = 'bench-error'
+
+export const steps: ReadonlyArray = [
+ {
+ type: 'click',
+ testId: 'go-hop1',
+ // The click lands on /hop1 whose loader chains redirects to /target; wait
+ // until the post-redirect location is committed before finishing the step.
+ isSettled: () =>
+ document.querySelector('[data-testid="loc"]')?.textContent === '/target',
+ },
+ 'go-missing-ok',
+ 'go-missing-gone',
+ 'go-broken',
+ 'go-target',
+ 'go-missing-gone',
+ 'home',
+]
+
+interface StepExpectation {
+ loc: string
+ present: ReadonlyArray
+ absent?: ReadonlyArray
+ errorText?: string
+}
+
+const expectations: ReadonlyArray = [
+ { loc: '/target', present: ['target-state'] },
+ { loc: '/missing/exists', present: ['missing-state'] },
+ {
+ loc: '/missing/gone',
+ present: ['not-found-state'],
+ absent: ['missing-state'],
+ },
+ { loc: '/broken', present: ['error-state'], errorText: errorMessage },
+ { loc: '/target', present: ['target-state'], absent: ['error-state'] },
+ { loc: '/missing/gone', present: ['not-found-state'] },
+ { loc: '/', present: ['home-state'] },
+]
+
+export function assertStepResult(stepIndex: number, container: HTMLElement) {
+ const expectation = expectations[stepIndex % expectations.length]!
+
+ const loc = container.querySelector('[data-testid="loc"]')
+ if (loc?.textContent !== expectation.loc) {
+ throw new Error(
+ `Expected location "${expectation.loc}" after step ${stepIndex}, received "${loc?.textContent ?? ''}"`,
+ )
+ }
+
+ for (const testId of expectation.present) {
+ const element = container.querySelector(`[data-testid="${testId}"]`)
+ if (!element) {
+ throw new Error(
+ `Expected [data-testid="${testId}"] after step ${stepIndex}`,
+ )
+ }
+ if (
+ expectation.errorText &&
+ !element.textContent?.includes(expectation.errorText)
+ ) {
+ throw new Error(
+ `Expected [data-testid="${testId}"] to include "${expectation.errorText}", received "${element.textContent}"`,
+ )
+ }
+ }
+
+ for (const testId of expectation.absent ?? []) {
+ if (container.querySelector(`[data-testid="${testId}"]`)) {
+ throw new Error(
+ `Expected [data-testid="${testId}"] to be absent after step ${stepIndex}`,
+ )
+ }
+ }
+}
+
+// Two laps through the 7-step sequence per benchmark iteration.
+export const ticksPerIteration = 14
+
+export const benchOptions = {
+ warmupIterations: 100,
+ time: 10_000,
+ throws: true,
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/project.json b/benchmarks/client-nav/scenarios/control-flow/solid/project.json
new file mode 100644
index 0000000000..2b5154d385
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-control-flow-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/control-flow/solid/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/setup.ts b/benchmarks/client-nav/scenarios/control-flow/solid/setup.ts
new file mode 100644
index 0000000000..6a46971603
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Solid',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/speed.bench.ts b/benchmarks/client-nav/scenarios/control-flow/solid/speed.bench.ts
new file mode 100644
index 0000000000..abbb85a575
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-control-flow', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-control-flow navigation loop (solid)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/speed.flame.ts b/benchmarks/client-nav/scenarios/control-flow/solid/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/main.tsx b/benchmarks/client-nav/scenarios/control-flow/solid/src/main.tsx
new file mode 100644
index 0000000000..817045bd02
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/main.tsx
@@ -0,0 +1,32 @@
+import { render } from 'solid-js/web'
+import { RouterProvider, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const unmount = render(() => , container)
+
+ return {
+ router,
+ unmount,
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/control-flow/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..44d88e9f8d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/routeTree.gen.ts
@@ -0,0 +1,156 @@
+/* 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 TargetRouteImport } from './routes/target'
+import { Route as Hop2RouteImport } from './routes/hop2'
+import { Route as Hop1RouteImport } from './routes/hop1'
+import { Route as BrokenRouteImport } from './routes/broken'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as MissingIdRouteImport } from './routes/missing.$id'
+
+const TargetRoute = TargetRouteImport.update({
+ id: '/target',
+ path: '/target',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const Hop2Route = Hop2RouteImport.update({
+ id: '/hop2',
+ path: '/hop2',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const Hop1Route = Hop1RouteImport.update({
+ id: '/hop1',
+ path: '/hop1',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const BrokenRoute = BrokenRouteImport.update({
+ id: '/broken',
+ path: '/broken',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const MissingIdRoute = MissingIdRouteImport.update({
+ id: '/missing/$id',
+ path: '/missing/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/broken' | '/hop1' | '/hop2' | '/target' | '/missing/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/broken' | '/hop1' | '/hop2' | '/target' | '/missing/$id'
+ id:
+ | '__root__'
+ | '/'
+ | '/broken'
+ | '/hop1'
+ | '/hop2'
+ | '/target'
+ | '/missing/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ BrokenRoute: typeof BrokenRoute
+ Hop1Route: typeof Hop1Route
+ Hop2Route: typeof Hop2Route
+ TargetRoute: typeof TargetRoute
+ MissingIdRoute: typeof MissingIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/target': {
+ id: '/target'
+ path: '/target'
+ fullPath: '/target'
+ preLoaderRoute: typeof TargetRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/hop2': {
+ id: '/hop2'
+ path: '/hop2'
+ fullPath: '/hop2'
+ preLoaderRoute: typeof Hop2RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/hop1': {
+ id: '/hop1'
+ path: '/hop1'
+ fullPath: '/hop1'
+ preLoaderRoute: typeof Hop1RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/broken': {
+ id: '/broken'
+ path: '/broken'
+ fullPath: '/broken'
+ preLoaderRoute: typeof BrokenRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/missing/$id': {
+ id: '/missing/$id'
+ path: '/missing/$id'
+ fullPath: '/missing/$id'
+ preLoaderRoute: typeof MissingIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ BrokenRoute: BrokenRoute,
+ Hop1Route: Hop1Route,
+ Hop2Route: Hop2Route,
+ TargetRoute: TargetRoute,
+ MissingIdRoute: MissingIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..1bce9e8a3e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/__root.tsx
@@ -0,0 +1,52 @@
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useLocation,
+} from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function LocationMarker() {
+ const pathname = useLocation({ select: (location) => location.pathname })
+ return {pathname()}
+}
+
+function RootComponent() {
+ return (
+ <>
+
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/broken.tsx b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/broken.tsx
new file mode 100644
index 0000000000..9931ef9c58
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/broken.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { errorMessage } from '../../../shared'
+import type { ErrorComponentProps } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/broken')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw new Error(errorMessage)
+ },
+ errorComponent: BrokenError,
+})
+
+function BrokenError(props: ErrorComponentProps) {
+ return {props.error.message}
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/hop1.tsx b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/hop1.tsx
new file mode 100644
index 0000000000..1aedb481ec
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/hop1.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute, redirect } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/hop1')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw redirect({ to: '/hop2' })
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/hop2.tsx b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/hop2.tsx
new file mode 100644
index 0000000000..91b601c516
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/hop2.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute, redirect } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/hop2')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw redirect({ to: '/target' })
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/index.tsx b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..cc46764de5
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return home
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/missing.$id.tsx b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/missing.$id.tsx
new file mode 100644
index 0000000000..9c0493fb48
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/missing.$id.tsx
@@ -0,0 +1,23 @@
+import { createFileRoute, notFound } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/missing/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: ({ params }) => {
+ if (params.id === 'gone') {
+ throw notFound()
+ }
+ return { id: params.id }
+ },
+ component: MissingPage,
+ notFoundComponent: MissingNotFound,
+})
+
+function MissingPage() {
+ const data = Route.useLoaderData()
+ return {data().id}
+}
+
+function MissingNotFound() {
+ return missing not found
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/target.tsx b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/target.tsx
new file mode 100644
index 0000000000..87de4e59a7
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/src/routes/target.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/target')({
+ component: TargetPage,
+})
+
+function TargetPage() {
+ return target
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/tsconfig.json b/benchmarks/client-nav/scenarios/control-flow/solid/tsconfig.json
new file mode 100644
index 0000000000..98d76ad6b8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/solid/vite.config.ts b/benchmarks/client-nav/scenarios/control-flow/solid/vite.config.ts
new file mode 100644
index 0000000000..1c6244d3d1
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/solid/vite.config.ts
@@ -0,0 +1,48 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import solid from 'vite-plugin-solid'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'solid',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ resolve: {
+ conditions: ['solid', 'browser'],
+ },
+ test: {
+ name: '@benchmarks/client-nav control-flow (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/project.json b/benchmarks/client-nav/scenarios/control-flow/vue/project.json
new file mode 100644
index 0000000000..7370687988
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-control-flow-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/control-flow/vue/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/setup.ts b/benchmarks/client-nav/scenarios/control-flow/vue/setup.ts
new file mode 100644
index 0000000000..97b74b3892
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Vue',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/speed.bench.ts b/benchmarks/client-nav/scenarios/control-flow/vue/speed.bench.ts
new file mode 100644
index 0000000000..551b88e68f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-control-flow', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-control-flow navigation loop (vue)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/speed.flame.ts b/benchmarks/client-nav/scenarios/control-flow/vue/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/main.tsx b/benchmarks/client-nav/scenarios/control-flow/vue/src/main.tsx
new file mode 100644
index 0000000000..c96d3f3a2e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/main.tsx
@@ -0,0 +1,39 @@
+import * as Vue from 'vue'
+import { RouterProvider, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const component =
+ const app = Vue.createApp({
+ render: () => component,
+ })
+
+ app.mount(container)
+
+ return {
+ router,
+ unmount() {
+ app.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/control-flow/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..60fc846f89
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/routeTree.gen.ts
@@ -0,0 +1,156 @@
+/* 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 TargetRouteImport } from './routes/target'
+import { Route as Hop2RouteImport } from './routes/hop2'
+import { Route as Hop1RouteImport } from './routes/hop1'
+import { Route as BrokenRouteImport } from './routes/broken'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as MissingIdRouteImport } from './routes/missing.$id'
+
+const TargetRoute = TargetRouteImport.update({
+ id: '/target',
+ path: '/target',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const Hop2Route = Hop2RouteImport.update({
+ id: '/hop2',
+ path: '/hop2',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const Hop1Route = Hop1RouteImport.update({
+ id: '/hop1',
+ path: '/hop1',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const BrokenRoute = BrokenRouteImport.update({
+ id: '/broken',
+ path: '/broken',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const MissingIdRoute = MissingIdRouteImport.update({
+ id: '/missing/$id',
+ path: '/missing/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/broken': typeof BrokenRoute
+ '/hop1': typeof Hop1Route
+ '/hop2': typeof Hop2Route
+ '/target': typeof TargetRoute
+ '/missing/$id': typeof MissingIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/broken' | '/hop1' | '/hop2' | '/target' | '/missing/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/broken' | '/hop1' | '/hop2' | '/target' | '/missing/$id'
+ id:
+ | '__root__'
+ | '/'
+ | '/broken'
+ | '/hop1'
+ | '/hop2'
+ | '/target'
+ | '/missing/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ BrokenRoute: typeof BrokenRoute
+ Hop1Route: typeof Hop1Route
+ Hop2Route: typeof Hop2Route
+ TargetRoute: typeof TargetRoute
+ MissingIdRoute: typeof MissingIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/target': {
+ id: '/target'
+ path: '/target'
+ fullPath: '/target'
+ preLoaderRoute: typeof TargetRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/hop2': {
+ id: '/hop2'
+ path: '/hop2'
+ fullPath: '/hop2'
+ preLoaderRoute: typeof Hop2RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/hop1': {
+ id: '/hop1'
+ path: '/hop1'
+ fullPath: '/hop1'
+ preLoaderRoute: typeof Hop1RouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/broken': {
+ id: '/broken'
+ path: '/broken'
+ fullPath: '/broken'
+ preLoaderRoute: typeof BrokenRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/missing/$id': {
+ id: '/missing/$id'
+ path: '/missing/$id'
+ fullPath: '/missing/$id'
+ preLoaderRoute: typeof MissingIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ BrokenRoute: BrokenRoute,
+ Hop1Route: Hop1Route,
+ Hop2Route: Hop2Route,
+ TargetRoute: TargetRoute,
+ MissingIdRoute: MissingIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..9695c4b32d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/__root.tsx
@@ -0,0 +1,57 @@
+import * as Vue from 'vue'
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useLocation,
+} from '@tanstack/vue-router'
+
+const LocationMarker = Vue.defineComponent({
+ setup() {
+ const pathname = useLocation({ select: (location) => location.pathname })
+ return () => {pathname.value}
+ },
+})
+
+const RootComponent = Vue.defineComponent({
+ setup() {
+ return () => (
+ <>
+
+
+
+ >
+ )
+ },
+})
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/broken.tsx b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/broken.tsx
new file mode 100644
index 0000000000..c67fa2817e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/broken.tsx
@@ -0,0 +1,21 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { errorMessage } from '../../../shared'
+
+const BrokenError = Vue.defineComponent({
+ props: {
+ error: { type: Error, required: true },
+ },
+ setup(props) {
+ return () => {props.error.message}
+ },
+})
+
+export const Route = createFileRoute('/broken')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw new Error(errorMessage)
+ },
+ errorComponent: BrokenError,
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/hop1.tsx b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/hop1.tsx
new file mode 100644
index 0000000000..ed00cefe3f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/hop1.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute, redirect } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/hop1')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw redirect({ to: '/hop2' })
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/hop2.tsx b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/hop2.tsx
new file mode 100644
index 0000000000..f6d4b1c1e0
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/hop2.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute, redirect } from '@tanstack/vue-router'
+
+export const Route = createFileRoute('/hop2')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: () => {
+ throw redirect({ to: '/target' })
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/index.tsx b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..f3ffc58610
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/index.tsx
@@ -0,0 +1,12 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+
+const HomePage = Vue.defineComponent({
+ setup() {
+ return () => home
+ },
+})
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/missing.$id.tsx b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/missing.$id.tsx
new file mode 100644
index 0000000000..022ea656a6
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/missing.$id.tsx
@@ -0,0 +1,28 @@
+import * as Vue from 'vue'
+import { createFileRoute, notFound } from '@tanstack/vue-router'
+
+const MissingPage = Vue.defineComponent({
+ setup() {
+ const data = Route.useLoaderData()
+ return () => {data.value.id}
+ },
+})
+
+const MissingNotFound = Vue.defineComponent({
+ setup() {
+ return () => missing not found
+ },
+})
+
+export const Route = createFileRoute('/missing/$id')({
+ staleTime: 0,
+ gcTime: 0,
+ loader: ({ params }) => {
+ if (params.id === 'gone') {
+ throw notFound()
+ }
+ return { id: params.id }
+ },
+ component: MissingPage,
+ notFoundComponent: MissingNotFound,
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/target.tsx b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/target.tsx
new file mode 100644
index 0000000000..4e22d0993a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/src/routes/target.tsx
@@ -0,0 +1,12 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+
+const TargetPage = Vue.defineComponent({
+ setup() {
+ return () => target
+ },
+})
+
+export const Route = createFileRoute('/target')({
+ component: TargetPage,
+})
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/tsconfig.json b/benchmarks/client-nav/scenarios/control-flow/vue/tsconfig.json
new file mode 100644
index 0000000000..7786fc3da9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/control-flow/vue/vite.config.ts b/benchmarks/client-nav/scenarios/control-flow/vue/vite.config.ts
new file mode 100644
index 0000000000..4694cf69c8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/control-flow/vue/vite.config.ts
@@ -0,0 +1,42 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'vue',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav control-flow (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/harness.ts b/benchmarks/client-nav/scenarios/harness.ts
new file mode 100644
index 0000000000..7b993d0ed8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/harness.ts
@@ -0,0 +1,371 @@
+import { getRequiredLink, waitForRequiredLink } from '../setup-helpers'
+
+export interface ScenarioRouter {
+ load: () => Promise
+ subscribe: (eventType: 'onRendered', callback: () => void) => () => void
+ navigate: (options: any) => Promise
+ preloadRoute: (options: any) => Promise
+ invalidate: () => Promise
+ history: {
+ back: () => void
+ forward: () => void
+ go: (index: number) => void
+ }
+}
+
+export interface MountedScenarioApp {
+ router: ScenarioRouter
+ unmount: () => void
+}
+
+/**
+ * A single benchmark step. Plain strings are link-click steps (the common
+ * case). Steps that change the router location (`click`, `navigate`, `back`,
+ * `forward`, `go`) are awaited through the router's `onRendered` event; steps
+ * that do background work (`hover`, `preload`, `invalidate`) are awaited
+ * through a fixed number of 0ms timer hops so the loop stays deterministic.
+ */
+export type ScenarioStep =
+ | string
+ | {
+ type: 'click'
+ testId: string
+ /**
+ * Optional post-render settle predicate for steps that trigger work
+ * finishing after `onRendered` (e.g. deferred data resolving through a
+ * Suspense boundary). The step keeps taking 0ms timer hops until the
+ * predicate returns true (bounded, throws when exceeded).
+ */
+ isSettled?: () => boolean
+ }
+ | {
+ /**
+ * Dispatches a single `mouseover` on the link, which triggers
+ * `preload: 'intent'` exactly once in every adapter: React synthesizes
+ * `mouseEnter` from `mouseover`, and Solid/Vue attach a native
+ * `mouseover` preload handler. (Solid/Vue also listen to `mouseenter` —
+ * dispatching both events would run the preload pipeline twice for them
+ * but only once for React.) Then settles via counted 0ms hops.
+ */
+ type: 'hover'
+ testId: string
+ settleHops?: number
+ }
+ | { type: 'navigate'; getOptions: () => object }
+ | { type: 'preload'; getOptions: () => object; settleHops?: number }
+ | { type: 'invalidate'; settleHops?: number }
+ | { type: 'back' }
+ | { type: 'forward' }
+ | { type: 'go'; delta: number }
+
+/**
+ * One macrotask turn via `setImmediate` rather than `setTimeout(0)`: both
+ * yield deterministically to the event loop (timers, MessageChannel, React's
+ * Node scheduler), but a timer costs ~3-4 syscalls (timerfd + epoll) per hop
+ * while an immediate costs ~1. CodSpeed excludes syscall time from the
+ * measure — inconsistently past a threshold, which flagged the hop-heavy
+ * benches as "skipped" — so the loop must stay syscall-lean.
+ */
+const timerHop = () =>
+ new Promise((resolve) => setImmediate(() => resolve()))
+
+async function settle(hops: number) {
+ for (let i = 0; i < hops; i++) {
+ await timerHop()
+ }
+}
+
+const MAX_SETTLE_HOPS = 100
+
+async function settleUntil(isSettled: () => boolean, label: string) {
+ for (let i = 0; i < MAX_SETTLE_HOPS; i++) {
+ if (isSettled()) {
+ return
+ }
+ await timerHop()
+ }
+ throw new Error(
+ `Step "${label}" did not settle within ${MAX_SETTLE_HOPS} timer hops`,
+ )
+}
+
+/**
+ * Reroute zero-delay `window.setTimeout` calls onto `setImmediate` for the
+ * duration of the benchmark. jsdom delivers history traversals through two
+ * nested `window.setTimeout(0)` tasks; Node clamps those to 1ms of wall
+ * time, so with nothing else pending the event loop blocks in `epoll_wait`
+ * until each timer expires — and that blocked wall-time is recorded as
+ * highly variable syscall time (5-26ms across runs), enough for CodSpeed to
+ * skip-warn the history benches. Zero-delay timeouts carry no ordering
+ * semantics a check-phase immediate doesn't satisfy, and the suite's
+ * conventions already forbid real-delay timers in measured code; non-zero
+ * delays are passed through untouched.
+ */
+function patchZeroDelayTimeouts() {
+ const originalSetTimeout = window.setTimeout.bind(window)
+ const originalClearTimeout = window.clearTimeout.bind(window)
+ const immediates = new Map()
+ // Negative ids cannot collide with real jsdom timer ids.
+ let nextImmediateId = -2
+
+ window.setTimeout = ((
+ handler: (...handlerArgs: Array) => void,
+ delay?: number,
+ ...args: Array
+ ) => {
+ if (!delay) {
+ const id = nextImmediateId--
+ immediates.set(
+ id,
+ setImmediate(() => {
+ immediates.delete(id)
+ handler(...args)
+ }),
+ )
+ return id
+ }
+ return originalSetTimeout(handler, delay, ...args)
+ }) as typeof window.setTimeout
+
+ window.clearTimeout = ((id: number) => {
+ const immediate = immediates.get(id)
+ if (immediate) {
+ clearImmediate(immediate)
+ immediates.delete(id)
+ return
+ }
+ originalClearTimeout(id)
+ }) as typeof window.clearTimeout
+
+ return () => {
+ window.setTimeout = originalSetTimeout as typeof window.setTimeout
+ window.clearTimeout = originalClearTimeout as typeof window.clearTimeout
+ }
+}
+
+export interface ScenarioSetupOptions {
+ frameworkLabel: string
+ mount: (container: HTMLElement) => MountedScenarioApp
+ /**
+ * Circular sequence of steps advanced by `tick()`. Link steps must target
+ * links rendered from the start (e.g. in the root layout), no two
+ * consecutive location-changing steps may target the same location (a
+ * same-location click never triggers `onRendered`), and the sequence must
+ * leave the app back on the initial route so the warm-up lap ends in the
+ * starting state.
+ */
+ steps: ReadonlyArray
+ /** Sanity check run once per step during the warm-up lap in `before()`. */
+ assertAfterStep?: (stepIndex: number, container: HTMLElement) => void
+ /**
+ * URL the window is reset to before mounting (default '/'). Scenarios whose
+ * router uses a `basepath` or an input rewrite must start on an external
+ * URL that maps to their initial route.
+ */
+ initialUrl?: string
+}
+
+function warnAboutDevMode(frameworkLabel: string) {
+ if (process.env.NODE_ENV !== 'production') {
+ console.warn(
+ `client-nav scenario benchmark is running without NODE_ENV=production; ${frameworkLabel} dev overhead will dominate results.`,
+ )
+ }
+}
+
+/**
+ * Shared scenario runner: mounts the prebuilt app, then advances a circular
+ * sequence of steps, synchronizing each step so its work is fully part of the
+ * measured tick and steps cannot overlap.
+ */
+export function createScenarioSetup(options: ScenarioSetupOptions) {
+ warnAboutDevMode(options.frameworkLabel)
+
+ let container: HTMLDivElement | undefined = undefined
+ let unmount: (() => void) | undefined = undefined
+ let unsub = () => {}
+ let restoreTimeouts = () => {}
+ let stepIndex = 0
+ let next: () => Promise = () =>
+ Promise.reject(new Error('Benchmark not initialized'))
+
+ async function before() {
+ stepIndex = 0
+ restoreTimeouts = patchZeroDelayTimeouts()
+ window.history.replaceState(null, '', options.initialUrl ?? '/')
+ container = document.createElement('div')
+ document.body.append(container)
+
+ const { router, unmount: dispose } = options.mount(container)
+ unmount = dispose
+
+ let resolveRendered: () => void = () => {}
+ unsub = router.subscribe('onRendered', () => {
+ resolveRendered()
+ })
+
+ const cachedLinks = new Map()
+ const rendered = () =>
+ new Promise((resolveNext) => {
+ resolveRendered = resolveNext
+ })
+
+ const runStep = async (step: ScenarioStep) => {
+ if (typeof step === 'string') {
+ step = { type: 'click', testId: step }
+ }
+
+ switch (step.type) {
+ case 'click': {
+ const renderedPromise = rendered()
+ getRequiredLink(container!, step.testId, cachedLinks).dispatchEvent(
+ new MouseEvent('click', {
+ bubbles: true,
+ cancelable: true,
+ button: 0,
+ }),
+ )
+ await renderedPromise
+ if (step.isSettled) {
+ await settleUntil(step.isSettled, `click ${step.testId}`)
+ }
+ return
+ }
+ case 'hover': {
+ const link = getRequiredLink(container!, step.testId, cachedLinks)
+ link.dispatchEvent(
+ new MouseEvent('mouseover', { bubbles: true, cancelable: true }),
+ )
+ await settle(step.settleHops ?? 4)
+ return
+ }
+ case 'navigate': {
+ const renderedPromise = rendered()
+ router.navigate(step.getOptions())
+ await renderedPromise
+ return
+ }
+ case 'preload': {
+ await router.preloadRoute(step.getOptions())
+ await settle(step.settleHops ?? 2)
+ return
+ }
+ case 'invalidate': {
+ await router.invalidate()
+ await settle(step.settleHops ?? 2)
+ return
+ }
+ case 'back': {
+ const renderedPromise = rendered()
+ router.history.back()
+ await renderedPromise
+ return
+ }
+ case 'forward': {
+ const renderedPromise = rendered()
+ router.history.forward()
+ await renderedPromise
+ return
+ }
+ case 'go': {
+ const renderedPromise = rendered()
+ router.history.go(step.delta)
+ await renderedPromise
+ return
+ }
+ }
+ }
+
+ await router.load()
+
+ const linkTestIds = new Set()
+ for (const step of options.steps) {
+ if (typeof step === 'string') {
+ linkTestIds.add(step)
+ } else if (step.type === 'click' || step.type === 'hover') {
+ linkTestIds.add(step.testId)
+ }
+ }
+ for (const testId of linkTestIds) {
+ await waitForRequiredLink(container, testId, cachedLinks)
+ }
+
+ // One warm-up lap that also sanity-checks each step's observable output,
+ // ending back on the initial route so measurement starts from a known state.
+ for (const [index, step] of options.steps.entries()) {
+ await runStep(step)
+ options.assertAfterStep?.(index, container)
+ }
+
+ next = () => {
+ const step = options.steps[stepIndex % options.steps.length]!
+ stepIndex += 1
+ return runStep(step)
+ }
+ }
+
+ function after() {
+ unmount?.()
+ container?.remove()
+ unsub()
+ restoreTimeouts()
+ restoreTimeouts = () => {}
+ unmount = undefined
+ container = undefined
+ }
+
+ function tick() {
+ return next()
+ }
+
+ return {
+ before,
+ tick,
+ after,
+ }
+}
+
+export interface MountLoopSetupOptions {
+ frameworkLabel: string
+ mount: (container: HTMLElement) => MountedScenarioApp
+ /** Test id that must appear in the container before a mount counts as done. */
+ readyTestId: string
+ assertReady?: (container: HTMLElement) => void
+}
+
+/**
+ * Mount-loop runner for cold-start scenarios: every tick creates a fresh
+ * container, mounts the app (router creation included), waits for the initial
+ * render to commit, then unmounts and cleans up.
+ */
+export function createMountLoopSetup(options: MountLoopSetupOptions) {
+ warnAboutDevMode(options.frameworkLabel)
+
+ async function tick() {
+ window.history.replaceState(null, '', '/')
+ const container = document.createElement('div')
+ document.body.append(container)
+
+ const { router, unmount } = options.mount(container)
+
+ try {
+ await router.load()
+ await settleUntil(
+ () =>
+ container.querySelector(`[data-testid="${options.readyTestId}"]`) !==
+ null,
+ `mount ${options.readyTestId}`,
+ )
+ options.assertReady?.(container)
+ } finally {
+ unmount()
+ container.remove()
+ }
+ }
+
+ return {
+ before: async () => {},
+ tick,
+ after: () => {},
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/project.json b/benchmarks/client-nav/scenarios/head/react/project.json
new file mode 100644
index 0000000000..1becf30fb9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-head-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/head/react/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/setup.ts b/benchmarks/client-nav/scenarios/head/react/setup.ts
new file mode 100644
index 0000000000..1a28b0ca94
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, stepTestIds } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'React',
+ mount: mountTestApp,
+ steps: stepTestIds,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/speed.bench.ts b/benchmarks/client-nav/scenarios/head/react/speed.bench.ts
new file mode 100644
index 0000000000..c71b426c56
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-head', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-head navigation loop (react)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/head/react/speed.flame.ts b/benchmarks/client-nav/scenarios/head/react/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/src/main.tsx b/benchmarks/client-nav/scenarios/head/react/src/main.tsx
new file mode 100644
index 0000000000..ef0b99555c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/src/main.tsx
@@ -0,0 +1,35 @@
+import { RouterProvider, createRouter } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const reactRoot = createRoot(container)
+ reactRoot.render()
+
+ return {
+ router,
+ unmount() {
+ reactRoot.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/head/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..5c80a3104e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/src/routeTree.gen.ts
@@ -0,0 +1,145 @@
+/* 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 SettingsRouteImport } from './routes/settings'
+import { Route as DocsRouteImport } from './routes/docs'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as DocsSectionRouteImport } from './routes/docs.$section'
+import { Route as ArticlesIdRouteImport } from './routes/articles.$id'
+
+const SettingsRoute = SettingsRouteImport.update({
+ id: '/settings',
+ path: '/settings',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DocsRoute = DocsRouteImport.update({
+ id: '/docs',
+ path: '/docs',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DocsSectionRoute = DocsSectionRouteImport.update({
+ id: '/$section',
+ path: '/$section',
+ getParentRoute: () => DocsRoute,
+} as any)
+const ArticlesIdRoute = ArticlesIdRouteImport.update({
+ id: '/articles/$id',
+ path: '/articles/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/docs' | '/settings' | '/articles/$id' | '/docs/$section'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/docs' | '/settings' | '/articles/$id' | '/docs/$section'
+ id:
+ | '__root__'
+ | '/'
+ | '/docs'
+ | '/settings'
+ | '/articles/$id'
+ | '/docs/$section'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ DocsRoute: typeof DocsRouteWithChildren
+ SettingsRoute: typeof SettingsRoute
+ ArticlesIdRoute: typeof ArticlesIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/settings': {
+ id: '/settings'
+ path: '/settings'
+ fullPath: '/settings'
+ preLoaderRoute: typeof SettingsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/docs': {
+ id: '/docs'
+ path: '/docs'
+ fullPath: '/docs'
+ preLoaderRoute: typeof DocsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/docs/$section': {
+ id: '/docs/$section'
+ path: '/$section'
+ fullPath: '/docs/$section'
+ preLoaderRoute: typeof DocsSectionRouteImport
+ parentRoute: typeof DocsRoute
+ }
+ '/articles/$id': {
+ id: '/articles/$id'
+ path: '/articles/$id'
+ fullPath: '/articles/$id'
+ preLoaderRoute: typeof ArticlesIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+interface DocsRouteChildren {
+ DocsSectionRoute: typeof DocsSectionRoute
+}
+
+const DocsRouteChildren: DocsRouteChildren = {
+ DocsSectionRoute: DocsSectionRoute,
+}
+
+const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ DocsRoute: DocsRouteWithChildren,
+ SettingsRoute: SettingsRoute,
+ ArticlesIdRoute: ArticlesIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/head/react/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/head/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..3d61dad0e9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/src/routes/__root.tsx
@@ -0,0 +1,59 @@
+import {
+ HeadContent,
+ Link,
+ Outlet,
+ createRootRoute,
+} from '@tanstack/react-router'
+import { articleIds, docsSections, rootHead } from '../../../shared'
+
+export const Route = createRootRoute({
+ head: rootHead,
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+ <>
+
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/src/routes/articles.$id.tsx b/benchmarks/client-nav/scenarios/head/react/src/routes/articles.$id.tsx
new file mode 100644
index 0000000000..6f5b78d60e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/src/routes/articles.$id.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { articleDescription, articleHead, articleTitle } from '../../../shared'
+
+export const Route = createFileRoute('/articles/$id')({
+ head: ({ params }) => articleHead(params.id),
+ component: ArticlePage,
+})
+
+function ArticlePage() {
+ const params = Route.useParams()
+
+ return (
+
+ {articleTitle(params.id)}
+ {articleDescription(params.id)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/src/routes/docs.$section.tsx b/benchmarks/client-nav/scenarios/head/react/src/routes/docs.$section.tsx
new file mode 100644
index 0000000000..6ed1935ada
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/src/routes/docs.$section.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { docsDescription, docsSectionHead, docsTitle } from '../../../shared'
+
+export const Route = createFileRoute('/docs/$section')({
+ head: ({ params }) => docsSectionHead(params.section),
+ component: DocsSectionPage,
+})
+
+function DocsSectionPage() {
+ const params = Route.useParams()
+
+ return (
+
+ {docsTitle(params.section)}
+ {docsDescription(params.section)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/src/routes/docs.tsx b/benchmarks/client-nav/scenarios/head/react/src/routes/docs.tsx
new file mode 100644
index 0000000000..a2da216ddb
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/src/routes/docs.tsx
@@ -0,0 +1,16 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import { docsLayoutHead } from '../../../shared'
+
+export const Route = createFileRoute('/docs')({
+ head: docsLayoutHead,
+ component: DocsLayout,
+})
+
+function DocsLayout() {
+ return (
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/src/routes/index.tsx b/benchmarks/client-nav/scenarios/head/react/src/routes/index.tsx
new file mode 100644
index 0000000000..6ac318452f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/src/routes/index.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { homeHead, homeTitle } from '../../../shared'
+
+export const Route = createFileRoute('/')({
+ head: homeHead,
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ {homeTitle}
+ Landing page
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/src/routes/settings.tsx b/benchmarks/client-nav/scenarios/head/react/src/routes/settings.tsx
new file mode 100644
index 0000000000..cd73902bff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/src/routes/settings.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { settingsHead, settingsTitle } from '../../../shared'
+
+export const Route = createFileRoute('/settings')({
+ head: settingsHead,
+ component: SettingsPage,
+})
+
+function SettingsPage() {
+ return (
+
+ {settingsTitle}
+ Account settings
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/tsconfig.json b/benchmarks/client-nav/scenarios/head/react/tsconfig.json
new file mode 100644
index 0000000000..1d59cbb450
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/head/react/vite.config.ts b/benchmarks/client-nav/scenarios/head/react/vite.config.ts
new file mode 100644
index 0000000000..086bd60bb4
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/react/vite.config.ts
@@ -0,0 +1,40 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'react',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav head (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/head/shared.ts b/benchmarks/client-nav/scenarios/head/shared.ts
new file mode 100644
index 0000000000..db185d602b
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/shared.ts
@@ -0,0 +1,152 @@
+/**
+ * Shared definition of the `head` scenario: route head() payloads, the click
+ * sequence, and per-step sanity assertions. The three framework apps consume
+ * these builders so the workload is identical modulo the rendering layer.
+ */
+
+const siteName = 'Head Bench'
+
+export const docsSections = ['api', 'guide', 'cli'] as const
+export const articleIds = ['1', '2', '3'] as const
+
+export const homeTitle = `Home | ${siteName}`
+export const settingsTitle = `Settings | ${siteName}`
+
+export function docsTitle(section: string) {
+ return `Docs ${section} | ${siteName}`
+}
+
+export function docsDescription(section: string) {
+ return `Documentation for the ${section} section of the head benchmark app.`
+}
+
+export function articleTitle(id: string) {
+ return `Article ${id} | ${siteName}`
+}
+
+export function articleDescription(id: string) {
+ return `Long-form article number ${id}, with a description sized like a realistic summary of the page content.`
+}
+
+export function rootHead() {
+ return {
+ meta: [
+ { charSet: 'utf-8' },
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
+ { name: 'application-name', content: siteName },
+ { name: 'description', content: 'Client head-management benchmark' },
+ { name: 'theme-color', content: '#111827' },
+ { property: 'og:type', content: 'website' },
+ { property: 'og:site_name', content: siteName },
+ ],
+ links: [
+ { rel: 'icon', href: '/favicon.ico' },
+ { rel: 'preconnect', href: 'https://assets.example.com' },
+ ],
+ }
+}
+
+export function homeHead() {
+ return {
+ meta: [
+ { title: homeTitle },
+ { name: 'description', content: 'Landing page of the head benchmark' },
+ { property: 'og:title', content: homeTitle },
+ ],
+ links: [{ rel: 'canonical', href: 'https://example.com/' }],
+ }
+}
+
+export function docsLayoutHead() {
+ return {
+ meta: [
+ // Overridden by the child section route on every docs navigation, so the
+ // dedupe path is exercised for both `name` and `property` entries.
+ { name: 'description', content: 'Documentation hub' },
+ { property: 'og:title', content: `Docs | ${siteName}` },
+ { name: 'docsearch:index', content: 'head-bench-docs' },
+ ],
+ links: [{ rel: 'search', href: '/docs/opensearch.xml' }],
+ }
+}
+
+export function docsSectionHead(section: string) {
+ return {
+ meta: [
+ { title: docsTitle(section) },
+ { name: 'description', content: docsDescription(section) },
+ { name: 'keywords', content: `docs,${section},router,benchmark` },
+ { property: 'og:title', content: docsTitle(section) },
+ { property: 'og:description', content: docsDescription(section) },
+ { property: 'og:url', content: `https://example.com/docs/${section}` },
+ { name: 'twitter:card', content: 'summary' },
+ { name: 'twitter:title', content: docsTitle(section) },
+ ],
+ links: [
+ { rel: 'canonical', href: `https://example.com/docs/${section}` },
+ { rel: 'prev', href: `https://example.com/docs/${section}/intro` },
+ ],
+ }
+}
+
+export function articleHead(id: string) {
+ return {
+ meta: [
+ { title: articleTitle(id) },
+ { name: 'description', content: articleDescription(id) },
+ { name: 'author', content: 'Bench Author' },
+ { property: 'og:title', content: articleTitle(id) },
+ { property: 'og:description', content: articleDescription(id) },
+ { property: 'og:url', content: `https://example.com/articles/${id}` },
+ { property: 'og:image', content: `https://example.com/covers/${id}.png` },
+ { property: 'article:published_time', content: '2026-01-01T00:00:00Z' },
+ { name: 'twitter:card', content: 'summary_large_image' },
+ { name: 'twitter:title', content: articleTitle(id) },
+ ],
+ links: [{ rel: 'canonical', href: `https://example.com/articles/${id}` }],
+ }
+}
+
+export function settingsHead() {
+ return {
+ meta: [
+ { title: settingsTitle },
+ // Same names as root entries, so the child-wins dedupe path runs.
+ { name: 'description', content: 'Account settings' },
+ { name: 'theme-color', content: '#f9fafb' },
+ { name: 'robots', content: 'noindex' },
+ ],
+ links: [],
+ }
+}
+
+export const steps = [
+ { testId: 'go-docs-api', title: docsTitle('api') },
+ { testId: 'go-article-1', title: articleTitle('1') },
+ { testId: 'go-docs-guide', title: docsTitle('guide') },
+ { testId: 'go-settings', title: settingsTitle },
+ { testId: 'go-article-2', title: articleTitle('2') },
+ { testId: 'go-docs-cli', title: docsTitle('cli') },
+ { testId: 'go-article-3', title: articleTitle('3') },
+ { testId: 'go-home', title: homeTitle },
+] as const
+
+export const stepTestIds = steps.map((step) => step.testId)
+
+export function assertStepResult(stepIndex: number) {
+ const expected = steps[stepIndex]!.title
+ if (document.title !== expected) {
+ throw new Error(
+ `Expected document.title to be "${expected}" after step ${stepIndex}, received "${document.title}"`,
+ )
+ }
+}
+
+// Two laps through the 8-step sequence per benchmark iteration.
+export const ticksPerIteration = 16
+
+export const benchOptions = {
+ warmupIterations: 100,
+ time: 10_000,
+ throws: true,
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/project.json b/benchmarks/client-nav/scenarios/head/solid/project.json
new file mode 100644
index 0000000000..2575a19b89
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-head-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/head/solid/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/setup.ts b/benchmarks/client-nav/scenarios/head/solid/setup.ts
new file mode 100644
index 0000000000..4ee924acff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, stepTestIds } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Solid',
+ mount: mountTestApp,
+ steps: stepTestIds,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/speed.bench.ts b/benchmarks/client-nav/scenarios/head/solid/speed.bench.ts
new file mode 100644
index 0000000000..ea0a5ca818
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-head', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-head navigation loop (solid)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/head/solid/speed.flame.ts b/benchmarks/client-nav/scenarios/head/solid/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/src/main.tsx b/benchmarks/client-nav/scenarios/head/solid/src/main.tsx
new file mode 100644
index 0000000000..817045bd02
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/src/main.tsx
@@ -0,0 +1,32 @@
+import { render } from 'solid-js/web'
+import { RouterProvider, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const unmount = render(() => , container)
+
+ return {
+ router,
+ unmount,
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/head/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..ead6a4436a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/src/routeTree.gen.ts
@@ -0,0 +1,145 @@
+/* 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 SettingsRouteImport } from './routes/settings'
+import { Route as DocsRouteImport } from './routes/docs'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as DocsSectionRouteImport } from './routes/docs.$section'
+import { Route as ArticlesIdRouteImport } from './routes/articles.$id'
+
+const SettingsRoute = SettingsRouteImport.update({
+ id: '/settings',
+ path: '/settings',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DocsRoute = DocsRouteImport.update({
+ id: '/docs',
+ path: '/docs',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DocsSectionRoute = DocsSectionRouteImport.update({
+ id: '/$section',
+ path: '/$section',
+ getParentRoute: () => DocsRoute,
+} as any)
+const ArticlesIdRoute = ArticlesIdRouteImport.update({
+ id: '/articles/$id',
+ path: '/articles/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/docs' | '/settings' | '/articles/$id' | '/docs/$section'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/docs' | '/settings' | '/articles/$id' | '/docs/$section'
+ id:
+ | '__root__'
+ | '/'
+ | '/docs'
+ | '/settings'
+ | '/articles/$id'
+ | '/docs/$section'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ DocsRoute: typeof DocsRouteWithChildren
+ SettingsRoute: typeof SettingsRoute
+ ArticlesIdRoute: typeof ArticlesIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/settings': {
+ id: '/settings'
+ path: '/settings'
+ fullPath: '/settings'
+ preLoaderRoute: typeof SettingsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/docs': {
+ id: '/docs'
+ path: '/docs'
+ fullPath: '/docs'
+ preLoaderRoute: typeof DocsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/docs/$section': {
+ id: '/docs/$section'
+ path: '/$section'
+ fullPath: '/docs/$section'
+ preLoaderRoute: typeof DocsSectionRouteImport
+ parentRoute: typeof DocsRoute
+ }
+ '/articles/$id': {
+ id: '/articles/$id'
+ path: '/articles/$id'
+ fullPath: '/articles/$id'
+ preLoaderRoute: typeof ArticlesIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+interface DocsRouteChildren {
+ DocsSectionRoute: typeof DocsSectionRoute
+}
+
+const DocsRouteChildren: DocsRouteChildren = {
+ DocsSectionRoute: DocsSectionRoute,
+}
+
+const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ DocsRoute: DocsRouteWithChildren,
+ SettingsRoute: SettingsRoute,
+ ArticlesIdRoute: ArticlesIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/head/solid/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/head/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..52ca7da373
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/src/routes/__root.tsx
@@ -0,0 +1,58 @@
+import { For } from 'solid-js'
+import {
+ HeadContent,
+ Link,
+ Outlet,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { articleIds, docsSections, rootHead } from '../../../shared'
+
+export const Route = createRootRoute({
+ head: rootHead,
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+ <>
+
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/src/routes/articles.$id.tsx b/benchmarks/client-nav/scenarios/head/solid/src/routes/articles.$id.tsx
new file mode 100644
index 0000000000..dab11ada18
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/src/routes/articles.$id.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { articleDescription, articleHead, articleTitle } from '../../../shared'
+
+export const Route = createFileRoute('/articles/$id')({
+ head: ({ params }) => articleHead(params.id),
+ component: ArticlePage,
+})
+
+function ArticlePage() {
+ const params = Route.useParams()
+
+ return (
+
+ {articleTitle(params().id)}
+ {articleDescription(params().id)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/src/routes/docs.$section.tsx b/benchmarks/client-nav/scenarios/head/solid/src/routes/docs.$section.tsx
new file mode 100644
index 0000000000..7ab1123e7a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/src/routes/docs.$section.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { docsDescription, docsSectionHead, docsTitle } from '../../../shared'
+
+export const Route = createFileRoute('/docs/$section')({
+ head: ({ params }) => docsSectionHead(params.section),
+ component: DocsSectionPage,
+})
+
+function DocsSectionPage() {
+ const params = Route.useParams()
+
+ return (
+
+ {docsTitle(params().section)}
+ {docsDescription(params().section)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/src/routes/docs.tsx b/benchmarks/client-nav/scenarios/head/solid/src/routes/docs.tsx
new file mode 100644
index 0000000000..7f84b214d0
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/src/routes/docs.tsx
@@ -0,0 +1,16 @@
+import { Outlet, createFileRoute } from '@tanstack/solid-router'
+import { docsLayoutHead } from '../../../shared'
+
+export const Route = createFileRoute('/docs')({
+ head: docsLayoutHead,
+ component: DocsLayout,
+})
+
+function DocsLayout() {
+ return (
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/src/routes/index.tsx b/benchmarks/client-nav/scenarios/head/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..229eecc8c1
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/src/routes/index.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { homeHead, homeTitle } from '../../../shared'
+
+export const Route = createFileRoute('/')({
+ head: homeHead,
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ {homeTitle}
+ Landing page
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/src/routes/settings.tsx b/benchmarks/client-nav/scenarios/head/solid/src/routes/settings.tsx
new file mode 100644
index 0000000000..88424caef3
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/src/routes/settings.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { settingsHead, settingsTitle } from '../../../shared'
+
+export const Route = createFileRoute('/settings')({
+ head: settingsHead,
+ component: SettingsPage,
+})
+
+function SettingsPage() {
+ return (
+
+ {settingsTitle}
+ Account settings
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/tsconfig.json b/benchmarks/client-nav/scenarios/head/solid/tsconfig.json
new file mode 100644
index 0000000000..98d76ad6b8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/head/solid/vite.config.ts b/benchmarks/client-nav/scenarios/head/solid/vite.config.ts
new file mode 100644
index 0000000000..7b5a1e22b4
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/solid/vite.config.ts
@@ -0,0 +1,48 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import solid from 'vite-plugin-solid'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'solid',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ resolve: {
+ conditions: ['solid', 'browser'],
+ },
+ test: {
+ name: '@benchmarks/client-nav head (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/head/vue/project.json b/benchmarks/client-nav/scenarios/head/vue/project.json
new file mode 100644
index 0000000000..5ad7909d74
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-head-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/head/vue/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/head/vue/setup.ts b/benchmarks/client-nav/scenarios/head/vue/setup.ts
new file mode 100644
index 0000000000..3dd6bc95a5
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, stepTestIds } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Vue',
+ mount: mountTestApp,
+ steps: stepTestIds,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/head/vue/speed.bench.ts b/benchmarks/client-nav/scenarios/head/vue/speed.bench.ts
new file mode 100644
index 0000000000..3630032e44
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-head', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-head navigation loop (vue)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/head/vue/speed.flame.ts b/benchmarks/client-nav/scenarios/head/vue/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/head/vue/src/main.tsx b/benchmarks/client-nav/scenarios/head/vue/src/main.tsx
new file mode 100644
index 0000000000..c96d3f3a2e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/src/main.tsx
@@ -0,0 +1,39 @@
+import * as Vue from 'vue'
+import { RouterProvider, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const component =
+ const app = Vue.createApp({
+ render: () => component,
+ })
+
+ app.mount(container)
+
+ return {
+ router,
+ unmount() {
+ app.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/head/vue/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/head/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..b115854c26
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/src/routeTree.gen.ts
@@ -0,0 +1,145 @@
+/* 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 SettingsRouteImport } from './routes/settings'
+import { Route as DocsRouteImport } from './routes/docs'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as DocsSectionRouteImport } from './routes/docs.$section'
+import { Route as ArticlesIdRouteImport } from './routes/articles.$id'
+
+const SettingsRoute = SettingsRouteImport.update({
+ id: '/settings',
+ path: '/settings',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DocsRoute = DocsRouteImport.update({
+ id: '/docs',
+ path: '/docs',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DocsSectionRoute = DocsSectionRouteImport.update({
+ id: '/$section',
+ path: '/$section',
+ getParentRoute: () => DocsRoute,
+} as any)
+const ArticlesIdRoute = ArticlesIdRouteImport.update({
+ id: '/articles/$id',
+ path: '/articles/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/settings': typeof SettingsRoute
+ '/articles/$id': typeof ArticlesIdRoute
+ '/docs/$section': typeof DocsSectionRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/docs' | '/settings' | '/articles/$id' | '/docs/$section'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/docs' | '/settings' | '/articles/$id' | '/docs/$section'
+ id:
+ | '__root__'
+ | '/'
+ | '/docs'
+ | '/settings'
+ | '/articles/$id'
+ | '/docs/$section'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ DocsRoute: typeof DocsRouteWithChildren
+ SettingsRoute: typeof SettingsRoute
+ ArticlesIdRoute: typeof ArticlesIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/settings': {
+ id: '/settings'
+ path: '/settings'
+ fullPath: '/settings'
+ preLoaderRoute: typeof SettingsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/docs': {
+ id: '/docs'
+ path: '/docs'
+ fullPath: '/docs'
+ preLoaderRoute: typeof DocsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/docs/$section': {
+ id: '/docs/$section'
+ path: '/$section'
+ fullPath: '/docs/$section'
+ preLoaderRoute: typeof DocsSectionRouteImport
+ parentRoute: typeof DocsRoute
+ }
+ '/articles/$id': {
+ id: '/articles/$id'
+ path: '/articles/$id'
+ fullPath: '/articles/$id'
+ preLoaderRoute: typeof ArticlesIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+interface DocsRouteChildren {
+ DocsSectionRoute: typeof DocsSectionRoute
+}
+
+const DocsRouteChildren: DocsRouteChildren = {
+ DocsSectionRoute: DocsSectionRoute,
+}
+
+const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ DocsRoute: DocsRouteWithChildren,
+ SettingsRoute: SettingsRoute,
+ ArticlesIdRoute: ArticlesIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/head/vue/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/head/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..ad87699fef
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/src/routes/__root.tsx
@@ -0,0 +1,58 @@
+import * as Vue from 'vue'
+import {
+ HeadContent,
+ Link,
+ Outlet,
+ createRootRoute,
+} from '@tanstack/vue-router'
+import { articleIds, docsSections, rootHead } from '../../../shared'
+
+const RootComponent = Vue.defineComponent({
+ setup() {
+ return () => (
+ <>
+
+
+
+ >
+ )
+ },
+})
+
+export const Route = createRootRoute({
+ head: rootHead,
+ component: RootComponent,
+})
diff --git a/benchmarks/client-nav/scenarios/head/vue/src/routes/articles.$id.tsx b/benchmarks/client-nav/scenarios/head/vue/src/routes/articles.$id.tsx
new file mode 100644
index 0000000000..43a6fade58
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/src/routes/articles.$id.tsx
@@ -0,0 +1,21 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { articleDescription, articleHead, articleTitle } from '../../../shared'
+
+const ArticlePage = Vue.defineComponent({
+ setup() {
+ const params = Route.useParams()
+
+ return () => (
+
+ {articleTitle(params.value.id)}
+ {articleDescription(params.value.id)}
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/articles/$id')({
+ head: ({ params }) => articleHead(params.id),
+ component: ArticlePage,
+})
diff --git a/benchmarks/client-nav/scenarios/head/vue/src/routes/docs.$section.tsx b/benchmarks/client-nav/scenarios/head/vue/src/routes/docs.$section.tsx
new file mode 100644
index 0000000000..ebb59d61a2
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/src/routes/docs.$section.tsx
@@ -0,0 +1,21 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { docsDescription, docsSectionHead, docsTitle } from '../../../shared'
+
+const DocsSectionPage = Vue.defineComponent({
+ setup() {
+ const params = Route.useParams()
+
+ return () => (
+
+ {docsTitle(params.value.section)}
+ {docsDescription(params.value.section)}
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/docs/$section')({
+ head: ({ params }) => docsSectionHead(params.section),
+ component: DocsSectionPage,
+})
diff --git a/benchmarks/client-nav/scenarios/head/vue/src/routes/docs.tsx b/benchmarks/client-nav/scenarios/head/vue/src/routes/docs.tsx
new file mode 100644
index 0000000000..feabbc4d4a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/src/routes/docs.tsx
@@ -0,0 +1,19 @@
+import * as Vue from 'vue'
+import { Outlet, createFileRoute } from '@tanstack/vue-router'
+import { docsLayoutHead } from '../../../shared'
+
+const DocsLayout = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/docs')({
+ head: docsLayoutHead,
+ component: DocsLayout,
+})
diff --git a/benchmarks/client-nav/scenarios/head/vue/src/routes/index.tsx b/benchmarks/client-nav/scenarios/head/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..421478833f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/src/routes/index.tsx
@@ -0,0 +1,19 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { homeHead, homeTitle } from '../../../shared'
+
+const HomePage = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ {homeTitle}
+ Landing page
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/')({
+ head: homeHead,
+ component: HomePage,
+})
diff --git a/benchmarks/client-nav/scenarios/head/vue/src/routes/settings.tsx b/benchmarks/client-nav/scenarios/head/vue/src/routes/settings.tsx
new file mode 100644
index 0000000000..1a519e16c2
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/src/routes/settings.tsx
@@ -0,0 +1,19 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { settingsHead, settingsTitle } from '../../../shared'
+
+const SettingsPage = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ {settingsTitle}
+ Account settings
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/settings')({
+ head: settingsHead,
+ component: SettingsPage,
+})
diff --git a/benchmarks/client-nav/scenarios/head/vue/tsconfig.json b/benchmarks/client-nav/scenarios/head/vue/tsconfig.json
new file mode 100644
index 0000000000..7786fc3da9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/head/vue/vite.config.ts b/benchmarks/client-nav/scenarios/head/vue/vite.config.ts
new file mode 100644
index 0000000000..0ad2067925
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/head/vue/vite.config.ts
@@ -0,0 +1,42 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'vue',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav head (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/history/react/project.json b/benchmarks/client-nav/scenarios/history/react/project.json
new file mode 100644
index 0000000000..a2fe0001be
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-history-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/history/react/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/setup.ts b/benchmarks/client-nav/scenarios/history/react/setup.ts
new file mode 100644
index 0000000000..130583b190
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'React',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/speed.bench.ts b/benchmarks/client-nav/scenarios/history/react/speed.bench.ts
new file mode 100644
index 0000000000..df2c951c03
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-history', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-history navigation loop (react)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/history/react/speed.flame.ts b/benchmarks/client-nav/scenarios/history/react/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/src/main.tsx b/benchmarks/client-nav/scenarios/history/react/src/main.tsx
new file mode 100644
index 0000000000..ef0b99555c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/src/main.tsx
@@ -0,0 +1,35 @@
+import { RouterProvider, createRouter } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const reactRoot = createRoot(container)
+ reactRoot.render()
+
+ return {
+ router,
+ unmount() {
+ reactRoot.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/history/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..61cc83c4c8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/src/routeTree.gen.ts
@@ -0,0 +1,113 @@
+/* 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 GalleryRouteImport } from './routes/gallery'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as PhotosPhotoIdRouteImport } from './routes/photos.$photoId'
+import { Route as PagesNRouteImport } from './routes/pages.$n'
+
+const GalleryRoute = GalleryRouteImport.update({
+ id: '/gallery',
+ path: '/gallery',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PhotosPhotoIdRoute = PhotosPhotoIdRouteImport.update({
+ id: '/photos/$photoId',
+ path: '/photos/$photoId',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PagesNRoute = PagesNRouteImport.update({
+ id: '/pages/$n',
+ path: '/pages/$n',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ id: '__root__' | '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ GalleryRoute: typeof GalleryRoute
+ PagesNRoute: typeof PagesNRoute
+ PhotosPhotoIdRoute: typeof PhotosPhotoIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/gallery': {
+ id: '/gallery'
+ path: '/gallery'
+ fullPath: '/gallery'
+ preLoaderRoute: typeof GalleryRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/photos/$photoId': {
+ id: '/photos/$photoId'
+ path: '/photos/$photoId'
+ fullPath: '/photos/$photoId'
+ preLoaderRoute: typeof PhotosPhotoIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/pages/$n': {
+ id: '/pages/$n'
+ path: '/pages/$n'
+ fullPath: '/pages/$n'
+ preLoaderRoute: typeof PagesNRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ GalleryRoute: GalleryRoute,
+ PagesNRoute: PagesNRoute,
+ PhotosPhotoIdRoute: PhotosPhotoIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/history/react/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/history/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..240d8719d4
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/src/routes/__root.tsx
@@ -0,0 +1,66 @@
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useBlocker,
+ useCanGoBack,
+ useLocation,
+} from '@tanstack/react-router'
+import { maskedPhotoId, shouldBlockFn } from '../../../shared'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ const pathname = useLocation({ select: (location) => location.pathname })
+ const canGoBack = useCanGoBack()
+
+ useBlocker({ shouldBlockFn })
+
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/src/routes/gallery.tsx b/benchmarks/client-nav/scenarios/history/react/src/routes/gallery.tsx
new file mode 100644
index 0000000000..55094556a6
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/src/routes/gallery.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/gallery')({
+ component: GalleryPage,
+})
+
+function GalleryPage() {
+ return (
+
+ Gallery
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/src/routes/index.tsx b/benchmarks/client-nav/scenarios/history/react/src/routes/index.tsx
new file mode 100644
index 0000000000..932a72f2ef
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/src/routes/index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ Home
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/src/routes/pages.$n.tsx b/benchmarks/client-nav/scenarios/history/react/src/routes/pages.$n.tsx
new file mode 100644
index 0000000000..b9c5e2f8d0
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/src/routes/pages.$n.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { pageLabel } from '../../../shared'
+
+export const Route = createFileRoute('/pages/$n')({
+ component: PageComponent,
+})
+
+function PageComponent() {
+ const params = Route.useParams()
+
+ return (
+
+ {pageLabel(params.n)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/src/routes/photos.$photoId.tsx b/benchmarks/client-nav/scenarios/history/react/src/routes/photos.$photoId.tsx
new file mode 100644
index 0000000000..793b531c89
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/src/routes/photos.$photoId.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { photoLabel } from '../../../shared'
+
+export const Route = createFileRoute('/photos/$photoId')({
+ component: PhotoComponent,
+})
+
+function PhotoComponent() {
+ const params = Route.useParams()
+
+ return (
+
+ {photoLabel(params.photoId)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/tsconfig.json b/benchmarks/client-nav/scenarios/history/react/tsconfig.json
new file mode 100644
index 0000000000..1d59cbb450
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/history/react/vite.config.ts b/benchmarks/client-nav/scenarios/history/react/vite.config.ts
new file mode 100644
index 0000000000..61a1b2feef
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/react/vite.config.ts
@@ -0,0 +1,40 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'react',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav history (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/history/shared.ts b/benchmarks/client-nav/scenarios/history/shared.ts
new file mode 100644
index 0000000000..9ddfa6c104
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/shared.ts
@@ -0,0 +1,89 @@
+/**
+ * Shared definition of the `history` scenario: push/replace/back/forward
+ * traversal, location masking, blocker registration overhead (the blocker
+ * never blocks), and useLocation/useCanGoBack subscriptions.
+ *
+ * The step lap is designed to keep the history depth stationary: it ends back
+ * on the initial entry, so the first push of the next lap truncates the stale
+ * forward tail and the session history never grows past 4 entries.
+ */
+import type { ScenarioStep } from '../harness'
+
+export const pageIds = ['1', '2', '3'] as const
+export const maskedPhotoId = '42'
+
+export function pageLabel(n: string) {
+ return `Page ${n}`
+}
+
+export function photoLabel(id: string) {
+ return `Photo ${id}`
+}
+
+/** Registered on every navigation, never blocks. */
+export const shouldBlockFn = () => false
+
+interface HistoryStepExpectation {
+ step: ScenarioStep
+ /** Expected router location pathname (the REAL location, even when masked). */
+ pathname: string
+ /** Expected window.location pathname when it differs (location masking). */
+ windowPathname?: string
+ /** Marker test id that must be present in the container after the step. */
+ marker?: string
+}
+
+export const stepExpectations: ReadonlyArray = [
+ { step: 'p-1', pathname: '/pages/1', marker: 'page-state' },
+ { step: 'p-2', pathname: '/pages/2', marker: 'page-state' },
+ {
+ step: 'photo-masked',
+ pathname: `/photos/${maskedPhotoId}`,
+ windowPathname: '/gallery',
+ marker: 'photo-state',
+ },
+ { step: { type: 'back' }, pathname: '/pages/2', marker: 'page-state' },
+ { step: { type: 'back' }, pathname: '/pages/1', marker: 'page-state' },
+ { step: { type: 'forward' }, pathname: '/pages/2', marker: 'page-state' },
+ { step: 'p-3-replace', pathname: '/pages/3', marker: 'page-state' },
+ { step: { type: 'back' }, pathname: '/pages/1', marker: 'page-state' },
+ { step: { type: 'back' }, pathname: '/', marker: 'home-state' },
+]
+
+export const steps = stepExpectations.map((expectation) => expectation.step)
+
+export function assertStepResult(stepIndex: number, container: HTMLElement) {
+ const expected = stepExpectations[stepIndex]!
+
+ const loc = container.querySelector('[data-testid="loc"]')?.textContent
+ if (loc !== expected.pathname) {
+ throw new Error(
+ `Expected location marker to be "${expected.pathname}" after step ${stepIndex}, received "${loc}"`,
+ )
+ }
+
+ const windowPathname = expected.windowPathname ?? expected.pathname
+ if (window.location.pathname !== windowPathname) {
+ throw new Error(
+ `Expected window.location.pathname to be "${windowPathname}" after step ${stepIndex}, received "${window.location.pathname}"`,
+ )
+ }
+
+ if (
+ expected.marker &&
+ !container.querySelector(`[data-testid="${expected.marker}"]`)
+ ) {
+ throw new Error(
+ `Expected marker "${expected.marker}" to be rendered after step ${stepIndex}`,
+ )
+ }
+}
+
+// Two laps through the 9-step sequence per benchmark iteration.
+export const ticksPerIteration = 18
+
+export const benchOptions = {
+ warmupIterations: 100,
+ time: 10_000,
+ throws: true,
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/project.json b/benchmarks/client-nav/scenarios/history/solid/project.json
new file mode 100644
index 0000000000..6f4bba1ca5
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-history-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/history/solid/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/setup.ts b/benchmarks/client-nav/scenarios/history/solid/setup.ts
new file mode 100644
index 0000000000..6a46971603
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Solid',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/speed.bench.ts b/benchmarks/client-nav/scenarios/history/solid/speed.bench.ts
new file mode 100644
index 0000000000..8e814bc133
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-history', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-history navigation loop (solid)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/history/solid/speed.flame.ts b/benchmarks/client-nav/scenarios/history/solid/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/src/main.tsx b/benchmarks/client-nav/scenarios/history/solid/src/main.tsx
new file mode 100644
index 0000000000..817045bd02
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/src/main.tsx
@@ -0,0 +1,32 @@
+import { render } from 'solid-js/web'
+import { RouterProvider, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const unmount = render(() => , container)
+
+ return {
+ router,
+ unmount,
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/history/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..a6ab2a4fc8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/src/routeTree.gen.ts
@@ -0,0 +1,113 @@
+/* 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 GalleryRouteImport } from './routes/gallery'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as PhotosPhotoIdRouteImport } from './routes/photos.$photoId'
+import { Route as PagesNRouteImport } from './routes/pages.$n'
+
+const GalleryRoute = GalleryRouteImport.update({
+ id: '/gallery',
+ path: '/gallery',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PhotosPhotoIdRoute = PhotosPhotoIdRouteImport.update({
+ id: '/photos/$photoId',
+ path: '/photos/$photoId',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PagesNRoute = PagesNRouteImport.update({
+ id: '/pages/$n',
+ path: '/pages/$n',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ id: '__root__' | '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ GalleryRoute: typeof GalleryRoute
+ PagesNRoute: typeof PagesNRoute
+ PhotosPhotoIdRoute: typeof PhotosPhotoIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/gallery': {
+ id: '/gallery'
+ path: '/gallery'
+ fullPath: '/gallery'
+ preLoaderRoute: typeof GalleryRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/photos/$photoId': {
+ id: '/photos/$photoId'
+ path: '/photos/$photoId'
+ fullPath: '/photos/$photoId'
+ preLoaderRoute: typeof PhotosPhotoIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/pages/$n': {
+ id: '/pages/$n'
+ path: '/pages/$n'
+ fullPath: '/pages/$n'
+ preLoaderRoute: typeof PagesNRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ GalleryRoute: GalleryRoute,
+ PagesNRoute: PagesNRoute,
+ PhotosPhotoIdRoute: PhotosPhotoIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/history/solid/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/history/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..edf7368929
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/src/routes/__root.tsx
@@ -0,0 +1,66 @@
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useBlocker,
+ useCanGoBack,
+ useLocation,
+} from '@tanstack/solid-router'
+import { maskedPhotoId, shouldBlockFn } from '../../../shared'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ const pathname = useLocation({ select: (location) => location.pathname })
+ const canGoBack = useCanGoBack()
+
+ useBlocker({ shouldBlockFn })
+
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/src/routes/gallery.tsx b/benchmarks/client-nav/scenarios/history/solid/src/routes/gallery.tsx
new file mode 100644
index 0000000000..b72d1c6195
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/src/routes/gallery.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/gallery')({
+ component: GalleryPage,
+})
+
+function GalleryPage() {
+ return (
+
+ Gallery
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/src/routes/index.tsx b/benchmarks/client-nav/scenarios/history/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..44a0b8045a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/src/routes/index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ Home
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/src/routes/pages.$n.tsx b/benchmarks/client-nav/scenarios/history/solid/src/routes/pages.$n.tsx
new file mode 100644
index 0000000000..1def6674c6
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/src/routes/pages.$n.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { pageLabel } from '../../../shared'
+
+export const Route = createFileRoute('/pages/$n')({
+ component: PageComponent,
+})
+
+function PageComponent() {
+ const params = Route.useParams()
+
+ return (
+
+ {pageLabel(params().n)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/src/routes/photos.$photoId.tsx b/benchmarks/client-nav/scenarios/history/solid/src/routes/photos.$photoId.tsx
new file mode 100644
index 0000000000..ab2fe06e3f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/src/routes/photos.$photoId.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { photoLabel } from '../../../shared'
+
+export const Route = createFileRoute('/photos/$photoId')({
+ component: PhotoComponent,
+})
+
+function PhotoComponent() {
+ const params = Route.useParams()
+
+ return (
+
+ {photoLabel(params().photoId)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/tsconfig.json b/benchmarks/client-nav/scenarios/history/solid/tsconfig.json
new file mode 100644
index 0000000000..98d76ad6b8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/history/solid/vite.config.ts b/benchmarks/client-nav/scenarios/history/solid/vite.config.ts
new file mode 100644
index 0000000000..de0eda543c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/solid/vite.config.ts
@@ -0,0 +1,48 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import solid from 'vite-plugin-solid'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'solid',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ resolve: {
+ conditions: ['solid', 'browser'],
+ },
+ test: {
+ name: '@benchmarks/client-nav history (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/history/vue/project.json b/benchmarks/client-nav/scenarios/history/vue/project.json
new file mode 100644
index 0000000000..17fca887ca
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-history-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/history/vue/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/history/vue/setup.ts b/benchmarks/client-nav/scenarios/history/vue/setup.ts
new file mode 100644
index 0000000000..97b74b3892
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Vue',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/history/vue/speed.bench.ts b/benchmarks/client-nav/scenarios/history/vue/speed.bench.ts
new file mode 100644
index 0000000000..be2956bd85
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-history', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-history navigation loop (vue)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/history/vue/speed.flame.ts b/benchmarks/client-nav/scenarios/history/vue/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/history/vue/src/main.tsx b/benchmarks/client-nav/scenarios/history/vue/src/main.tsx
new file mode 100644
index 0000000000..c96d3f3a2e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/src/main.tsx
@@ -0,0 +1,39 @@
+import * as Vue from 'vue'
+import { RouterProvider, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const component =
+ const app = Vue.createApp({
+ render: () => component,
+ })
+
+ app.mount(container)
+
+ return {
+ router,
+ unmount() {
+ app.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/history/vue/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/history/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..cee1b68870
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/src/routeTree.gen.ts
@@ -0,0 +1,113 @@
+/* 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 GalleryRouteImport } from './routes/gallery'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as PhotosPhotoIdRouteImport } from './routes/photos.$photoId'
+import { Route as PagesNRouteImport } from './routes/pages.$n'
+
+const GalleryRoute = GalleryRouteImport.update({
+ id: '/gallery',
+ path: '/gallery',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PhotosPhotoIdRoute = PhotosPhotoIdRouteImport.update({
+ id: '/photos/$photoId',
+ path: '/photos/$photoId',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PagesNRoute = PagesNRouteImport.update({
+ id: '/pages/$n',
+ path: '/pages/$n',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/gallery': typeof GalleryRoute
+ '/pages/$n': typeof PagesNRoute
+ '/photos/$photoId': typeof PhotosPhotoIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ id: '__root__' | '/' | '/gallery' | '/pages/$n' | '/photos/$photoId'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ GalleryRoute: typeof GalleryRoute
+ PagesNRoute: typeof PagesNRoute
+ PhotosPhotoIdRoute: typeof PhotosPhotoIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/gallery': {
+ id: '/gallery'
+ path: '/gallery'
+ fullPath: '/gallery'
+ preLoaderRoute: typeof GalleryRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/photos/$photoId': {
+ id: '/photos/$photoId'
+ path: '/photos/$photoId'
+ fullPath: '/photos/$photoId'
+ preLoaderRoute: typeof PhotosPhotoIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/pages/$n': {
+ id: '/pages/$n'
+ path: '/pages/$n'
+ fullPath: '/pages/$n'
+ preLoaderRoute: typeof PagesNRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ GalleryRoute: GalleryRoute,
+ PagesNRoute: PagesNRoute,
+ PhotosPhotoIdRoute: PhotosPhotoIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/history/vue/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/history/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..7f7dff159c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/src/routes/__root.tsx
@@ -0,0 +1,69 @@
+import * as Vue from 'vue'
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useBlocker,
+ useCanGoBack,
+ useLocation,
+} from '@tanstack/vue-router'
+import { maskedPhotoId, shouldBlockFn } from '../../../shared'
+
+const RootComponent = Vue.defineComponent({
+ setup() {
+ const pathname = useLocation({ select: (location) => location.pathname })
+ const canGoBack = useCanGoBack()
+
+ useBlocker({ shouldBlockFn })
+
+ return () => (
+ <>
+
+
+ >
+ )
+ },
+})
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
diff --git a/benchmarks/client-nav/scenarios/history/vue/src/routes/gallery.tsx b/benchmarks/client-nav/scenarios/history/vue/src/routes/gallery.tsx
new file mode 100644
index 0000000000..3a397b1843
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/src/routes/gallery.tsx
@@ -0,0 +1,16 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+
+const GalleryPage = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ Gallery
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/gallery')({
+ component: GalleryPage,
+})
diff --git a/benchmarks/client-nav/scenarios/history/vue/src/routes/index.tsx b/benchmarks/client-nav/scenarios/history/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..fbc5b957a7
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/src/routes/index.tsx
@@ -0,0 +1,16 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+
+const HomePage = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ Home
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
diff --git a/benchmarks/client-nav/scenarios/history/vue/src/routes/pages.$n.tsx b/benchmarks/client-nav/scenarios/history/vue/src/routes/pages.$n.tsx
new file mode 100644
index 0000000000..560e83ed29
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/src/routes/pages.$n.tsx
@@ -0,0 +1,19 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { pageLabel } from '../../../shared'
+
+const PageComponent = Vue.defineComponent({
+ setup() {
+ const params = Route.useParams()
+
+ return () => (
+
+ {pageLabel(params.value.n)}
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/pages/$n')({
+ component: PageComponent,
+})
diff --git a/benchmarks/client-nav/scenarios/history/vue/src/routes/photos.$photoId.tsx b/benchmarks/client-nav/scenarios/history/vue/src/routes/photos.$photoId.tsx
new file mode 100644
index 0000000000..186566fe72
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/src/routes/photos.$photoId.tsx
@@ -0,0 +1,19 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { photoLabel } from '../../../shared'
+
+const PhotoComponent = Vue.defineComponent({
+ setup() {
+ const params = Route.useParams()
+
+ return () => (
+
+ {photoLabel(params.value.photoId)}
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/photos/$photoId')({
+ component: PhotoComponent,
+})
diff --git a/benchmarks/client-nav/scenarios/history/vue/tsconfig.json b/benchmarks/client-nav/scenarios/history/vue/tsconfig.json
new file mode 100644
index 0000000000..7786fc3da9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/history/vue/vite.config.ts b/benchmarks/client-nav/scenarios/history/vue/vite.config.ts
new file mode 100644
index 0000000000..58986d6526
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/history/vue/vite.config.ts
@@ -0,0 +1,42 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'vue',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav history (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/links/react/project.json b/benchmarks/client-nav/scenarios/links/react/project.json
new file mode 100644
index 0000000000..52c9df8a68
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-links-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/links/react/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/setup.ts b/benchmarks/client-nav/scenarios/links/react/setup.ts
new file mode 100644
index 0000000000..5409df903c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, scenarioSteps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'React',
+ mount: mountTestApp,
+ steps: scenarioSteps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/speed.bench.ts b/benchmarks/client-nav/scenarios/links/react/speed.bench.ts
new file mode 100644
index 0000000000..68bb869182
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-links', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-links navigation loop (react)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/links/react/speed.flame.ts b/benchmarks/client-nav/scenarios/links/react/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/src/main.tsx b/benchmarks/client-nav/scenarios/links/react/src/main.tsx
new file mode 100644
index 0000000000..ef0b99555c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/src/main.tsx
@@ -0,0 +1,35 @@
+import { RouterProvider, createRouter } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const reactRoot = createRoot(container)
+ reactRoot.render()
+
+ return {
+ router,
+ unmount() {
+ reactRoot.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/links/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..97ec042c1c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/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 AboutRouteImport } from './routes/about'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const AboutRoute = AboutRouteImport.update({
+ id: '/about',
+ path: '/about',
+ getParentRoute: () => rootRouteImport,
+} as any)
+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
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/about' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/about' | '/items/$id'
+ id: '__root__' | '/' | '/about' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ AboutRoute: typeof AboutRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/about': {
+ id: '/about'
+ path: '/about'
+ fullPath: '/about'
+ preLoaderRoute: typeof AboutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ 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,
+ AboutRoute: AboutRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/links/react/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/links/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..7a48510e8a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/src/routes/__root.tsx
@@ -0,0 +1,104 @@
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useMatchRoute,
+} from '@tanstack/react-router'
+import {
+ itemIds,
+ matchProbeIds,
+ stepItemIds,
+ variantSearch,
+} from '../../../shared'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function StatusPanel() {
+ const matchRoute = useMatchRoute()
+
+ return (
+
+ )
+}
+
+function LinkGrid() {
+ return (
+
+ {itemIds.map((id) => (
+
+
+ {`Item ${id}`}
+
+
+ {`Item ${id} exact`}
+
+
+ {`Item ${id} search`}
+
+
+ {`Item ${id} hash`}
+
+
+ {({ isActive }) => (isActive ? `Item ${id} on` : `Item ${id} off`)}
+
+
+ ))}
+
+ )
+}
+
+function RootComponent() {
+ return (
+ <>
+
+
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/src/routes/about.tsx b/benchmarks/client-nav/scenarios/links/react/src/routes/about.tsx
new file mode 100644
index 0000000000..0e4afc9297
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/src/routes/about.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { aboutMarker } from '../../../shared'
+
+export const Route = createFileRoute('/about')({
+ component: AboutPage,
+})
+
+function AboutPage() {
+ return (
+
+ {aboutMarker}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/src/routes/index.tsx b/benchmarks/client-nav/scenarios/links/react/src/routes/index.tsx
new file mode 100644
index 0000000000..2b8c83ebab
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/src/routes/index.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { homeMarker } from '../../../shared'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ {homeMarker}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/src/routes/items.$id.tsx b/benchmarks/client-nav/scenarios/links/react/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..23121edb5c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/src/routes/items.$id.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { itemMarker } from '../../../shared'
+
+export const Route = createFileRoute('/items/$id')({
+ component: ItemPage,
+})
+
+function ItemPage() {
+ const params = Route.useParams()
+
+ return (
+
+ {itemMarker(params.id)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/tsconfig.json b/benchmarks/client-nav/scenarios/links/react/tsconfig.json
new file mode 100644
index 0000000000..1d59cbb450
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/links/react/vite.config.ts b/benchmarks/client-nav/scenarios/links/react/vite.config.ts
new file mode 100644
index 0000000000..9e9cd48895
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/react/vite.config.ts
@@ -0,0 +1,40 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'react',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav links (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/links/shared.ts b/benchmarks/client-nav/scenarios/links/shared.ts
new file mode 100644
index 0000000000..6e75ea045c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/shared.ts
@@ -0,0 +1,133 @@
+/**
+ * Shared definition of the `links` scenario: a large persistent grid of
+ * ``s in the root layout so every navigation recomputes link props and
+ * active state across many activeOptions variants, plus `useMatchRoute` /
+ * `MatchRoute` consumers. The three framework apps consume these constants so
+ * the workload is identical modulo the rendering layer.
+ */
+import type { ScenarioStep } from '../harness'
+
+// 40 item ids x 5 link variants = 200 links mounted in the root layout.
+export const LINK_ITEM_COUNT = 40
+export const LINK_VARIANT_COUNT = 5
+
+export const itemIds = Array.from({ length: LINK_ITEM_COUNT }, (_, index) =>
+ String(index + 1),
+)
+
+// Static search object attached to the includeSearch:false variant.
+export const variantSearch = { tab: 'specs', page: 2 }
+
+// Item ids that get a `go-item-` test id on their plain variant link.
+export const stepItemIds = ['1', '7', '13', '25', '40']
+
+// Ids probed by the useMatchRoute checks in the root StatusPanel.
+export const matchProbeIds = [
+ '1',
+ '5',
+ '7',
+ '13',
+ '20',
+ '25',
+ '30',
+ '35',
+ '40',
+ '2',
+]
+
+export function itemMarker(id: string) {
+ return `item-${id}`
+}
+
+export const homeMarker = 'home'
+export const aboutMarker = 'about'
+
+interface StepDef {
+ testId: string
+ marker: string
+ /** Expected number of `.active-link` elements after the step. */
+ activeCount: number
+ /** When one link is active, its href must include this substring. */
+ activeHrefPart?: string
+}
+
+export const stepDefs: ReadonlyArray = [
+ {
+ testId: 'go-item-1',
+ marker: itemMarker('1'),
+ activeCount: 1,
+ activeHrefPart: '/items/1',
+ },
+ {
+ testId: 'go-item-7',
+ marker: itemMarker('7'),
+ activeCount: 1,
+ activeHrefPart: '/items/7',
+ },
+ { testId: 'go-about', marker: aboutMarker, activeCount: 0 },
+ {
+ testId: 'go-item-13',
+ marker: itemMarker('13'),
+ activeCount: 1,
+ activeHrefPart: '/items/13',
+ },
+ {
+ testId: 'go-item-25',
+ marker: itemMarker('25'),
+ activeCount: 1,
+ activeHrefPart: '/items/25',
+ },
+ {
+ testId: 'go-item-40',
+ marker: itemMarker('40'),
+ activeCount: 1,
+ activeHrefPart: '/items/40',
+ },
+ {
+ testId: 'go-item-7',
+ marker: itemMarker('7'),
+ activeCount: 1,
+ activeHrefPart: '/items/7',
+ },
+ { testId: 'go-home', marker: homeMarker, activeCount: 0 },
+]
+
+export const scenarioSteps: ReadonlyArray = stepDefs.map(
+ (step) => step.testId,
+)
+
+export function assertStepResult(stepIndex: number, container: HTMLElement) {
+ const step = stepDefs[stepIndex]!
+
+ const marker = container.querySelector('[data-testid="page-state"]')
+ if (marker?.textContent !== step.marker) {
+ throw new Error(
+ `Expected page marker "${step.marker}" after step ${stepIndex}, received "${marker?.textContent}"`,
+ )
+ }
+
+ const activeLinks = container.querySelectorAll('a.active-link')
+ if (activeLinks.length !== step.activeCount) {
+ throw new Error(
+ `Expected ${step.activeCount} active link(s) after step ${stepIndex}, received ${activeLinks.length}`,
+ )
+ }
+ if (step.activeHrefPart) {
+ const href = activeLinks[0]!.getAttribute('href') ?? ''
+ if (!href.includes(step.activeHrefPart)) {
+ throw new Error(
+ `Expected the active link href to include "${step.activeHrefPart}" after step ${stepIndex}, received "${href}"`,
+ )
+ }
+ }
+}
+
+// One lap through the 8-step sequence per benchmark iteration; each step
+// recomputes all 200 links plus the match probes.
+export const ticksPerIteration = 8
+
+export const benchOptions = {
+ warmupIterations: 50,
+ time: 10_000,
+ throws: true,
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/project.json b/benchmarks/client-nav/scenarios/links/solid/project.json
new file mode 100644
index 0000000000..ef1dab4c50
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-links-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/links/solid/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/setup.ts b/benchmarks/client-nav/scenarios/links/solid/setup.ts
new file mode 100644
index 0000000000..34270fe8a4
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, scenarioSteps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Solid',
+ mount: mountTestApp,
+ steps: scenarioSteps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/speed.bench.ts b/benchmarks/client-nav/scenarios/links/solid/speed.bench.ts
new file mode 100644
index 0000000000..beba86fb6c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-links', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-links navigation loop (solid)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/links/solid/speed.flame.ts b/benchmarks/client-nav/scenarios/links/solid/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/src/main.tsx b/benchmarks/client-nav/scenarios/links/solid/src/main.tsx
new file mode 100644
index 0000000000..817045bd02
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/src/main.tsx
@@ -0,0 +1,32 @@
+import { render } from 'solid-js/web'
+import { RouterProvider, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const unmount = render(() => , container)
+
+ return {
+ router,
+ unmount,
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/links/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..b61431e40d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/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 AboutRouteImport } from './routes/about'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const AboutRoute = AboutRouteImport.update({
+ id: '/about',
+ path: '/about',
+ getParentRoute: () => rootRouteImport,
+} as any)
+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
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/about' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/about' | '/items/$id'
+ id: '__root__' | '/' | '/about' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ AboutRoute: typeof AboutRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/about': {
+ id: '/about'
+ path: '/about'
+ fullPath: '/about'
+ preLoaderRoute: typeof AboutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ 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,
+ AboutRoute: AboutRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/links/solid/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/links/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..a889a26854
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/src/routes/__root.tsx
@@ -0,0 +1,119 @@
+import { For } from 'solid-js'
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useMatchRoute,
+} from '@tanstack/solid-router'
+import {
+ itemIds,
+ matchProbeIds,
+ stepItemIds,
+ variantSearch,
+} from '../../../shared'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function StatusPanel() {
+ const matchRoute = useMatchRoute()
+
+ return (
+
+ )
+}
+
+function LinkGrid() {
+ return (
+
+
+ {(id) => (
+
+
+ {`Item ${id}`}
+
+
+ {`Item ${id} exact`}
+
+
+ {`Item ${id} search`}
+
+
+ {`Item ${id} hash`}
+
+
+ {({ isActive }) =>
+ isActive ? `Item ${id} on` : `Item ${id} off`
+ }
+
+
+ )}
+
+
+ )
+}
+
+function RootComponent() {
+ return (
+ <>
+
+
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/src/routes/about.tsx b/benchmarks/client-nav/scenarios/links/solid/src/routes/about.tsx
new file mode 100644
index 0000000000..a2a71678b7
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/src/routes/about.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { aboutMarker } from '../../../shared'
+
+export const Route = createFileRoute('/about')({
+ component: AboutPage,
+})
+
+function AboutPage() {
+ return (
+
+ {aboutMarker}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/src/routes/index.tsx b/benchmarks/client-nav/scenarios/links/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..72fbfe387a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/src/routes/index.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { homeMarker } from '../../../shared'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ {homeMarker}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/src/routes/items.$id.tsx b/benchmarks/client-nav/scenarios/links/solid/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..64d84f81f4
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/src/routes/items.$id.tsx
@@ -0,0 +1,16 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { itemMarker } from '../../../shared'
+
+export const Route = createFileRoute('/items/$id')({
+ component: ItemPage,
+})
+
+function ItemPage() {
+ const params = Route.useParams()
+
+ return (
+
+ {itemMarker(params().id)}
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/tsconfig.json b/benchmarks/client-nav/scenarios/links/solid/tsconfig.json
new file mode 100644
index 0000000000..98d76ad6b8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/links/solid/vite.config.ts b/benchmarks/client-nav/scenarios/links/solid/vite.config.ts
new file mode 100644
index 0000000000..9a5b3d138b
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/solid/vite.config.ts
@@ -0,0 +1,48 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import solid from 'vite-plugin-solid'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'solid',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ resolve: {
+ conditions: ['solid', 'browser'],
+ },
+ test: {
+ name: '@benchmarks/client-nav links (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/links/vue/project.json b/benchmarks/client-nav/scenarios/links/vue/project.json
new file mode 100644
index 0000000000..fe1e68fad3
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-links-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/links/vue/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/links/vue/setup.ts b/benchmarks/client-nav/scenarios/links/vue/setup.ts
new file mode 100644
index 0000000000..f70a154909
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, scenarioSteps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Vue',
+ mount: mountTestApp,
+ steps: scenarioSteps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/links/vue/speed.bench.ts b/benchmarks/client-nav/scenarios/links/vue/speed.bench.ts
new file mode 100644
index 0000000000..54b99d1273
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-links', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-links navigation loop (vue)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/links/vue/speed.flame.ts b/benchmarks/client-nav/scenarios/links/vue/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/links/vue/src/main.tsx b/benchmarks/client-nav/scenarios/links/vue/src/main.tsx
new file mode 100644
index 0000000000..c96d3f3a2e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/src/main.tsx
@@ -0,0 +1,39 @@
+import * as Vue from 'vue'
+import { RouterProvider, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const component =
+ const app = Vue.createApp({
+ render: () => component,
+ })
+
+ app.mount(container)
+
+ return {
+ router,
+ unmount() {
+ app.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/links/vue/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/links/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..cee64be43c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/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 AboutRouteImport } from './routes/about'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ItemsIdRouteImport } from './routes/items.$id'
+
+const AboutRoute = AboutRouteImport.update({
+ id: '/about',
+ path: '/about',
+ getParentRoute: () => rootRouteImport,
+} as any)
+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
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/items/$id': typeof ItemsIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/about' | '/items/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/about' | '/items/$id'
+ id: '__root__' | '/' | '/about' | '/items/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ AboutRoute: typeof AboutRoute
+ ItemsIdRoute: typeof ItemsIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/about': {
+ id: '/about'
+ path: '/about'
+ fullPath: '/about'
+ preLoaderRoute: typeof AboutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ 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,
+ AboutRoute: AboutRoute,
+ ItemsIdRoute: ItemsIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/links/vue/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/links/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..04f99677fd
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/src/routes/__root.tsx
@@ -0,0 +1,120 @@
+import * as Vue from 'vue'
+import {
+ Link,
+ Outlet,
+ createRootRoute,
+ useMatchRoute,
+} from '@tanstack/vue-router'
+import {
+ itemIds,
+ matchProbeIds,
+ stepItemIds,
+ variantSearch,
+} from '../../../shared'
+
+const StatusPanel = Vue.defineComponent({
+ setup() {
+ const matchRoute = useMatchRoute()
+ const probes = matchProbeIds.map((id) => ({
+ id,
+ match: matchRoute({ to: '/items/$id', params: { id } }),
+ }))
+ // Static probes use the setup-scoped matchRoute instead of :
+ // vue-router's MatchRoute creates an undisposed watcher per render (one
+ // leaked subscription per navigation), so all three apps use
+ // useMatchRoute for cross-framework parity.
+ const atHome = matchRoute({ to: '/' })
+ const atAbout = matchRoute({ to: '/about' })
+ const underRoot = matchRoute({ to: '/', fuzzy: true })
+ const underAbout = matchRoute({ to: '/about', fuzzy: true })
+ const aboutSearch = matchRoute({ to: '/about', includeSearch: true })
+
+ return () => (
+
+ )
+ },
+})
+
+const LinkGrid = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ {itemIds.map((id) => (
+
+
+ {`Item ${id}`}
+
+
+ {`Item ${id} exact`}
+
+
+ {`Item ${id} search`}
+
+
+ {`Item ${id} hash`}
+
+
+ {({ isActive }: { isActive: boolean }) =>
+ isActive ? `Item ${id} on` : `Item ${id} off`
+ }
+
+
+ ))}
+
+ )
+ },
+})
+
+const RootComponent = Vue.defineComponent({
+ setup() {
+ return () => (
+ <>
+
+
+
+
+ >
+ )
+ },
+})
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
diff --git a/benchmarks/client-nav/scenarios/links/vue/src/routes/about.tsx b/benchmarks/client-nav/scenarios/links/vue/src/routes/about.tsx
new file mode 100644
index 0000000000..5cc1473bff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/src/routes/about.tsx
@@ -0,0 +1,17 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { aboutMarker } from '../../../shared'
+
+const AboutPage = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ {aboutMarker}
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/about')({
+ component: AboutPage,
+})
diff --git a/benchmarks/client-nav/scenarios/links/vue/src/routes/index.tsx b/benchmarks/client-nav/scenarios/links/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..f04a9f5796
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/src/routes/index.tsx
@@ -0,0 +1,17 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { homeMarker } from '../../../shared'
+
+const HomePage = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ {homeMarker}
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
diff --git a/benchmarks/client-nav/scenarios/links/vue/src/routes/items.$id.tsx b/benchmarks/client-nav/scenarios/links/vue/src/routes/items.$id.tsx
new file mode 100644
index 0000000000..0dc5efa3d3
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/src/routes/items.$id.tsx
@@ -0,0 +1,19 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { itemMarker } from '../../../shared'
+
+const ItemPage = Vue.defineComponent({
+ setup() {
+ const params = Route.useParams()
+
+ return () => (
+
+ {itemMarker(params.value.id)}
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/items/$id')({
+ component: ItemPage,
+})
diff --git a/benchmarks/client-nav/scenarios/links/vue/tsconfig.json b/benchmarks/client-nav/scenarios/links/vue/tsconfig.json
new file mode 100644
index 0000000000..7786fc3da9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/links/vue/vite.config.ts b/benchmarks/client-nav/scenarios/links/vue/vite.config.ts
new file mode 100644
index 0000000000..d1cf9f561d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/links/vue/vite.config.ts
@@ -0,0 +1,42 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'vue',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav links (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/react/project.json b/benchmarks/client-nav/scenarios/loaders/react/project.json
new file mode 100644
index 0000000000..d3dbe585d4
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-loaders-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/loaders/react/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/setup.ts b/benchmarks/client-nav/scenarios/loaders/react/setup.ts
new file mode 100644
index 0000000000..130583b190
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'React',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/speed.bench.ts b/benchmarks/client-nav/scenarios/loaders/react/speed.bench.ts
new file mode 100644
index 0000000000..7c44f1a0f7
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-loaders', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-loaders navigation loop (react)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/react/speed.flame.ts b/benchmarks/client-nav/scenarios/loaders/react/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/src/main.tsx b/benchmarks/client-nav/scenarios/loaders/react/src/main.tsx
new file mode 100644
index 0000000000..ef0b99555c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/src/main.tsx
@@ -0,0 +1,35 @@
+import { RouterProvider, createRouter } from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const reactRoot = createRoot(container)
+ reactRoot.render()
+
+ return {
+ router,
+ unmount() {
+ reactRoot.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/loaders/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..50f0a8863b
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/src/routeTree.gen.ts
@@ -0,0 +1,113 @@
+/* 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 DepsRouteImport } from './routes/deps'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as FreshIdRouteImport } from './routes/fresh.$id'
+import { Route as CachedIdRouteImport } from './routes/cached.$id'
+
+const DepsRoute = DepsRouteImport.update({
+ id: '/deps',
+ path: '/deps',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FreshIdRoute = FreshIdRouteImport.update({
+ id: '/fresh/$id',
+ path: '/fresh/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const CachedIdRoute = CachedIdRouteImport.update({
+ id: '/cached/$id',
+ path: '/cached/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ id: '__root__' | '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ DepsRoute: typeof DepsRoute
+ CachedIdRoute: typeof CachedIdRoute
+ FreshIdRoute: typeof FreshIdRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/deps': {
+ id: '/deps'
+ path: '/deps'
+ fullPath: '/deps'
+ preLoaderRoute: typeof DepsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/fresh/$id': {
+ id: '/fresh/$id'
+ path: '/fresh/$id'
+ fullPath: '/fresh/$id'
+ preLoaderRoute: typeof FreshIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/cached/$id': {
+ id: '/cached/$id'
+ path: '/cached/$id'
+ fullPath: '/cached/$id'
+ preLoaderRoute: typeof CachedIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ DepsRoute: DepsRoute,
+ CachedIdRoute: CachedIdRoute,
+ FreshIdRoute: FreshIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/loaders/react/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/loaders/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..6309763cf3
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/src/routes/__root.tsx
@@ -0,0 +1,70 @@
+import { Link, Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/src/routes/cached.$id.tsx b/benchmarks/client-nav/scenarios/loaders/react/src/routes/cached.$id.tsx
new file mode 100644
index 0000000000..9b954185cc
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/src/routes/cached.$id.tsx
@@ -0,0 +1,55 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { computeChecksum, itemsChecksum, makeItems } from '../../../shared'
+
+export const Route = createFileRoute('/cached/$id')({
+ loader: ({ params }) => {
+ const items = makeItems(`cached-${params.id}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 1e9,
+ gcTime: 1e9,
+ component: CachedPage,
+})
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+function SumSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ void computeChecksum(value)
+ return null
+}
+
+function FirstItemSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.items[0]?.value ?? 0),
+ })
+
+ void computeChecksum(value)
+ return null
+}
+
+function CachedPage() {
+ const params = Route.useParams()
+ const loaderData = Route.useLoaderData()
+
+ return (
+
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ Cached
+ {`c-${params.id}-${loaderData.checksum}`}
+
+ {loaderData.items.slice(0, 5).map((item) => (
+ - {item.name}
+ ))}
+
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/src/routes/deps.tsx b/benchmarks/client-nav/scenarios/loaders/react/src/routes/deps.tsx
new file mode 100644
index 0000000000..92afe04a21
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/src/routes/deps.tsx
@@ -0,0 +1,65 @@
+import { createFileRoute } from '@tanstack/react-router'
+import type { SearchSchemaInput } from '@tanstack/react-router'
+import {
+ computeChecksum,
+ itemsChecksum,
+ makeItems,
+ normalizePage,
+} from '../../../shared'
+
+export const Route = createFileRoute('/deps')({
+ validateSearch: (search: Record & SearchSchemaInput) => ({
+ page: normalizePage(search.page),
+ }),
+ loaderDeps: ({ search }) => ({ page: search.page }),
+ loader: ({ deps }) => {
+ const items = makeItems(`deps-${deps.page}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 1e9,
+ gcTime: 1e9,
+ component: DepsPage,
+})
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+function SumSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ void computeChecksum(value)
+ return null
+}
+
+function DepsSubscriber() {
+ const value = Route.useLoaderDeps({
+ select: (deps) => computeChecksum(deps.page * 17),
+ })
+
+ void computeChecksum(value)
+ return null
+}
+
+function DepsPage() {
+ const search = Route.useSearch()
+ const loaderData = Route.useLoaderData()
+
+ return (
+
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ Deps
+ {`d-${search.page}-${loaderData.checksum}`}
+
+ {loaderData.items.slice(0, 5).map((item) => (
+ - {item.name}
+ ))}
+
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/src/routes/fresh.$id.tsx b/benchmarks/client-nav/scenarios/loaders/react/src/routes/fresh.$id.tsx
new file mode 100644
index 0000000000..fb7cbf2f00
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/src/routes/fresh.$id.tsx
@@ -0,0 +1,55 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { computeChecksum, itemsChecksum, makeItems } from '../../../shared'
+
+export const Route = createFileRoute('/fresh/$id')({
+ loader: ({ params }) => {
+ const items = makeItems(`fresh-${params.id}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 0,
+ gcTime: 0,
+ component: FreshPage,
+})
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+function SumSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ void computeChecksum(value)
+ return null
+}
+
+function FirstItemSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.items[0]?.value ?? 0),
+ })
+
+ void computeChecksum(value)
+ return null
+}
+
+function FreshPage() {
+ const params = Route.useParams()
+ const loaderData = Route.useLoaderData()
+
+ return (
+
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ Fresh
+ {`f-${params.id}-${loaderData.checksum}`}
+
+ {loaderData.items.slice(0, 5).map((item) => (
+ - {item.name}
+ ))}
+
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/src/routes/index.tsx b/benchmarks/client-nav/scenarios/loaders/react/src/routes/index.tsx
new file mode 100644
index 0000000000..9203c00841
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/src/routes/index.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ Loaders Bench
+ home
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/tsconfig.json b/benchmarks/client-nav/scenarios/loaders/react/tsconfig.json
new file mode 100644
index 0000000000..1d59cbb450
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/react/vite.config.ts b/benchmarks/client-nav/scenarios/loaders/react/vite.config.ts
new file mode 100644
index 0000000000..6fa28bcd35
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/react/vite.config.ts
@@ -0,0 +1,40 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'react',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ react(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav loaders (react)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/shared.ts b/benchmarks/client-nav/scenarios/loaders/shared.ts
new file mode 100644
index 0000000000..f744246160
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/shared.ts
@@ -0,0 +1,134 @@
+/**
+ * Shared definition of the `loaders` scenario: deterministic loader data
+ * derivation, the click sequence, and per-step sanity assertions. The three
+ * framework apps consume these builders so the workload is identical modulo
+ * the rendering layer.
+ *
+ * Isolates: client loader dispatch — always-stale re-runs (staleTime 0),
+ * pure cache hits (staleTime 1e9), loaderDeps-keyed caching,
+ * router.invalidate(), and useLoaderData selector subscriptions.
+ */
+
+import type { ScenarioStep } from '../harness'
+
+export function computeChecksum(seed: number) {
+ let value = Math.trunc(seed) | 0
+
+ for (let index = 0; index < 40; index++) {
+ value = (value * 1664525 + 1013904223 + index) >>> 0
+ }
+
+ return value
+}
+
+export interface LoaderItem {
+ id: string
+ name: string
+ value: number
+ flag: boolean
+}
+
+export function makeItems(seedText: string): Array {
+ let seed = 0
+ for (let index = 0; index < seedText.length; index++) {
+ seed = (seed * 31 + seedText.charCodeAt(index)) >>> 0
+ }
+
+ const items: Array = []
+ let value = seed
+
+ for (let index = 0; index < 50; index++) {
+ value = (value * 1664525 + 1013904223) >>> 0
+ items.push({
+ id: `${seedText}-${index}`,
+ name: `Item ${index} of ${seedText}`,
+ value: value % 100000,
+ flag: value % 3 === 0,
+ })
+ }
+
+ return items
+}
+
+export function itemsChecksum(items: Array) {
+ let sum = 0
+ for (const item of items) {
+ sum = (sum + item.value + (item.flag ? 1 : 0)) >>> 0
+ }
+ return sum
+}
+
+export function normalizePage(value: unknown) {
+ const page = Number(value)
+ return Number.isFinite(page) && page > 0 ? Math.trunc(page) : 1
+}
+
+export function freshMarkerText(id: string) {
+ return `f-${id}-${itemsChecksum(makeItems(`fresh-${id}`))}`
+}
+
+export function cachedMarkerText(id: string) {
+ return `c-${id}-${itemsChecksum(makeItems(`cached-${id}`))}`
+}
+
+export function depsMarkerText(page: number) {
+ return `d-${page}-${itemsChecksum(makeItems(`deps-${page}`))}`
+}
+
+export const steps: ReadonlyArray = [
+ 'go-fresh-1',
+ 'go-fresh-2',
+ 'go-cached-1',
+ 'go-cached-2',
+ 'go-deps-1',
+ 'go-deps-2',
+ // Invalidates the currently matched routes (the deps route), re-running its
+ // loader with unchanged deps. Note: this also marks the cached fresh/cached
+ // matches invalid, so each of those loaders re-runs once per lap on its next
+ // visit — deterministic, identical work every lap.
+ { type: 'invalidate' },
+ 'go-home',
+]
+
+interface ExpectedState {
+ testId: string
+ text?: string
+}
+
+const expectedStates: ReadonlyArray = [
+ { testId: 'fresh-state', text: freshMarkerText('1') },
+ { testId: 'fresh-state', text: freshMarkerText('2') },
+ { testId: 'cached-state', text: cachedMarkerText('1') },
+ { testId: 'cached-state', text: cachedMarkerText('2') },
+ { testId: 'deps-state', text: depsMarkerText(1) },
+ { testId: 'deps-state', text: depsMarkerText(2) },
+ // After invalidate the deps loader re-ran with the same deps.
+ { testId: 'deps-state', text: depsMarkerText(2) },
+ { testId: 'home-state' },
+]
+
+export function assertStepResult(stepIndex: number, container: HTMLElement) {
+ const expected = expectedStates[stepIndex]!
+ const element = container.querySelector(`[data-testid="${expected.testId}"]`)
+
+ if (!element) {
+ throw new Error(
+ `Expected marker "${expected.testId}" to exist after step ${stepIndex}`,
+ )
+ }
+
+ if (expected.text !== undefined && element.textContent !== expected.text) {
+ throw new Error(
+ `Expected marker "${expected.testId}" to read "${expected.text}" after step ${stepIndex}, received "${element.textContent}"`,
+ )
+ }
+}
+
+// Two laps through the 8-step sequence per benchmark iteration.
+export const ticksPerIteration = 16
+
+export const benchOptions = {
+ warmupIterations: 100,
+ time: 10_000,
+ throws: true,
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/project.json b/benchmarks/client-nav/scenarios/loaders/solid/project.json
new file mode 100644
index 0000000000..071e6bc385
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-loaders-solid",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/solid-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/loaders/solid/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/setup.ts b/benchmarks/client-nav/scenarios/loaders/solid/setup.ts
new file mode 100644
index 0000000000..6a46971603
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Solid',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/speed.bench.ts b/benchmarks/client-nav/scenarios/loaders/solid/speed.bench.ts
new file mode 100644
index 0000000000..4c97823d11
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-loaders', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-loaders navigation loop (solid)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/speed.flame.ts b/benchmarks/client-nav/scenarios/loaders/solid/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/src/main.tsx b/benchmarks/client-nav/scenarios/loaders/solid/src/main.tsx
new file mode 100644
index 0000000000..817045bd02
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/src/main.tsx
@@ -0,0 +1,32 @@
+import { render } from 'solid-js/web'
+import { RouterProvider, createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const unmount = render(() => , container)
+
+ return {
+ router,
+ unmount,
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/loaders/solid/src/routeTree.gen.ts
new file mode 100644
index 0000000000..bdec400237
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/src/routeTree.gen.ts
@@ -0,0 +1,113 @@
+/* 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 DepsRouteImport } from './routes/deps'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as FreshIdRouteImport } from './routes/fresh.$id'
+import { Route as CachedIdRouteImport } from './routes/cached.$id'
+
+const DepsRoute = DepsRouteImport.update({
+ id: '/deps',
+ path: '/deps',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FreshIdRoute = FreshIdRouteImport.update({
+ id: '/fresh/$id',
+ path: '/fresh/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const CachedIdRoute = CachedIdRouteImport.update({
+ id: '/cached/$id',
+ path: '/cached/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ id: '__root__' | '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ DepsRoute: typeof DepsRoute
+ CachedIdRoute: typeof CachedIdRoute
+ FreshIdRoute: typeof FreshIdRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/deps': {
+ id: '/deps'
+ path: '/deps'
+ fullPath: '/deps'
+ preLoaderRoute: typeof DepsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/fresh/$id': {
+ id: '/fresh/$id'
+ path: '/fresh/$id'
+ fullPath: '/fresh/$id'
+ preLoaderRoute: typeof FreshIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/cached/$id': {
+ id: '/cached/$id'
+ path: '/cached/$id'
+ fullPath: '/cached/$id'
+ preLoaderRoute: typeof CachedIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ DepsRoute: DepsRoute,
+ CachedIdRoute: CachedIdRoute,
+ FreshIdRoute: FreshIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/__root.tsx
new file mode 100644
index 0000000000..30350f7907
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/__root.tsx
@@ -0,0 +1,66 @@
+import { Link, Outlet, createRootRoute } from '@tanstack/solid-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/src/routes/cached.$id.tsx b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/cached.$id.tsx
new file mode 100644
index 0000000000..aab6be74dc
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/cached.$id.tsx
@@ -0,0 +1,62 @@
+import { For, createRenderEffect } from 'solid-js'
+import { createFileRoute } from '@tanstack/solid-router'
+import { computeChecksum, itemsChecksum, makeItems } from '../../../shared'
+
+export const Route = createFileRoute('/cached/$id')({
+ loader: ({ params }) => {
+ const items = makeItems(`cached-${params.id}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 1e9,
+ gcTime: 1e9,
+ component: CachedPage,
+})
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+function PerfValue(props: { value: () => number }) {
+ createRenderEffect(() => {
+ void props.value()
+ })
+
+ return null
+}
+
+function SumSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ return computeChecksum(value())} />
+}
+
+function FirstItemSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.items[0]?.value ?? 0),
+ })
+
+ return computeChecksum(value())} />
+}
+
+function CachedPage() {
+ const params = Route.useParams()
+ const loaderData = Route.useLoaderData()
+
+ return (
+
+ {subscriberIndexes.map(() => (
+
+ ))}
+ {subscriberIndexes.map(() => (
+
+ ))}
+ Cached
+ {`c-${params().id}-${loaderData().checksum}`}
+
+
+ {(item) => - {item.name}
}
+
+
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/src/routes/deps.tsx b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/deps.tsx
new file mode 100644
index 0000000000..2f23f2d3da
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/deps.tsx
@@ -0,0 +1,72 @@
+import { For, createRenderEffect } from 'solid-js'
+import { createFileRoute } from '@tanstack/solid-router'
+import type { SearchSchemaInput } from '@tanstack/solid-router'
+import {
+ computeChecksum,
+ itemsChecksum,
+ makeItems,
+ normalizePage,
+} from '../../../shared'
+
+export const Route = createFileRoute('/deps')({
+ validateSearch: (search: Record & SearchSchemaInput) => ({
+ page: normalizePage(search.page),
+ }),
+ loaderDeps: ({ search }) => ({ page: search.page }),
+ loader: ({ deps }) => {
+ const items = makeItems(`deps-${deps.page}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 1e9,
+ gcTime: 1e9,
+ component: DepsPage,
+})
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+function PerfValue(props: { value: () => number }) {
+ createRenderEffect(() => {
+ void props.value()
+ })
+
+ return null
+}
+
+function SumSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ return computeChecksum(value())} />
+}
+
+function DepsSubscriber() {
+ const value = Route.useLoaderDeps({
+ select: (deps) => computeChecksum(deps.page * 17),
+ })
+
+ return computeChecksum(value())} />
+}
+
+function DepsPage() {
+ const search = Route.useSearch()
+ const loaderData = Route.useLoaderData()
+
+ return (
+
+ {subscriberIndexes.map(() => (
+
+ ))}
+ {subscriberIndexes.map(() => (
+
+ ))}
+ Deps
+ {`d-${search().page}-${loaderData().checksum}`}
+
+
+ {(item) => - {item.name}
}
+
+
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/src/routes/fresh.$id.tsx b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/fresh.$id.tsx
new file mode 100644
index 0000000000..acd6a9aa27
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/fresh.$id.tsx
@@ -0,0 +1,62 @@
+import { For, createRenderEffect } from 'solid-js'
+import { createFileRoute } from '@tanstack/solid-router'
+import { computeChecksum, itemsChecksum, makeItems } from '../../../shared'
+
+export const Route = createFileRoute('/fresh/$id')({
+ loader: ({ params }) => {
+ const items = makeItems(`fresh-${params.id}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 0,
+ gcTime: 0,
+ component: FreshPage,
+})
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+function PerfValue(props: { value: () => number }) {
+ createRenderEffect(() => {
+ void props.value()
+ })
+
+ return null
+}
+
+function SumSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ return computeChecksum(value())} />
+}
+
+function FirstItemSubscriber() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.items[0]?.value ?? 0),
+ })
+
+ return computeChecksum(value())} />
+}
+
+function FreshPage() {
+ const params = Route.useParams()
+ const loaderData = Route.useLoaderData()
+
+ return (
+
+ {subscriberIndexes.map(() => (
+
+ ))}
+ {subscriberIndexes.map(() => (
+
+ ))}
+ Fresh
+ {`f-${params().id}-${loaderData().checksum}`}
+
+
+ {(item) => - {item.name}
}
+
+
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/src/routes/index.tsx b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/index.tsx
new file mode 100644
index 0000000000..7287d47d0a
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/src/routes/index.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
+
+function HomePage() {
+ return (
+
+ Loaders Bench
+ home
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/tsconfig.json b/benchmarks/client-nav/scenarios/loaders/solid/tsconfig.json
new file mode 100644
index 0000000000..98d76ad6b8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/solid/vite.config.ts b/benchmarks/client-nav/scenarios/loaders/solid/vite.config.ts
new file mode 100644
index 0000000000..ea913ac578
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/solid/vite.config.ts
@@ -0,0 +1,48 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import solid from 'vite-plugin-solid'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'solid',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ solid({ hot: false, dev: false }),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ resolve: {
+ conditions: ['solid', 'browser'],
+ },
+ test: {
+ name: '@benchmarks/client-nav loaders (solid)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ server: {
+ deps: {
+ inline: [/@solidjs/, /@tanstack\/solid-store/],
+ },
+ },
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/project.json b/benchmarks/client-nav/scenarios/loaders/vue/project.json
new file mode 100644
index 0000000000..69bd95837d
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-loaders-vue",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/vue-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/loaders/vue/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/setup.ts b/benchmarks/client-nav/scenarios/loaders/vue/setup.ts
new file mode 100644
index 0000000000..97b74b3892
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/setup.ts
@@ -0,0 +1,17 @@
+import { createScenarioSetup } from '../../harness'
+import { assertStepResult, steps } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createScenarioSetup({
+ frameworkLabel: 'Vue',
+ mount: mountTestApp,
+ steps,
+ assertAfterStep: assertStepResult,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/speed.bench.ts b/benchmarks/client-nav/scenarios/loaders/vue/speed.bench.ts
new file mode 100644
index 0000000000..eeb9cefaeb
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-loaders', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-loaders navigation loop (vue)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/speed.flame.ts b/benchmarks/client-nav/scenarios/loaders/vue/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/src/main.tsx b/benchmarks/client-nav/scenarios/loaders/vue/src/main.tsx
new file mode 100644
index 0000000000..c96d3f3a2e
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/src/main.tsx
@@ -0,0 +1,39 @@
+import * as Vue from 'vue'
+import { RouterProvider, createRouter } from '@tanstack/vue-router'
+import { routeTree } from './routeTree.gen'
+
+export function createTestRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ // Key the scroll-restoration cache by pathname instead of the default
+ // random per-entry location key: with push navigations the default mints
+ // a fresh key per navigation and the module-level cache grows one entry
+ // per push for the whole run, which is non-stationary.
+ getScrollRestorationKey: (location) => location.pathname,
+ })
+}
+
+declare module '@tanstack/vue-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const router = createTestRouter()
+
+ const component =
+ const app = Vue.createApp({
+ render: () => component,
+ })
+
+ app.mount(container)
+
+ return {
+ router,
+ unmount() {
+ app.unmount()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/loaders/vue/src/routeTree.gen.ts
new file mode 100644
index 0000000000..25a82df477
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/src/routeTree.gen.ts
@@ -0,0 +1,113 @@
+/* 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 DepsRouteImport } from './routes/deps'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as FreshIdRouteImport } from './routes/fresh.$id'
+import { Route as CachedIdRouteImport } from './routes/cached.$id'
+
+const DepsRoute = DepsRouteImport.update({
+ id: '/deps',
+ path: '/deps',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const FreshIdRoute = FreshIdRouteImport.update({
+ id: '/fresh/$id',
+ path: '/fresh/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const CachedIdRoute = CachedIdRouteImport.update({
+ id: '/cached/$id',
+ path: '/cached/$id',
+ getParentRoute: () => rootRouteImport,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/deps': typeof DepsRoute
+ '/cached/$id': typeof CachedIdRoute
+ '/fresh/$id': typeof FreshIdRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ id: '__root__' | '/' | '/deps' | '/cached/$id' | '/fresh/$id'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ DepsRoute: typeof DepsRoute
+ CachedIdRoute: typeof CachedIdRoute
+ FreshIdRoute: typeof FreshIdRoute
+}
+
+declare module '@tanstack/vue-router' {
+ interface FileRoutesByPath {
+ '/deps': {
+ id: '/deps'
+ path: '/deps'
+ fullPath: '/deps'
+ preLoaderRoute: typeof DepsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/fresh/$id': {
+ id: '/fresh/$id'
+ path: '/fresh/$id'
+ fullPath: '/fresh/$id'
+ preLoaderRoute: typeof FreshIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/cached/$id': {
+ id: '/cached/$id'
+ path: '/cached/$id'
+ fullPath: '/cached/$id'
+ preLoaderRoute: typeof CachedIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ }
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ DepsRoute: DepsRoute,
+ CachedIdRoute: CachedIdRoute,
+ FreshIdRoute: FreshIdRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/__root.tsx
new file mode 100644
index 0000000000..b9102a92c4
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/__root.tsx
@@ -0,0 +1,69 @@
+import * as Vue from 'vue'
+import { Link, Outlet, createRootRoute } from '@tanstack/vue-router'
+
+const RootComponent = Vue.defineComponent({
+ setup() {
+ return () => (
+ <>
+
+
+ >
+ )
+ },
+})
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/src/routes/cached.$id.tsx b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/cached.$id.tsx
new file mode 100644
index 0000000000..a426e35765
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/cached.$id.tsx
@@ -0,0 +1,68 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { computeChecksum, itemsChecksum, makeItems } from '../../../shared'
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+const SumSubscriber = Vue.defineComponent({
+ setup() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ return () => {
+ void computeChecksum(value.value)
+ return null
+ }
+ },
+})
+
+const FirstItemSubscriber = Vue.defineComponent({
+ setup() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.items[0]?.value ?? 0),
+ })
+
+ return () => {
+ void computeChecksum(value.value)
+ return null
+ }
+ },
+})
+
+const CachedPage = Vue.defineComponent({
+ setup() {
+ const params = Route.useParams()
+ const loaderData = Route.useLoaderData()
+
+ return () => (
+
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ Cached
+
+ {`c-${params.value.id}-${loaderData.value.checksum}`}
+
+
+ {loaderData.value.items.slice(0, 5).map((item) => (
+ - {item.name}
+ ))}
+
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/cached/$id')({
+ loader: ({ params }) => {
+ const items = makeItems(`cached-${params.id}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 1e9,
+ gcTime: 1e9,
+ component: CachedPage,
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/src/routes/deps.tsx b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/deps.tsx
new file mode 100644
index 0000000000..f987d15d34
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/deps.tsx
@@ -0,0 +1,78 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import type { SearchSchemaInput } from '@tanstack/vue-router'
+import {
+ computeChecksum,
+ itemsChecksum,
+ makeItems,
+ normalizePage,
+} from '../../../shared'
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+const SumSubscriber = Vue.defineComponent({
+ setup() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ return () => {
+ void computeChecksum(value.value)
+ return null
+ }
+ },
+})
+
+const DepsSubscriber = Vue.defineComponent({
+ setup() {
+ const value = Route.useLoaderDeps({
+ select: (deps) => computeChecksum(deps.page * 17),
+ })
+
+ return () => {
+ void computeChecksum(value.value)
+ return null
+ }
+ },
+})
+
+const DepsPage = Vue.defineComponent({
+ setup() {
+ const search = Route.useSearch()
+ const loaderData = Route.useLoaderData()
+
+ return () => (
+
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ Deps
+
+ {`d-${search.value.page}-${loaderData.value.checksum}`}
+
+
+ {loaderData.value.items.slice(0, 5).map((item) => (
+ - {item.name}
+ ))}
+
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/deps')({
+ validateSearch: (search: Record & SearchSchemaInput) => ({
+ page: normalizePage(search.page),
+ }),
+ loaderDeps: ({ search }) => ({ page: search.page }),
+ loader: ({ deps }) => {
+ const items = makeItems(`deps-${deps.page}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 1e9,
+ gcTime: 1e9,
+ component: DepsPage,
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/src/routes/fresh.$id.tsx b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/fresh.$id.tsx
new file mode 100644
index 0000000000..64eec85cfa
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/fresh.$id.tsx
@@ -0,0 +1,68 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+import { computeChecksum, itemsChecksum, makeItems } from '../../../shared'
+
+const subscriberIndexes = Array.from({ length: 2 }, (_, index) => index)
+
+const SumSubscriber = Vue.defineComponent({
+ setup() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.checksum),
+ })
+
+ return () => {
+ void computeChecksum(value.value)
+ return null
+ }
+ },
+})
+
+const FirstItemSubscriber = Vue.defineComponent({
+ setup() {
+ const value = Route.useLoaderData({
+ select: (data) => computeChecksum(data.items[0]?.value ?? 0),
+ })
+
+ return () => {
+ void computeChecksum(value.value)
+ return null
+ }
+ },
+})
+
+const FreshPage = Vue.defineComponent({
+ setup() {
+ const params = Route.useParams()
+ const loaderData = Route.useLoaderData()
+
+ return () => (
+
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ {subscriberIndexes.map((index) => (
+
+ ))}
+ Fresh
+
+ {`f-${params.value.id}-${loaderData.value.checksum}`}
+
+
+ {loaderData.value.items.slice(0, 5).map((item) => (
+ - {item.name}
+ ))}
+
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/fresh/$id')({
+ loader: ({ params }) => {
+ const items = makeItems(`fresh-${params.id}`)
+ return { items, checksum: itemsChecksum(items) }
+ },
+ staleTime: 0,
+ gcTime: 0,
+ component: FreshPage,
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/src/routes/index.tsx b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/index.tsx
new file mode 100644
index 0000000000..c9724c90a4
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/src/routes/index.tsx
@@ -0,0 +1,17 @@
+import * as Vue from 'vue'
+import { createFileRoute } from '@tanstack/vue-router'
+
+const HomePage = Vue.defineComponent({
+ setup() {
+ return () => (
+
+ Loaders Bench
+ home
+
+ )
+ },
+})
+
+export const Route = createFileRoute('/')({
+ component: HomePage,
+})
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/tsconfig.json b/benchmarks/client-nav/scenarios/loaders/vue/tsconfig.json
new file mode 100644
index 0000000000..7786fc3da9
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "allowImportingTsExtensions": true,
+ "types": ["node", "vite/client", "vitest/globals"]
+ },
+ "include": [
+ "speed.bench.ts",
+ "speed.flame.ts",
+ "setup.ts",
+ "vite.config.ts",
+ "../shared.ts",
+ "../../harness.ts",
+ "../../../setup-helpers.ts",
+ "../../../jsdom.ts",
+ "../../../vitest.setup.ts",
+ "./src/**/*"
+ ]
+}
diff --git a/benchmarks/client-nav/scenarios/loaders/vue/vite.config.ts b/benchmarks/client-nav/scenarios/loaders/vue/vite.config.ts
new file mode 100644
index 0000000000..350fadf387
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/loaders/vue/vite.config.ts
@@ -0,0 +1,42 @@
+import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { tanstackRouter } from '@tanstack/router-plugin/vite'
+
+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(),
+ tanstackRouter({
+ target: 'vue',
+ routesDirectory: `${rootDir}src/routes`,
+ generatedRouteTree: `${rootDir}src/routeTree.gen.ts`,
+ }),
+ vue(),
+ vueJsx(),
+ ],
+ build: {
+ outDir: './dist',
+ emptyOutDir: true,
+ minify: false,
+ lib: {
+ entry: './src/main.tsx',
+ formats: ['es'],
+ fileName: 'app',
+ },
+ },
+ test: {
+ name: '@benchmarks/client-nav loaders (vue)',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['../../../vitest.setup.ts'],
+ },
+})
diff --git a/benchmarks/client-nav/scenarios/mount/react/project.json b/benchmarks/client-nav/scenarios/mount/react/project.json
new file mode 100644
index 0000000000..61839943b3
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/project.json
@@ -0,0 +1,40 @@
+{
+ "name": "@benchmarks/client-nav-mount-react",
+ "projectType": "application",
+ "targets": {
+ "build:client": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "NODE_ENV=production vite build --config {projectRoot}/vite.config.ts"
+ }
+ },
+ "test:types:client": {
+ "executor": "nx:run-commands",
+ "dependsOn": [
+ {
+ "projects": ["@tanstack/react-router", "@tanstack/router-plugin"],
+ "target": "build"
+ }
+ ],
+ "options": {
+ "command": "tsc -p {projectRoot}/tsconfig.json --noEmit"
+ }
+ },
+ "test:flame": {
+ "executor": "nx:run-commands",
+ "cache": false,
+ "dependsOn": ["build:client"],
+ "options": {
+ "command": "NODE_ENV=production flame run --md-format=detailed --delay=none --node-options=\"--stack-size=65500\" ./scenarios/mount/react/speed.flame.ts",
+ "cwd": "benchmarks/client-nav"
+ }
+ }
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/setup.ts b/benchmarks/client-nav/scenarios/mount/react/setup.ts
new file mode 100644
index 0000000000..ed0426c696
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/setup.ts
@@ -0,0 +1,17 @@
+import { createMountLoopSetup } from '../../harness'
+import { assertReady, readyTestId } from '../shared'
+import type * as App from './src/main'
+
+const appModulePath = './dist/app.js'
+const { mountTestApp } = (await import(
+ /* @vite-ignore */ appModulePath
+)) as typeof App
+
+export function setup() {
+ return createMountLoopSetup({
+ frameworkLabel: 'React',
+ mount: mountTestApp,
+ readyTestId,
+ assertReady,
+ })
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/speed.bench.ts b/benchmarks/client-nav/scenarios/mount/react/speed.bench.ts
new file mode 100644
index 0000000000..8f7967c42c
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/speed.bench.ts
@@ -0,0 +1,34 @@
+import { afterAll, beforeAll, bench, describe } from 'vitest'
+import { benchOptions, ticksPerIteration } from '../shared'
+import { setup } from './setup'
+
+describe('client-mount', () => {
+ const test = setup()
+
+ /**
+ * Running `vitest bench` ignores "suite hooks" like `beforeAll` and `afterAll`,
+ * so we use tinybench's `setup` and `teardown` options to run our setup and teardown logic.
+ *
+ * But CodSpeed calls the benchmarked function directly, bypassing `setup` and `teardown`,
+ * but it does support `beforeAll` and `afterAll`.
+ *
+ * So it looks like we're setting up in duplicate, but in reality, it's only running once per environment, as intended.
+ */
+
+ beforeAll(test.before)
+ afterAll(test.after)
+
+ bench(
+ 'client-mount loop (react)',
+ async () => {
+ for (let i = 0; i < ticksPerIteration; i++) {
+ await test.tick()
+ }
+ },
+ {
+ ...benchOptions,
+ setup: test.before,
+ teardown: test.after,
+ },
+ )
+})
diff --git a/benchmarks/client-nav/scenarios/mount/react/speed.flame.ts b/benchmarks/client-nav/scenarios/mount/react/speed.flame.ts
new file mode 100644
index 0000000000..f4a0b4e6ff
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/speed.flame.ts
@@ -0,0 +1,18 @@
+import { window } from '../../../jsdom.ts'
+import { setup } from './setup.ts'
+
+const DURATION_MS = 10_000
+
+const test = setup()
+
+try {
+ await test.before()
+
+ const startedAt = performance.now()
+ while (performance.now() - startedAt < DURATION_MS) {
+ await test.tick()
+ }
+} finally {
+ test.after()
+ window.close()
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/src/main.tsx b/benchmarks/client-nav/scenarios/mount/react/src/main.tsx
new file mode 100644
index 0000000000..78fe2082aa
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/src/main.tsx
@@ -0,0 +1,47 @@
+import {
+ RouterProvider,
+ createBrowserHistory,
+ createRouter,
+} from '@tanstack/react-router'
+import { createRoot } from 'react-dom/client'
+import { routeTree } from './routeTree.gen'
+import type { RouterHistory } from '@tanstack/react-router'
+
+/**
+ * Unlike the other scenarios, this app is mounted and unmounted in a loop, so
+ * it must not register page-lifetime globals that have no dispose path:
+ * - the history is created explicitly and `destroy()`ed on unmount — the
+ * default per-router `createBrowserHistory()` monkey-patches
+ * `window.history.pushState`/`replaceState`, chaining one wrapper per
+ * router and degrading every later mount;
+ * - `scrollRestoration` stays off — enabling it registers per-router
+ * document/window listeners that retain every router ever created.
+ */
+export function createTestRouter(history: RouterHistory) {
+ return createRouter({
+ routeTree,
+ history,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
+
+export function mountTestApp(container: HTMLElement) {
+ const history = createBrowserHistory()
+ const router = createTestRouter(history)
+
+ const reactRoot = createRoot(container)
+ reactRoot.render()
+
+ return {
+ router,
+ unmount() {
+ reactRoot.unmount()
+ history.destroy()
+ },
+ }
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/mount/react/src/routeTree.gen.ts
new file mode 100644
index 0000000000..a796abe0ec
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/src/routeTree.gen.ts
@@ -0,0 +1,264 @@
+/* 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 ShopRouteImport } from './routes/shop'
+import { Route as SettingsRouteImport } from './routes/settings'
+import { Route as SearchRouteImport } from './routes/search'
+import { Route as BlogRouteImport } from './routes/blog'
+import { Route as AboutRouteImport } from './routes/about'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as ShopIndexRouteImport } from './routes/shop.index'
+import { Route as BlogIndexRouteImport } from './routes/blog.index'
+import { Route as ShopProductIdRouteImport } from './routes/shop.$productId'
+import { Route as BlogSlugRouteImport } from './routes/blog.$slug'
+
+const ShopRoute = ShopRouteImport.update({
+ id: '/shop',
+ path: '/shop',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SettingsRoute = SettingsRouteImport.update({
+ id: '/settings',
+ path: '/settings',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const SearchRoute = SearchRouteImport.update({
+ id: '/search',
+ path: '/search',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const BlogRoute = BlogRouteImport.update({
+ id: '/blog',
+ path: '/blog',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AboutRoute = AboutRouteImport.update({
+ id: '/about',
+ path: '/about',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ShopIndexRoute = ShopIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => ShopRoute,
+} as any)
+const BlogIndexRoute = BlogIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => BlogRoute,
+} as any)
+const ShopProductIdRoute = ShopProductIdRouteImport.update({
+ id: '/$productId',
+ path: '/$productId',
+ getParentRoute: () => ShopRoute,
+} as any)
+const BlogSlugRoute = BlogSlugRouteImport.update({
+ id: '/$slug',
+ path: '/$slug',
+ getParentRoute: () => BlogRoute,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/blog': typeof BlogRouteWithChildren
+ '/search': typeof SearchRoute
+ '/settings': typeof SettingsRoute
+ '/shop': typeof ShopRouteWithChildren
+ '/blog/$slug': typeof BlogSlugRoute
+ '/shop/$productId': typeof ShopProductIdRoute
+ '/blog/': typeof BlogIndexRoute
+ '/shop/': typeof ShopIndexRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/search': typeof SearchRoute
+ '/settings': typeof SettingsRoute
+ '/blog/$slug': typeof BlogSlugRoute
+ '/shop/$productId': typeof ShopProductIdRoute
+ '/blog': typeof BlogIndexRoute
+ '/shop': typeof ShopIndexRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/about': typeof AboutRoute
+ '/blog': typeof BlogRouteWithChildren
+ '/search': typeof SearchRoute
+ '/settings': typeof SettingsRoute
+ '/shop': typeof ShopRouteWithChildren
+ '/blog/$slug': typeof BlogSlugRoute
+ '/shop/$productId': typeof ShopProductIdRoute
+ '/blog/': typeof BlogIndexRoute
+ '/shop/': typeof ShopIndexRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | '/about'
+ | '/blog'
+ | '/search'
+ | '/settings'
+ | '/shop'
+ | '/blog/$slug'
+ | '/shop/$productId'
+ | '/blog/'
+ | '/shop/'
+ fileRoutesByTo: FileRoutesByTo
+ to:
+ | '/'
+ | '/about'
+ | '/search'
+ | '/settings'
+ | '/blog/$slug'
+ | '/shop/$productId'
+ | '/blog'
+ | '/shop'
+ id:
+ | '__root__'
+ | '/'
+ | '/about'
+ | '/blog'
+ | '/search'
+ | '/settings'
+ | '/shop'
+ | '/blog/$slug'
+ | '/shop/$productId'
+ | '/blog/'
+ | '/shop/'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ AboutRoute: typeof AboutRoute
+ BlogRoute: typeof BlogRouteWithChildren
+ SearchRoute: typeof SearchRoute
+ SettingsRoute: typeof SettingsRoute
+ ShopRoute: typeof ShopRouteWithChildren
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/shop': {
+ id: '/shop'
+ path: '/shop'
+ fullPath: '/shop'
+ preLoaderRoute: typeof ShopRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/settings': {
+ id: '/settings'
+ path: '/settings'
+ fullPath: '/settings'
+ preLoaderRoute: typeof SettingsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/search': {
+ id: '/search'
+ path: '/search'
+ fullPath: '/search'
+ preLoaderRoute: typeof SearchRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/blog': {
+ id: '/blog'
+ path: '/blog'
+ fullPath: '/blog'
+ preLoaderRoute: typeof BlogRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/about': {
+ id: '/about'
+ path: '/about'
+ fullPath: '/about'
+ preLoaderRoute: typeof AboutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/shop/': {
+ id: '/shop/'
+ path: '/'
+ fullPath: '/shop/'
+ preLoaderRoute: typeof ShopIndexRouteImport
+ parentRoute: typeof ShopRoute
+ }
+ '/blog/': {
+ id: '/blog/'
+ path: '/'
+ fullPath: '/blog/'
+ preLoaderRoute: typeof BlogIndexRouteImport
+ parentRoute: typeof BlogRoute
+ }
+ '/shop/$productId': {
+ id: '/shop/$productId'
+ path: '/$productId'
+ fullPath: '/shop/$productId'
+ preLoaderRoute: typeof ShopProductIdRouteImport
+ parentRoute: typeof ShopRoute
+ }
+ '/blog/$slug': {
+ id: '/blog/$slug'
+ path: '/$slug'
+ fullPath: '/blog/$slug'
+ preLoaderRoute: typeof BlogSlugRouteImport
+ parentRoute: typeof BlogRoute
+ }
+ }
+}
+
+interface BlogRouteChildren {
+ BlogSlugRoute: typeof BlogSlugRoute
+ BlogIndexRoute: typeof BlogIndexRoute
+}
+
+const BlogRouteChildren: BlogRouteChildren = {
+ BlogSlugRoute: BlogSlugRoute,
+ BlogIndexRoute: BlogIndexRoute,
+}
+
+const BlogRouteWithChildren = BlogRoute._addFileChildren(BlogRouteChildren)
+
+interface ShopRouteChildren {
+ ShopProductIdRoute: typeof ShopProductIdRoute
+ ShopIndexRoute: typeof ShopIndexRoute
+}
+
+const ShopRouteChildren: ShopRouteChildren = {
+ ShopProductIdRoute: ShopProductIdRoute,
+ ShopIndexRoute: ShopIndexRoute,
+}
+
+const ShopRouteWithChildren = ShopRoute._addFileChildren(ShopRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ AboutRoute: AboutRoute,
+ BlogRoute: BlogRouteWithChildren,
+ SearchRoute: SearchRoute,
+ SettingsRoute: SettingsRoute,
+ ShopRoute: ShopRouteWithChildren,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
diff --git a/benchmarks/client-nav/scenarios/mount/react/src/routes/__root.tsx b/benchmarks/client-nav/scenarios/mount/react/src/routes/__root.tsx
new file mode 100644
index 0000000000..0deab171d8
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/src/routes/__root.tsx
@@ -0,0 +1,41 @@
+import { Link, Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/src/routes/about.tsx b/benchmarks/client-nav/scenarios/mount/react/src/routes/about.tsx
new file mode 100644
index 0000000000..3fedde283b
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/src/routes/about.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/about')({
+ component: AboutPage,
+})
+
+function AboutPage() {
+ return About
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.$slug.tsx b/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.$slug.tsx
new file mode 100644
index 0000000000..f995d95d94
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.$slug.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { articleData } from '../../../shared'
+
+export const Route = createFileRoute('/blog/$slug')({
+ loader: ({ params }) => articleData(params.slug),
+ component: ArticlePage,
+})
+
+function ArticlePage() {
+ const article = Route.useLoaderData()
+
+ return {`${article.title} (${article.words} words)`}
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.index.tsx b/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.index.tsx
new file mode 100644
index 0000000000..437187eb4f
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/blog/')({
+ component: BlogIndexPage,
+})
+
+function BlogIndexPage() {
+ return All articles
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.tsx b/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.tsx
new file mode 100644
index 0000000000..ab4fbeb482
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/src/routes/blog.tsx
@@ -0,0 +1,16 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import { blogContext } from '../../../shared'
+
+export const Route = createFileRoute('/blog')({
+ beforeLoad: () => blogContext(),
+ component: BlogLayout,
+})
+
+function BlogLayout() {
+ return (
+
+ )
+}
diff --git a/benchmarks/client-nav/scenarios/mount/react/src/routes/index.tsx b/benchmarks/client-nav/scenarios/mount/react/src/routes/index.tsx
new file mode 100644
index 0000000000..dbc99d8f66
--- /dev/null
+++ b/benchmarks/client-nav/scenarios/mount/react/src/routes/index.tsx
@@ -0,0 +1,22 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { homeItems, readyTestId } from '../../../shared'
+
+export const Route = createFileRoute('/')({
+ loader: () => homeItems(),
+ component: HomePage,
+})
+
+function HomePage() {
+ const items = Route.useLoaderData()
+
+ return (
+
+ {`Home (${items.length} items)`}
+
+ {items.map((item) => (
+