Describe the bug
A navigation's resetScroll intent is stored in a single slot on the router rather than per navigation:
// packages/router-core/src/router.ts
this._scroll.next = next.resetScroll ?? true
If a second navigation is committed while the first is still settling, it overwrites that slot and the first navigation's scroll reset is silently dropped.
This shows up with an ordinary pattern: a page derives state from its own data and writes it back into its URL on first render.
useEffect(() => {
if (color) return
navigate({
to: '/results',
replace: true,
resetScroll: false, // correct: this is not a page change
search: (prev) => ({ ...prev, color: 'w,u,c' }),
})
}, [color, navigate])
resetScroll: false is correct for that navigation — it only adds a search param to the route you are already on, and it should not move the viewport. But it lands in the same slot as the resetScroll: true recorded by the link navigation that is still settling. That navigation then never resets, and the user is left looking at the new page at the previous page's scroll offset.
Omitting resetScroll: false is not a workaround either: the reset then happens, but under the second navigation's timing, so the destination is painted at the old scroll offset first and visibly jumps to the top afterwards.
The two navigations have independent, explicitly documented resetScroll values, so neither should be able to consume or cancel the other's.
Your Example Website or App
https://github.com/daveycodez/tanstack-router-scroll-repro
Steps to Reproduce the Bug or Issue
npm install && npm run dev
- Scroll to the bottom of the home page
- Click "open the results page"
Or run the included Playwright test: npx playwright install chromium && npm test
Expected behavior
Following the link resets scroll to the top of the results page.
Actual behavior
Scroll stays at the home page's offset (~8400px). The read-out in the bottom-right corner turns red.
The only variable is resetScroll: false on the second navigation, over 3 runs each:
| second navigation |
link navigation's reset |
resetScroll: false |
never applied — stays at 8420 |
default (true) |
applied — 0 |
Platform
- @tanstack/react-router: 1.170.25
- Browser: Chromium 141
- OS: macOS
Additional context
Found while debugging a search page that infers filters from its results and writes them into the URL after render — the write cannot happen in beforeLoad/SSR there, because the filtered URL must not be the indexed one.
Describe the bug
A navigation's
resetScrollintent is stored in a single slot on the router rather than per navigation:If a second navigation is committed while the first is still settling, it overwrites that slot and the first navigation's scroll reset is silently dropped.
This shows up with an ordinary pattern: a page derives state from its own data and writes it back into its URL on first render.
resetScroll: falseis correct for that navigation — it only adds a search param to the route you are already on, and it should not move the viewport. But it lands in the same slot as theresetScroll: truerecorded by the link navigation that is still settling. That navigation then never resets, and the user is left looking at the new page at the previous page's scroll offset.Omitting
resetScroll: falseis not a workaround either: the reset then happens, but under the second navigation's timing, so the destination is painted at the old scroll offset first and visibly jumps to the top afterwards.The two navigations have independent, explicitly documented
resetScrollvalues, so neither should be able to consume or cancel the other's.Your Example Website or App
https://github.com/daveycodez/tanstack-router-scroll-repro
Steps to Reproduce the Bug or Issue
npm install && npm run devOr run the included Playwright test:
npx playwright install chromium && npm testExpected behavior
Following the link resets scroll to the top of the results page.
Actual behavior
Scroll stays at the home page's offset (~8400px). The read-out in the bottom-right corner turns red.
The only variable is
resetScroll: falseon the second navigation, over 3 runs each:resetScroll: falsetrue)Platform
Additional context
Found while debugging a search page that infers filters from its results and writes them into the URL after render — the write cannot happen in
beforeLoad/SSR there, because the filtered URL must not be the indexed one.