Skip to content

fix: Fix hash scrolling with resetScroll={false}#7464

Merged
schiller-manuel merged 2 commits into
mainfrom
fix-hash-reset-false
May 21, 2026
Merged

fix: Fix hash scrolling with resetScroll={false}#7464
schiller-manuel merged 2 commits into
mainfrom
fix-hash-reset-false

Conversation

@schiller-manuel
Copy link
Copy Markdown
Collaborator

@schiller-manuel schiller-manuel commented May 21, 2026

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed hash-based scrolling behavior when scroll reset is disabled
    • Improved scroll position restoration in server-side rendering scenarios
    • Enhanced scroll restoration to properly handle custom session keys
  • Tests

    • Added end-to-end tests validating scroll restoration across various navigation scenarios

Review Change Stack

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented May 21, 2026

View your CI Pipeline Execution ↗ for commit efcc684

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

☁️ Nx Cloud last updated this comment at 2026-05-21 20:44:08 UTC

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Warning

Rate limit exceeded

@schiller-manuel has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 56 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1b18fa5d-3a57-4e38-a37f-b744a4d676fa

📥 Commits

Reviewing files that changed from the base of the PR and between efcc684 and 3a0512b.

📒 Files selected for processing (1)
  • packages/router-core/tests/scroll-restoration-script.test.ts
📝 Walkthrough

Walkthrough

This PR fixes scroll restoration behavior in TanStack Router by refactoring the inline script API from an options object to positional parameters, updating the script generation and scroll handler flow logic, and adding comprehensive unit and end-to-end test coverage for hash-based and SSR scroll key restoration scenarios.

Changes

Scroll Restoration Fixes

Layer / File(s) Summary
Inline Script API Refactor and Script Generation
packages/router-core/src/scroll-restoration-inline.ts, packages/router-core/src/scroll-restoration-script/server.ts
API changed from options: { storageKey, key? } to positional parameters (storageKey, key?). Inline script now parses sessionStorage with silent error handling, restores window and element scroll positions via scrollTo, and wraps querySelector calls in try/catch. Script generation embeds storageKey directly and passes both parameters as separate JSON arguments.
Scroll Restoration Handler Flow Logic
packages/router-core/src/scroll-restoration.ts
Refactored the reset scroll path to conditionally set ignoreScroll, restore cached window and element positions with hash awareness, and split fallback behavior so scroll-to-top only triggers when there is no hash present; hash elements use dedicated scrollIntoView fallback.
SSR Script Generation Unit Tests
packages/router-core/tests/scroll-restoration-script.test.ts
Added Vitest suite verifying getScrollRestorationScriptForRouter generates correct scrollTo calls for custom and default restoration keys, and ensuring silent resilience with invalid/malformed sessionStorage entries without throwing.
SSR Test Routes and Route Registration
e2e/react-start/scroll-restoration/src/routeTree.gen.ts, e2e/react-start/scroll-restoration/src/routes/(tests)/ssr-scroll-key.tsx, e2e/react-start/scroll-restoration/src/router.tsx
Registered new /(tests)/ssr-scroll-key route with full type-safety across FileRoutesByFullPath, FileRoutesByTo, FileRoutesById and module augmentation. Created corresponding route component with ScrollBlock. Router returns custom ssr-scroll-key restoration key for the SSR test path.
End-to-End Tests
e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx, e2e/react-start/scroll-restoration/tests/app.spec.ts, e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts, e2e/react-start/scroll-restoration/tests/ssr-scroll-key.spec.ts
Added Playwright e2e tests validating scroll position preservation on page reload, hard navigation to hash URLs, hash navigation with resetScroll={false} (scrolling relative to prior position), and SSR scroll restoration using custom sessionStorage keys. Includes "no reset" hash link in test route.
Changesets Release Note
.changeset/scroll-restoration-fixes.md
Documents patch release for @tanstack/router-core fixing hash scrolling behavior when resetScroll={false}.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • TanStack/router#7447: Both PRs modify the same scroll-restoration code paths and extend the e2e scroll-restoration tests around resetScroll={false} and hash/SSR restoration keys.

