Skip to content

feat(router-core) path: parsePathname returns a readonly array#4705

Merged
Sheraff merged 2 commits intomainfrom
router-core-path-readonly-parse
Jul 19, 2025
Merged

feat(router-core) path: parsePathname returns a readonly array#4705
Sheraff merged 2 commits intomainfrom
router-core-path-readonly-parse

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Jul 18, 2025

parsePathname should return a ReadonlyArray instead of Array, and Segment should be readonly too. This is because if we want to improve perf by either caching at runtime or pre-computing routes at build time, we need to ensure those objects can't be manipulated.

An unrelated change in this PR is also to move some inline functions to module-scoped functions: segmentToString (from resolvePath) and isMatch (from matchByPath). Those functions are in the hot path so it's nice if the runtime compiler can promote them to optimized functions.

@nx-cloud
Copy link

nx-cloud bot commented Jul 18, 2025

View your CI Pipeline Execution ↗ for commit aea7e85

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 12m 57s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2m 3s View ↗

☁️ Nx Cloud last updated this comment at 2025-07-18 23:03:07 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jul 18, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@4705

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@4705

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@4705

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@4705

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@4705

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@4705

@tanstack/react-router-with-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-with-query@4705

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@4705

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@4705

@tanstack/react-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-plugin@4705

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@4705

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@4705

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@4705

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@4705

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@4705

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@4705

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@4705

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@4705

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@4705

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@4705

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@4705

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@4705

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@4705

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@4705

@tanstack/solid-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-plugin@4705

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@4705

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@4705

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@4705

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@4705

@tanstack/start-server-functions-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-client@4705

@tanstack/start-server-functions-fetcher

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-fetcher@4705

@tanstack/start-server-functions-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-server@4705

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@4705

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@4705

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@4705

commit: aea7e85

@Sheraff Sheraff requested a review from schiller-manuel July 18, 2025 22:53
@Sheraff Sheraff merged commit 113bf21 into main Jul 19, 2025
5 checks passed
@Sheraff Sheraff deleted the router-core-path-readonly-parse branch July 19, 2025 08:13
Sheraff added a commit that referenced this pull request Jul 23, 2025
We noticed that `parsePathname` is a performance bottleneck during
navigation events. Here's a flamegraph from an application w/ ~300
routes around a navigation event:

<img width="853" height="372" alt="Screenshot 2025-07-22 at 19 36 24"
src="https://github.com/user-attachments/assets/7ed2e09a-8ea9-4170-862a-2d53c3eb4793"
/>


This PR proposes a basic least-recently-used cache implementation (that
data structure is about 4-5x slower than a `Map` according to
benchmarks, and it uses a little more memory).

We can add caching to `parsePathname` now that what it returns is
readonly (#4705), which should
improve the performance of this bottleneck.

The cache is set to a size of 1000. When / if we end up making a
pre-built matcher (WIP #4714), we
maybe can reduce this size. But `parsePathname` is still called w/ built
pathnames so we probably shouldn't remove the cache entirely.


When benchmarking `matchPathname` with and without the cache, we notice
a ~9x increase in throughput with the cache.
```
 ✓  @tanstack/router-core  tests/cache.bench.ts > cache.bench 7191ms
     name             hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · original   1,795.61  0.5055  0.7022  0.5569  0.5604  0.6234  0.6464  0.7022  ±0.22%      898
   · cached    16,307.87  0.0562  0.2294  0.0613  0.0608  0.0767  0.1007  0.1152  ±0.17%     8154   fastest

 BENCH  Summary

   @tanstack/router-core  cached - tests/cache.bench.ts > cache.bench
    9.08x faster than original
```
Assuming this 9x increase translates to a proportional reduction of the
self-time seen in the flamegraph, it would go from 55ms to 6ms.
naoya7076 pushed a commit to naoya7076/router that referenced this pull request Feb 15, 2026
…ack#4705)

`parsePathname` should return a `ReadonlyArray` instead of `Array`, and
`Segment` should be `readonly` too. This is because if we want to
improve perf by either caching at runtime or pre-computing routes at
build time, we need to ensure those objects can't be manipulated.

An unrelated change in this PR is also to move some inline functions to
module-scoped functions: `segmentToString` (from `resolvePath`) and
`isMatch` (from `matchByPath`). Those functions are in the hot path so
it's nice if the runtime compiler can promote them to optimized
functions.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
naoya7076 pushed a commit to naoya7076/router that referenced this pull request Feb 15, 2026
…ack#4752)

We noticed that `parsePathname` is a performance bottleneck during
navigation events. Here's a flamegraph from an application w/ ~300
routes around a navigation event:

<img width="853" height="372" alt="Screenshot 2025-07-22 at 19 36 24"
src="https://github.com/user-attachments/assets/7ed2e09a-8ea9-4170-862a-2d53c3eb4793"
/>


This PR proposes a basic least-recently-used cache implementation (that
data structure is about 4-5x slower than a `Map` according to
benchmarks, and it uses a little more memory).

We can add caching to `parsePathname` now that what it returns is
readonly (TanStack#4705), which should
improve the performance of this bottleneck.

The cache is set to a size of 1000. When / if we end up making a
pre-built matcher (WIP TanStack#4714), we
maybe can reduce this size. But `parsePathname` is still called w/ built
pathnames so we probably shouldn't remove the cache entirely.


When benchmarking `matchPathname` with and without the cache, we notice
a ~9x increase in throughput with the cache.
```
 ✓  @tanstack/router-core  tests/cache.bench.ts > cache.bench 7191ms
     name             hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · original   1,795.61  0.5055  0.7022  0.5569  0.5604  0.6234  0.6464  0.7022  ±0.22%      898
   · cached    16,307.87  0.0562  0.2294  0.0613  0.0608  0.0767  0.1007  0.1152  ±0.17%     8154   fastest

 BENCH  Summary

   @tanstack/router-core  cached - tests/cache.bench.ts > cache.bench
    9.08x faster than original
```
Assuming this 9x increase translates to a proportional reduction of the
self-time seen in the flamegraph, it would go from 55ms to 6ms.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants