Skip to content

Commit 88a17c1

Browse files
benchmark navigation
1 parent 86605e3 commit 88a17c1

3 files changed

Lines changed: 232 additions & 13 deletions

File tree

packages/router-core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"tiny-warning": "^1.0.3"
8989
},
9090
"devDependencies": {
91-
"esbuild": "^0.25.0"
91+
"esbuild": "^0.25.0",
92+
"zod": "^3.24.2"
9293
}
9394
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { bench, describe } from 'vitest'
2+
import { createMemoryHistory } from '@tanstack/history'
3+
import { z } from 'zod'
4+
import { BaseRootRoute, BaseRoute, RouterCore } from '../src'
5+
6+
// ============================================================================
7+
// Router with Zod search validation (realistic scenario)
8+
// ============================================================================
9+
function createRouter() {
10+
const rootRoute = new BaseRootRoute({})
11+
12+
const indexRoute = new BaseRoute({
13+
getParentRoute: () => rootRoute,
14+
path: '/',
15+
})
16+
17+
const usersRoute = new BaseRoute({
18+
getParentRoute: () => rootRoute,
19+
path: '/users',
20+
validateSearch: z.object({
21+
filter: z.enum(['active', 'inactive', 'all']).optional(),
22+
sort: z.enum(['name', 'date', 'email']).optional(),
23+
page: z.number().optional(),
24+
}),
25+
})
26+
27+
const userRoute = new BaseRoute({
28+
getParentRoute: () => usersRoute,
29+
path: '/$userId',
30+
})
31+
32+
const userSettingsRoute = new BaseRoute({
33+
getParentRoute: () => userRoute,
34+
path: '/settings',
35+
validateSearch: z.object({
36+
tab: z.enum(['general', 'security', 'notifications']).optional(),
37+
}),
38+
})
39+
40+
const productsRoute = new BaseRoute({
41+
getParentRoute: () => rootRoute,
42+
path: '/products',
43+
validateSearch: z.object({
44+
category: z.string().optional(),
45+
minPrice: z.number().optional(),
46+
maxPrice: z.number().optional(),
47+
inStock: z.boolean().optional(),
48+
}),
49+
})
50+
51+
const productRoute = new BaseRoute({
52+
getParentRoute: () => productsRoute,
53+
path: '/$productId',
54+
})
55+
56+
const productReviewsRoute = new BaseRoute({
57+
getParentRoute: () => productRoute,
58+
path: '/reviews',
59+
validateSearch: z.object({
60+
rating: z.number().optional(),
61+
verified: z.boolean().optional(),
62+
}),
63+
})
64+
65+
const routeTree = rootRoute.addChildren([
66+
indexRoute,
67+
usersRoute.addChildren([userRoute.addChildren([userSettingsRoute])]),
68+
productsRoute.addChildren([
69+
productRoute.addChildren([productReviewsRoute]),
70+
]),
71+
])
72+
73+
return new RouterCore({
74+
routeTree,
75+
history: createMemoryHistory(),
76+
})
77+
}
78+
79+
// ============================================================================
80+
// Real Navigation Benchmarks - Tests actual navigate() calls
81+
// ============================================================================
82+
83+
describe('Navigation: search param updates (same route)', () => {
84+
bench('update page number 1000x', async () => {
85+
const router = createRouter()
86+
await router.load()
87+
88+
// Navigate to users route first
89+
await router.navigate({
90+
to: '/users',
91+
search: { filter: 'active', sort: 'name', page: 1 },
92+
})
93+
94+
// Update page param repeatedly
95+
for (let i = 0; i < 1000; i++) {
96+
await router.navigate({
97+
to: '/users',
98+
search: { filter: 'active', sort: 'name', page: i % 100 },
99+
})
100+
}
101+
})
102+
103+
bench('toggle filter param 1000x', async () => {
104+
const router = createRouter()
105+
await router.load()
106+
107+
await router.navigate({
108+
to: '/users',
109+
search: { filter: 'active', sort: 'name', page: 1 },
110+
})
111+
112+
const filters = ['active', 'inactive', 'all'] as const
113+
for (let i = 0; i < 1000; i++) {
114+
await router.navigate({
115+
to: '/users',
116+
search: { filter: filters[i % 3], sort: 'name', page: 1 },
117+
})
118+
}
119+
})
120+
})
121+
122+
describe('Navigation: route changes with params', () => {
123+
bench('navigate between user profiles 1000x', async () => {
124+
const router = createRouter()
125+
await router.load()
126+
127+
for (let i = 0; i < 1000; i++) {
128+
await router.navigate({
129+
to: '/users/$userId/settings',
130+
params: { userId: `user-${i % 100}` },
131+
search: { tab: 'security' },
132+
})
133+
}
134+
})
135+
136+
bench('navigate between products 1000x', async () => {
137+
const router = createRouter()
138+
await router.load()
139+
140+
for (let i = 0; i < 1000; i++) {
141+
await router.navigate({
142+
to: '/products/$productId/reviews',
143+
params: { productId: `prod-${i % 100}` },
144+
search: { rating: (i % 5) + 1, verified: i % 2 === 0 },
145+
})
146+
}
147+
})
148+
})
149+
150+
describe('Navigation: mixed navigation patterns', () => {
151+
bench('alternating routes 1000x', async () => {
152+
const router = createRouter()
153+
await router.load()
154+
155+
for (let i = 0; i < 1000; i++) {
156+
if (i % 2 === 0) {
157+
await router.navigate({
158+
to: '/users/$userId/settings',
159+
params: { userId: `user-${i}` },
160+
search: { tab: 'general' },
161+
})
162+
} else {
163+
await router.navigate({
164+
to: '/products/$productId/reviews',
165+
params: { productId: `prod-${i}` },
166+
search: { rating: 5, verified: true },
167+
})
168+
}
169+
}
170+
})
171+
172+
bench('deep to shallow navigation 1000x', async () => {
173+
const router = createRouter()
174+
await router.load()
175+
176+
for (let i = 0; i < 1000; i++) {
177+
if (i % 2 === 0) {
178+
// Deep route
179+
await router.navigate({
180+
to: '/users/$userId/settings',
181+
params: { userId: `user-${i}` },
182+
search: { tab: 'security' },
183+
})
184+
} else {
185+
// Shallow route
186+
await router.navigate({
187+
to: '/',
188+
})
189+
}
190+
}
191+
})
192+
})
193+
194+
describe('Navigation: back/forward simulation', () => {
195+
bench('push 500 then back 500', async () => {
196+
const router = createRouter()
197+
await router.load()
198+
199+
// Push 500 navigations
200+
for (let i = 0; i < 500; i++) {
201+
await router.navigate({
202+
to: '/users/$userId/settings',
203+
params: { userId: `user-${i}` },
204+
search: { tab: i % 2 === 0 ? 'general' : 'security' },
205+
})
206+
}
207+
208+
// Go back 500 times
209+
for (let i = 0; i < 500; i++) {
210+
router.history.back()
211+
// Wait for history to settle
212+
await new Promise((r) => setTimeout(r, 0))
213+
}
214+
})
215+
})

pnpm-lock.yaml

Lines changed: 15 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)