Suggested labels

package: router-core, package: react-router

Suggested reviewers

  • Sheraff

Poem

🐰 A scrollbar's tale, now set just right,
With hashes and keys in the DOM's light,
No reset means momentum stays true,
SSR scroll remembers each view,
Tests prove the journey—up, down, anew! 🎢

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main fix: resolving hash scrolling behavior when resetScroll={false} is used, which is reflected across the core scroll restoration changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-hash-reset-false

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🚀 Changeset Version Preview

1 package(s) bumped directly, 22 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/router-core 1.171.4 → 1.171.5 Changeset
@tanstack/react-router 1.170.6 → 1.170.7 Dependent
@tanstack/react-start 1.168.9 → 1.168.10 Dependent
@tanstack/react-start-client 1.168.1 → 1.168.2 Dependent
@tanstack/react-start-rsc 0.1.9 → 0.1.10 Dependent
@tanstack/react-start-server 1.167.6 → 1.167.7 Dependent
@tanstack/router-cli 1.167.8 → 1.167.9 Dependent
@tanstack/router-generator 1.167.8 → 1.167.9 Dependent
@tanstack/router-plugin 1.168.9 → 1.168.10 Dependent
@tanstack/router-vite-plugin 1.167.9 → 1.167.10 Dependent
@tanstack/solid-router 1.170.6 → 1.170.7 Dependent
@tanstack/solid-start 1.168.9 → 1.168.10 Dependent
@tanstack/solid-start-client 1.168.1 → 1.168.2 Dependent
@tanstack/solid-start-server 1.167.6 → 1.167.7 Dependent
@tanstack/start-client-core 1.170.1 → 1.170.2 Dependent
@tanstack/start-plugin-core 1.171.2 → 1.171.3 Dependent
@tanstack/start-server-core 1.169.1 → 1.169.2 Dependent
@tanstack/start-static-server-functions 1.167.6 → 1.167.7 Dependent
@tanstack/start-storage-context 1.167.6 → 1.167.7 Dependent
@tanstack/vue-router 1.170.6 → 1.170.7 Dependent
@tanstack/vue-start 1.168.9 → 1.168.10 Dependent
@tanstack/vue-start-client 1.167.6 → 1.167.7 Dependent
@tanstack/vue-start-server 1.167.6 → 1.167.7 Dependent

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 21, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7464

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7464

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7464

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7464

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7464

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7464

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7464

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7464

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7464

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7464

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7464

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7464

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7464

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7464

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7464

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7464

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7464

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7464

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7464

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7464

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7464

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7464

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7464

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7464

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7464

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7464

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7464

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7464

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7464

