Skip to content

fix: preserve scroll position after SSR hash hydration#7066

Merged
schiller-manuel merged 2 commits intomainfrom
fix-hash-scroll
Mar 28, 2026
Merged

fix: preserve scroll position after SSR hash hydration#7066
schiller-manuel merged 2 commits intomainfrom
fix-hash-scroll

Conversation

@schiller-manuel
Copy link
Copy Markdown
Contributor

@schiller-manuel schiller-manuel commented Mar 28, 2026

Summary by CodeRabbit

  • New Features

    • Added a small in-app scenario and navigation link to reproduce and inspect hash-scroll behavior.
  • Bug Fixes

    • Fixed unintended scroll position changes after SSR hydration when preloading routes or invalidating data without a location change; hash-based scroll restoration now remains stable.
  • Tests

    • Added end-to-end tests verifying scroll position during route preloading and data invalidation cycles.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 22cb0692-bfbe-4c0f-9334-4d1c8c4ee24a

📥 Commits

Reviewing files that changed from the base of the PR and between 2a64368 and 66b7228.

📒 Files selected for processing (1)
  • e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts

📝 Walkthrough

Walkthrough

Prevents re-running hash scroll after SSR hydration by marking the router's resolved location when hydration skips a load; adds E2E routes and tests reproducing the hash-scroll scenario.

Changes

Cohort / File(s) Summary
Core SSR Hydration Fix
packages/router-core/src/ssr/ssr-client.ts
In hydrate(), when skipping router.load() during SSR hydration, set router.stores.resolvedLocation to the current location state to avoid false location-change detection on later preload/invalidate cycles.
Changeset Documentation
.changeset/fair-buckets-learn.md
Added a changeset entry documenting the patch fix for preventing re-running hash scrolling after SSR hydration.
E2E Test Routes
e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx, e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-about.tsx
Added two test routes: /hash-scroll-repro (sections, invalidate button, hover link) and /hash-scroll-about (async loader) to reproduce/hash-scroll behavior.
Route Type Declarations
e2e/react-start/scroll-restoration/src/routeTree.gen.ts
Generated route tree updated to include the two new routes in route type unions, module augmentation (FileRoutesByPath), RootRouteChildren, and rootRouteChildren registrations.
Root Navigation
e2e/react-start/scroll-restoration/src/routes/__root.tsx
Added a navigation link to /hash-scroll-repro in header/footer link sets.
E2E Test Specification
e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts
Added Playwright tests with helpers verifying that window.scrollY remains unchanged after hover preloading and after router.invalidate() when location hasn't changed.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant SSRClient
  participant Router
  participant PreloadService

  Browser->>SSRClient: Hydrate (initial load)
  SSRClient->>Router: determine if router.load() needed
  alt skip router.load()
    SSRClient->>Router: set resolvedLocation = location.state
  end
  Note right of Router: resolvedLocation marked as resolved

  Browser->>PreloadService: Hover / Invalidate triggers
  PreloadService->>Router: check if location changed vs resolvedLocation
  alt location unchanged
    Router-->>Browser: skip hash scroll / no re-run
  else location changed
    Router->>Browser: perform navigation / hash scroll
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 I nudged the scroll and kept it still,
Hydration noted the spot, quiet and chill.
Preloads hover, invalidates try,
The page stays steady — no sudden fly.
Tests hop in to prove my will. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: preserve scroll position after SSR hash hydration' directly and accurately describes the main behavioral fix: preventing re-running hash scrolling after SSR hydration when subsequent preload or invalidate cycles complete without a location change.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-hash-scroll

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

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud bot commented Mar 28, 2026

View your CI Pipeline Execution ↗ for commit 66b7228

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 1m 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-28 18:36:43 UTC

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 28, 2026

🚀 Changeset Version Preview

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

🟩 Patch bumps

