Skip to content

fix(react): Match TanStack Router pageload against the router location - #23299

Merged
JPeer264 merged 2 commits into
developfrom
jp/tanstack-pageload-basepath
Aug 23, 2026
Merged

fix(react): Match TanStack Router pageload against the router location#23299
JPeer264 merged 2 commits into
developfrom
jp/tanstack-pageload-basepath

Conversation

@JPeer264

@JPeer264 JPeer264 commented Aug 11, 2026

Copy link
Copy Markdown
Member

closes #23253
closes #23253

When a basepath is added then the window.location.pathname has the wrong information. We should actually use the routers pathname instead: https://tanstack.com/router/latest/docs/api/router/ParsedLocationType

This should be guaranteed to have the correct info (but still have a fallback in case something goes south). A new basepath variant in the tanstack-router E2E test is now testing if the tests also work with the basepath included (plus two specific tests as regression on top).


Description done by AI (and added on the commit):

The pageload span resolved its route by passing window.location.pathname to router.matchRoutes. With a router basepath that pathname still carries the prefix, which the router itself never sees, so a sufficiently dynamic route absorbed the extra segments and a plausible-but-wrong route id won (/$a/$b/$c with a = "app" instead of /posts/$postId).

The transaction name recovers on its own, since onResolved renames the span before it is sent. The scope transaction does not: it is pinned when the pageload span starts and never rewritten, so error events kept the wrong transaction for the whole page lifetime. The stale params from the bad match also survive on the span, because onResolved merges the correct ones in without clearing them.

Match against the router's own state.location instead. Stripping the basepath by hand would have fixed only recent routers and broken the documented 1.64.0 minimum, where matchRoutes is basepath-aware internally and expects the prefix to still be there. state.location is by construction in the form its own version expects — stripped where the router rewrites in parseLocation, unstripped where it does not — so no version detection is needed. It also carries an already-parsed search, dropping a redundant parseSearch call.

@JPeer264 JPeer264 self-assigned this Aug 11, 2026
@JPeer264
JPeer264 requested a review from a team as a code owner August 11, 2026 14:12
@JPeer264
JPeer264 requested review from mydea and s1gr1d and removed request for a team August 11, 2026 14:12
@linear-code

linear-code Bot commented Aug 11, 2026

Copy link
Copy Markdown

JS-3341

@JPeer264
JPeer264 requested review from nicohrubec and removed request for mydea August 11, 2026 14:13
Comment thread packages/react/src/tanstackrouter.ts
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 30.3 kB - -
@sentry/browser - with treeshaking flags 28.47 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 26.81 kB - -
@sentry/browser (incl. Tracing) 48.58 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 48.59 kB - -
@sentry/browser (incl. Tracing, Profiling) 51.46 kB - -
@sentry/browser (incl. Tracing, Replay) 87.98 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 77.36 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 92.7 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 105.39 kB - -
@sentry/browser (incl. Feedback) 47.65 kB - -
@sentry/browser (incl. sendFeedback) 35.13 kB - -
@sentry/browser (incl. FeedbackAsync) 40.28 kB - -
@sentry/browser (incl. Metrics) 31.24 kB - -
@sentry/browser (incl. Logs) 31.52 kB - -
@sentry/browser (incl. Metrics & Logs) 32.15 kB - -
@sentry/react 32.09 kB - -
@sentry/react (incl. Tracing) 50.77 kB - -
@sentry/vue 35.34 kB - -
@sentry/vue (incl. Tracing) 50.54 kB - -
@sentry/svelte 30.33 kB - -
CDN Bundle 31.61 kB - -
CDN Bundle (incl. Tracing) 48.9 kB - -
CDN Bundle (incl. Logs, Metrics) 33.8 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 50.82 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 74.31 kB - -
CDN Bundle (incl. Tracing, Replay) 86.48 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 88.31 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 92.19 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 94.12 kB - -
CDN Bundle - uncompressed 93.84 kB - -
CDN Bundle (incl. Tracing) - uncompressed 146.75 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 100.14 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 152.44 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 229.08 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 266.01 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 271.68 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 279.71 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 285.37 kB - -
@sentry/nextjs (client) 53.29 kB - -
@sentry/sveltekit (client) 49 kB - -
@sentry/core/server 65.38 kB - -
@sentry/core/browser 51.72 kB - -
@sentry/node 117.47 kB +0.02% +22 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 85.18 kB - -
@sentry/node - without tracing 82 kB +0.03% +21 B 🔺
@sentry/aws-serverless 91.37 kB +0.03% +27 B 🔺
@sentry/cloudflare (withSentry) - minified 194.35 kB - -
@sentry/cloudflare (withSentry) 480.53 kB - -

View base workflow run


await page.goto(`${BASE}/posts/456`);

await page.waitForTimeout(1000);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: this seems like it could easily flake. can we do this differently? maybe wait for the initial transaction and only then throw an error and see if we get the correct data

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Updated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// The scope transaction is set when the pageload span starts, and the later `updateName` in
// `onResolved` does not rewrite it — so a wrong initial match mislabelled every error for the
// whole page lifetime. This is the part of the bug the transaction name alone does not reveal.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we clarify this comment, e.g. what does "this is the part of the bug the transaction name alone does not reveal" mean?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope the machinery is clearer now

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is very revealing

@github-actions

Copy link
Copy Markdown
Contributor

👋 @s1gr1d — Please review this PR when you get a chance!

1 similar comment
@github-actions

Copy link
Copy Markdown
Contributor

👋 @s1gr1d — Please review this PR when you get a chance!

@JPeer264
JPeer264 requested a review from nicohrubec August 23, 2026 06:56
Comment thread packages/react/src/tanstackrouter.ts

@nicohrubec nicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥇

JPeer264 and others added 2 commits August 23, 2026 11:09
The pageload span resolved its route by passing `window.location.pathname`
to `router.matchRoutes`. With a router `basepath` that pathname still carries
the prefix, which the router itself never sees, so a sufficiently dynamic
route absorbed the extra segments and a plausible-but-wrong route id won
(`/$a/$b/$c` with `a = "app"` instead of `/posts/$postId`).

The transaction name recovers on its own, since `onResolved` renames the span
before it is sent. The scope transaction does not: it is pinned when the
pageload span starts and never rewritten, so error events kept the wrong
transaction for the whole page lifetime. The stale params from the bad match
also survive on the span, because `onResolved` merges the correct ones in
without clearing them.

Match against the router's own `state.location` instead. Stripping the
basepath by hand would have fixed only recent routers and broken the
documented 1.64.0 minimum, where `matchRoutes` is basepath-aware internally
and expects the prefix to still be there. `state.location` is by construction
in the form its own version expects — stripped where the router rewrites in
`parseLocation`, unstripped where it does not — so no version detection is
needed. It also carries an already-parsed `search`, dropping a redundant
`parseSearch` call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@JPeer264
JPeer264 force-pushed the jp/tanstack-pageload-basepath branch from b503ac9 to 8bebcd7 Compare August 23, 2026 08:09
@JPeer264
JPeer264 merged commit 57c9916 into develop Aug 23, 2026
121 of 122 checks passed
@JPeer264
JPeer264 deleted the jp/tanstack-pageload-basepath branch August 23, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tanstackRouterBrowserTracingIntegration: pageload transaction name ignores router basepath

2 participants