commit: 3a0512b

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Bundle Size Benchmarks

  • Commit: df92df6ec2b9
  • Measured at: 2026-05-21T20:44:47.379Z
  • Baseline source: history:65b4abe65bc2
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Initial gzip Raw Brotli Trend
react-router.minimal 87.29 KiB -2 B (-0.00%) 87.16 KiB 274.02 KiB 75.93 KiB ███████▁▁▁▁▁
react-router.full 90.82 KiB +3 B (+0.00%) 90.69 KiB 285.52 KiB 78.75 KiB ▆▆▆▆▆▆█▁▁▁▁▁
solid-router.minimal 35.53 KiB +2 B (+0.01%) 35.41 KiB 106.33 KiB 31.95 KiB ▅▅▅████▁▁▁▁▁
solid-router.full 40.28 KiB +6 B (+0.01%) 40.15 KiB 120.53 KiB 36.20 KiB ▁▁▁▂▂▂▃▄▄▄▄█
vue-router.minimal 53.33 KiB +4 B (+0.01%) 53.20 KiB 151.46 KiB 47.88 KiB ▇▇▇▇▇▇▁▅▅▅▅█
vue-router.full 58.45 KiB +1 B (+0.00%) 58.32 KiB 167.63 KiB 52.35 KiB ▆▆▆▆▆▆▁▆▆▆▆█
react-start.minimal 102.03 KiB +4 B (+0.00%) 101.89 KiB 322.47 KiB 88.29 KiB ▁▁▁▄▄▄▃▆▆▆▆█
react-start.deferred-hydration 103.07 KiB +1 B (+0.00%) 102.20 KiB 324.12 KiB 89.21 KiB ████▁▁▁▁▂
react-start.full 105.43 KiB +8 B (+0.01%) 105.29 KiB 332.79 KiB 91.17 KiB ▆▆▆████▁▁▁▁▃
react-start.rsbuild.minimal 99.64 KiB 0 B (0.00%) 99.46 KiB 316.93 KiB 85.73 KiB ▆▆▆▆▆▆▁████
react-start.rsbuild.full 102.93 KiB +4 B (+0.00%) 102.76 KiB 327.34 KiB 88.59 KiB ▇▇▇▇▇▇▁▂▂▂▂█
solid-start.minimal 49.67 KiB -2 B (-0.00%) 49.54 KiB 152.46 KiB 43.87 KiB ▁▁▁███▇▄▄▄▄▄
solid-start.deferred-hydration 53.74 KiB +1 B (+0.00%) 50.40 KiB 161.00 KiB 47.74 KiB ███▇▁▁▁▁▁
solid-start.full 55.46 KiB +1 B (+0.00%) 55.33 KiB 169.35 KiB 48.80 KiB ▁▁▁███▆▆▆▆▆▇

Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/router-core/tests/scroll-restoration-script.test.ts`:
- Around line 85-137: Add a regression test that covers the "missing restoration
key" case by creating a router via createScrollRestorationRouter(), generating
its script with getScrollRestorationScriptForRouter(router), stubbing scrollTo,
setting window.history.replaceState to contain a __TSR_key that does not exist
in sessionStorage, and storing sessionStorage[storageKey] as a JSON object that
omits that key (so elementEntries === undefined). Then call
runScrollRestorationScript(script) and assert it does not throw and that
scrollTo was called with (0, 0) to verify fallback-to-top behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 452474da-4863-4210-b954-fdf6b0823f62

📥 Commits

Reviewing files that changed from the base of the PR and between e2896b5 and efcc684.

📒 Files selected for processing (12)
  • .changeset/scroll-restoration-fixes.md
  • e2e/react-start/scroll-restoration/src/routeTree.gen.ts
  • e2e/react-start/scroll-restoration/src/router.tsx
  • e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx
  • e2e/react-start/scroll-restoration/src/routes/(tests)/ssr-scroll-key.tsx
  • e2e/react-start/scroll-restoration/tests/app.spec.ts
  • e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts
  • e2e/react-start/scroll-restoration/tests/ssr-scroll-key.spec.ts
  • packages/router-core/src/scroll-restoration-inline.ts
  • packages/router-core/src/scroll-restoration-script/server.ts
  • packages/router-core/src/scroll-restoration.ts
  • packages/router-core/tests/scroll-restoration-script.test.ts

Comment thread packages/router-core/tests/scroll-restoration-script.test.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@schiller-manuel schiller-manuel merged commit 5268ba4 into main May 21, 2026
19 checks passed
@schiller-manuel schiller-manuel deleted the fix-hash-reset-false branch May 21, 2026 20:54
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 21, 2026

Merging this PR will not alter performance

✅ 5 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing fix-hash-reset-false (3a0512b) with main (65b4abe)2

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

  2. No successful run was found on main (e2896b5) during the generation of this report, so 65b4abe was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

This was referenced May 21, 2026
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.

1 participant