Package Version Reason
@tanstack/router-core 1.168.6 → 1.168.7 Changeset
@tanstack/react-router 1.168.7 → 1.168.8 Dependent
@tanstack/react-start 1.167.12 → 1.167.13 Dependent
@tanstack/react-start-client 1.166.22 → 1.166.23 Dependent
@tanstack/react-start-server 1.166.22 → 1.166.23 Dependent
@tanstack/router-cli 1.166.22 → 1.166.23 Dependent
@tanstack/router-generator 1.166.21 → 1.166.22 Dependent
@tanstack/router-plugin 1.167.8 → 1.167.9 Dependent
@tanstack/router-vite-plugin 1.166.23 → 1.166.24 Dependent
@tanstack/solid-router 1.168.6 → 1.168.7 Dependent
@tanstack/solid-start 1.167.11 → 1.167.12 Dependent
@tanstack/solid-start-client 1.166.20 → 1.166.21 Dependent
@tanstack/solid-start-server 1.166.20 → 1.166.21 Dependent
@tanstack/start-client-core 1.167.6 → 1.167.7 Dependent
@tanstack/start-plugin-core 1.167.13 → 1.167.14 Dependent
@tanstack/start-server-core 1.167.6 → 1.167.7 Dependent
@tanstack/start-static-server-functions 1.166.22 → 1.166.23 Dependent
@tanstack/start-storage-context 1.166.20 → 1.166.21 Dependent
@tanstack/vue-router 1.168.6 → 1.168.7 Dependent
@tanstack/vue-start 1.167.11 → 1.167.12 Dependent
@tanstack/vue-start-client 1.166.20 → 1.166.21 Dependent
@tanstack/vue-start-server 1.166.20 → 1.166.21 Dependent

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 28, 2026

Bundle Size Benchmarks

  • Commit: a556afa0f07c
  • Measured at: 2026-03-28T18:30:51.960Z
  • Baseline source: history:42c3f3b3a3a4
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.47 KiB 0 B (0.00%) 275.67 KiB 75.99 KiB ██▇▇▃▃▂▂▂▂▁
react-router.full 90.76 KiB 0 B (0.00%) 286.86 KiB 78.96 KiB ██▇▇▃▃▂▂▂▂▁
solid-router.minimal 35.51 KiB 0 B (0.00%) 107.04 KiB 31.90 KiB ██▅▅▅▅▂▂▂▂▁
solid-router.full 39.98 KiB 0 B (0.00%) 120.57 KiB 35.84 KiB ██▄▄▄▄▃▃▃▃▁
vue-router.minimal 53.36 KiB 0 B (0.00%) 152.97 KiB 47.91 KiB ████▂▂▂▂▂▂▁
vue-router.full 58.22 KiB 0 B (0.00%) 168.43 KiB 52.08 KiB ████▃▃▂▂▂▂▁
react-start.minimal 101.99 KiB +7 B (+0.01%) 323.91 KiB 88.19 KiB ██▆▆▂▂▂▂▂▂▁▁
react-start.full 105.36 KiB +4 B (+0.00%) 334.26 KiB 91.00 KiB ██▇▇▂▂▂▂▂▂▁▁
solid-start.minimal 49.61 KiB +4 B (+0.01%) 153.29 KiB 43.69 KiB ██▅▅▅▅▂▂▂▂▁▁
solid-start.full 55.10 KiB +7 B (+0.01%) 169.52 KiB 48.41 KiB ██▄▄▄▄▃▃▃▃▁▁

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 28, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 66b7228

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 the current code and only fix it if needed.

Inline comments:
In `@e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts`:
- Around line 44-50: The response listener is registered after triggering the
hover, causing a race where the preload response may be missed; move the
waitForResponse registration before calling
page.getByTestId('hash-scroll-about-link').hover() so the predicate that checks
response.url().includes('/hash-scroll-about') && response.ok() is listening
prior to the hover action; ensure you register the listener
(page.waitForResponse with the same predicate) and only then trigger
page.getByTestId('hash-scroll-about-link').hover() to avoid intermittent test
failures.
🪄 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: 07b86e88-9900-4628-bf58-b3c27f3e55ca

📥 Commits

Reviewing files that changed from the base of the PR and between a556afa and 2a64368.

📒 Files selected for processing (7)
  • .changeset/fair-buckets-learn.md
  • e2e/react-start/scroll-restoration/src/routeTree.gen.ts
  • e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-about.tsx
  • e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx
  • e2e/react-start/scroll-restoration/src/routes/__root.tsx
  • e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts
  • packages/router-core/src/ssr/ssr-client.ts

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 28, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing fix-hash-scroll (66b7228) with main (42c3f3b)1

Open in CodSpeed

Footnotes

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

@schiller-manuel schiller-manuel merged commit 6ee0e79 into main Mar 28, 2026
18 of 19 checks passed
@schiller-manuel schiller-manuel deleted the fix-hash-scroll branch March 28, 2026 18:38
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