Skip to content

fix(router-core): handle window and element scroll restoration independently - #7807

Merged
schiller-manuel merged 1 commit into
mainfrom
fix-7687
Jul 13, 2026
Merged

fix(router-core): handle window and element scroll restoration independently#7807
schiller-manuel merged 1 commit into
mainfrom
fix-7687

Conversation

@schiller-manuel

@schiller-manuelschiller-manuel commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Window and element scroll targets are now handled independently. Restoring one target no longer suppresses resets for other uncached configured targets, and a restored element is no longer reset when the window has no cached position.

Hash navigation no longer resets elements configured through scrollToTopSelectors and retains precedence over stale window positions through destination invalidations.

Scroll positions are sampled when leaving a route, preserving live changes made after the most recent scroll event. This also prevents client hydration from undoing nested positions restored by the SSR script.

Fixes#7687.

Summary by CodeRabbit

  • Bug Fixes

    • Improved scroll restoration so window and nested element positions restore independently.
    • Preserved live scroll positions when navigating and prevented hydration from overwriting restored nested positions.
    • Refined hash navigation and scroll-to-top behavior, including stale or uncached targets.
    • Corrected restoration behavior when navigation disables scroll resets.
  • Tests

    • Expanded coverage for nested scrolling, hash navigation, SSR hydration, browser back navigation, and scroll restoration keys.

…ndently
Window and element scroll targets are now handled independently. Restoring one target no longer suppresses resets for other uncached configured targets, and a restored element is no longer reset when the window has no cached position.
Hash navigation no longer resets elements configured through `scrollToTopSelectors` and retains precedence over stale window positions through destination invalidations.
Scroll positions are sampled when leaving a route, preserving live changes made after the most recent scroll event. This also prevents client hydration from undoing nested positions restored by the SSR script.
Fixes#7687.
@nx-cloud

nx-cloudBot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit b18d5ba

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...❌ Failed14m 25sView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 16sView ↗

☁️ Nx Cloud last updated this comment at 2026-07-13 18:00:45 UTC

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

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

🟩 Patch bumps

PackageVersionReason
@tanstack/router-core1.171.14 → 1.171.15Changeset
@tanstack/react-router1.170.17 → 1.170.18Dependent
@tanstack/react-start1.168.27 → 1.168.28Dependent
@tanstack/react-start-client1.168.15 → 1.168.16Dependent
@tanstack/react-start-rsc0.1.26 → 0.1.27Dependent
@tanstack/react-start-server1.167.21 → 1.167.22Dependent
@tanstack/router-cli1.167.18 → 1.167.19Dependent
@tanstack/router-generator1.167.18 → 1.167.19Dependent
@tanstack/router-plugin1.168.19 → 1.168.20Dependent
@tanstack/router-vite-plugin1.167.19 → 1.167.20Dependent
@tanstack/solid-router1.170.17 → 1.170.18Dependent
@tanstack/solid-start1.168.27 → 1.168.28Dependent
@tanstack/solid-start-client1.168.15 → 1.168.16Dependent
@tanstack/solid-start-server1.167.21 → 1.167.22Dependent
@tanstack/start-client-core1.170.13 → 1.170.14Dependent
@tanstack/start-plugin-core1.171.19 → 1.171.20Dependent
@tanstack/start-server-core1.169.16 → 1.169.17Dependent
@tanstack/start-static-server-functions1.167.18 → 1.167.19Dependent
@tanstack/start-storage-context1.167.16 → 1.167.17Dependent
@tanstack/vue-router1.170.16 → 1.170.17Dependent
@tanstack/vue-start1.168.26 → 1.168.27Dependent
@tanstack/vue-start-client1.167.18 → 1.167.19Dependent
@tanstack/vue-start-server1.167.21 → 1.167.22Dependent

@coderabbitai

coderabbitaiBot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Scroll restoration behavior

Layer / File(s)Summary
Core scroll tracking and restoration
packages/router-core/src/router.ts, packages/router-core/src/scroll-restoration.ts, .changeset/*
Scroll targets are tracked independently, positions are sampled before navigation, hash state is recorded directly, and restored elements are excluded from fallback resets.
Core restoration contract tests
packages/router-core/tests/scroll-restoration.test.ts
Tests cover live position snapshots, independent element restoration, stale selectors, uncached targets, and hash navigation.
Hash navigation scenarios
e2e/react-start/scroll-restoration/src/router.tsx, e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-*.tsx, e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts
Hash keys, invalidation, configured target preservation, and reset behavior across restoration keys are exercised.
Issue 7687 route and regression coverage
e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687*, e2e/react-start/scroll-restoration/src/routeTree.gen.ts, e2e/react-start/scroll-restoration/tests/issue-7687.spec.ts
New nested-scroll routes and Playwright scenarios cover independent restoration, stale selectors, browser back, and resetScroll={false}.
SSR nested target preservation
e2e/react-start/scroll-restoration/src/routes/(tests)/ssr-scroll-key.tsx, e2e/react-start/scroll-restoration/tests/ssr-scroll-key.spec.ts
SSR-restored nested positions are tested before and after client hydration.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
participant Router
participant ScrollCache
participant Window
participant NestedTarget
Router->>ScrollCache: snapshot tracked positions
Router->>ScrollCache: load cached positions
ScrollCache->>Window: restore window position
ScrollCache->>NestedTarget: restore element position
Router->>NestedTarget: reset only uncached targets
Loading

Possibly related PRs

  • TanStack/router#7447: Updates the same router-core scroll-restoration implementation and related React-start coverage.
  • TanStack/router#7464: Changes hash navigation and reset-scroll handling in the same scroll-restoration code.

Suggested labels:package: react-start

Suggested reviewers:birkskyum, sheraff, jong-kyung, naoya7076, nlynzaad

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 7.69% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely summarizes the main change: independent window and element scroll restoration in router-core.
Linked Issues check✅ PassedThe fix and added tests address #7687 by restoring element targets without letting scroll-to-top reset them.
Out of Scope Changes check✅ PassedThe added routes and tests stay within scroll-restoration work and support the reported regression; no unrelated changes stand out.
✨ 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-7687

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

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: 4dc74a262019
  • Measured at: 2026-07-13T17:47:20.453Z
  • Baseline source: history:a3e24c35a58d
  • Dashboard: bundle-size history
ScenarioCurrent (gzip)Delta vs baselineInitial gzipRawBrotliTrend
react-router.minimal87.35 KiB-26 B (-0.03%)87.21 KiB273.70 KiB76.01 KiB▁▄▄▄▄██████▆
react-router.full91.06 KiB-42 B (-0.05%)90.92 KiB285.59 KiB79.19 KiB▁▅▅▅▅██████▅
solid-router.minimal35.53 KiB-38 B (-0.10%)35.40 KiB105.90 KiB31.94 KiB▁▅▅▅▅██████▅
solid-router.full40.58 KiB-31 B (-0.07%)40.46 KiB121.11 KiB36.51 KiB▁▆▆▆▆██████▅
vue-router.minimal53.00 KiB-69 B (-0.13%)52.87 KiB149.94 KiB47.63 KiB▁▄▄▄▄██████▃
vue-router.full58.94 KiB-99 B (-0.16%)58.81 KiB168.70 KiB52.76 KiB▂▅▅▅▅██████▁
react-start.minimal101.99 KiB-41 B (-0.04%)101.85 KiB322.13 KiB88.23 KiB▁▃▃▃▆██████▆
react-start.deferred-hydration102.72 KiB-40 B (-0.04%)101.87 KiB323.50 KiB88.88 KiB▁▄▄▄▆██████▅
react-start.full105.36 KiB-87 B (-0.08%)105.23 KiB332.06 KiB91.12 KiB▁▃▃▃▅██████▄
react-start.rsbuild.minimal99.68 KiB-64 B (-0.06%)99.51 KiB316.57 KiB85.74 KiB▁▃▂▂▅██████▄
react-start.rsbuild.minimal-iife100.08 KiB-67 B (-0.07%)99.91 KiB317.51 KiB86.11 KiB▁▃▂▂▅██████▄
react-start.rsbuild.full102.89 KiB-70 B (-0.07%)102.72 KiB326.63 KiB88.52 KiB▁▃▃▃▅██████▃
solid-start.minimal49.61 KiB-104 B (-0.20%)49.48 KiB152.07 KiB43.83 KiB▁▃▃▃▆██████▂
solid-start.deferred-hydration52.87 KiB-106 B (-0.20%)49.53 KiB160.11 KiB46.89 KiB▁▃▃▃▅██████▁
solid-start.full55.42 KiB-95 B (-0.17%)55.29 KiB169.12 KiB48.90 KiB▁▄▄▄▆██████▂
vue-start.minimal71.03 KiB-104 B (-0.14%)70.90 KiB207.19 KiB62.89 KiB▁▄▄▄▇██████▂
vue-start.full75.03 KiB-101 B (-0.13%)74.90 KiB219.83 KiB66.37 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.

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-rsc

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: b18d5ba

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will degrade performance by 10.76%

⚡ 3 improved benchmarks
❌ 3 regressed benchmarks
✅ 174 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem server error-paths redirect (vue)304.6 KB591.3 KB-48.48%
Memorymem server error-paths unmatched (react)606.9 KB794.7 KB-23.63%
Memorymem server error-paths not-found (react)262 KB277.4 KB-5.55%
Memorymem server streaming-peak chunked (vue)13.9 MB11.1 MB+25.43%
Memorymem server server-fn-churn (react)283 KB270.9 KB+4.46%
Simulationclient-async-pipeline navigation loop (react)51.7 ms49.8 ms+3.78%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix-7687 (b18d5ba) with main (a3e24c3)1

Open in CodSpeed

Footnotes

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

@nx-cloudnx-cloudBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nx Cloud has identified a possible root cause for your failed CI:

We investigated both CI failures and determined neither is caused by the PR's changes. The tanstack-react-start-e2e-import-protection:test:e2e failure is a Chromium SIGSEGV browser crash in the CI environment, and the tanstack-start-example-rscs:build failure is a pre-existing nitro package export compatibility issue confirmed to also occur on unrelated branches.

No code changes were suggested for this issue.

Trigger a rerun:

Rerun CI

Nx Cloud View detailed reasoning on Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
e2e/react-start/scroll-restoration/tests/issue-7687.spec.ts (1)

4-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid waitForLoadState('networkidle').

Playwright's own docs mark 'networkidle' as discouraged since background/analytics requests can keep the network "busy" indefinitely, causing flaky or slow tests; prefer relying on the subsequent toBeVisible() web-first assertion alone.

♻️ Proposed simplification
 async function goToList(page: Page) {
await page.goto('/issue-7687')
- await page.waitForLoadState('networkidle')
await expect(
page.getByRole('heading', { name: 'issue-7687-list' }),
).toBeVisible()
}
🤖 Prompt for 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.
In `@e2e/react-start/scroll-restoration/tests/issue-7687.spec.ts` around lines 4 -
10, Remove the waitForLoadState('networkidle') call from goToList and rely on
the existing heading toBeVisible() assertion to wait for the page readiness.
🤖 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 `@e2e/react-start/scroll-restoration/src/router.tsx`:
- Around line 10-21: Update the scrollToTopSelectors entry for the nested hash
scroller in the router configuration to use that element’s data-attribute
selector instead of the ineffective ID selector, preserving its participation in
reset-to-top and cross-key carry-over handling.
In `@e2e/react-start/scroll-restoration/src/routes/`(tests)/hash-scroll-repro.tsx:
- Around line 26-35: Handle rejection from router.invalidate() within the
React.useLayoutEffect callback by adding a catch path to the existing promise
chain, preventing failed invalidations from becoming unhandled promise
rejections while preserving the success-only setInvalidateCount update.
---
Nitpick comments:
In `@e2e/react-start/scroll-restoration/tests/issue-7687.spec.ts`:
- Around line 4-10: Remove the waitForLoadState('networkidle') call from
goToList and rely on the existing heading toBeVisible() assertion to wait for
the page readiness.
🪄 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: 32d7ccab-0d1b-4628-82c8-d7b27eef8871

📥 Commits

Reviewing files that changed from the base of the PR and between 41f7bf3 and b18d5ba.

📒 Files selected for processing (15)
  • .changeset/fix-independent-scroll-targets.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-about.tsx
  • e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx
  • e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.detail.tsx
  • e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.index.tsx
  • e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.tsx
  • e2e/react-start/scroll-restoration/src/routes/(tests)/ssr-scroll-key.tsx
  • e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts
  • e2e/react-start/scroll-restoration/tests/issue-7687.spec.ts
  • e2e/react-start/scroll-restoration/tests/ssr-scroll-key.spec.ts
  • packages/router-core/src/router.ts
  • packages/router-core/src/scroll-restoration.ts
  • packages/router-core/tests/scroll-restoration.test.ts

Comment on lines +10 to +21
scrollToTopSelectors: [
'[data-scroll-restoration-id="carry-over-reset"]',
'#hash-scroll-nested',
'#hash-scroll-reset-target',
'#issue-7687-reset-probe',
'#issue-7687-scroller',
() =>
typeof document === 'undefined'
? undefined
: document.getElementById('issue-7687-stale-selector'),
'#ssr-scroll-key-nested',
],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Confirm no element anywhere defines id="hash-scroll-nested", and inspect getScrollToTopElements/getScrollRestorationSelector matching logic.
rg -n 'id="hash-scroll-nested"' e2e/react-start/scroll-restoration
rg -n 'getScrollToTopElements|getScrollRestorationSelector' packages/router-core/src/scroll-restoration.ts -A5 -B2

Repository: TanStack/router

Length of output: 2021


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== router.tsx ==\n'
sed -n '1,80p' e2e/react-start/scroll-restoration/src/router.tsx
printf'\n== hash-scroll-repro.tsx (around nested target) ==\n'
sed -n '80,130p' e2e/react-start/scroll-restoration/src/routes/'(tests)'/hash-scroll-repro.tsx
printf'\n== scroll-restoration.ts (selector logic) ==\n'
sed -n '1,360p' packages/router-core/src/scroll-restoration.ts

Repository: TanStack/router

Length of output: 13404


Use the data attribute selector for hash-scroll-nested.
scrollToTopSelectors are resolved with document.querySelector(), so #hash-scroll-nested never matches this element. That leaves the nested scroller out of both the reset-to-top path and the cross-key carry-over exclusion.

🐛 Proposed fix
- '`#hash-scroll-nested`',+ '[data-scroll-restoration-id="hash-scroll-nested"]',
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
scrollToTopSelectors: [
'[data-scroll-restoration-id="carry-over-reset"]',
'#hash-scroll-nested',
'#hash-scroll-reset-target',
'#issue-7687-reset-probe',
'#issue-7687-scroller',
()=>
typeofdocument==='undefined'
? undefined
: document.getElementById('issue-7687-stale-selector'),
'#ssr-scroll-key-nested',
],
scrollToTopSelectors: [
'[data-scroll-restoration-id="carry-over-reset"]',
'[data-scroll-restoration-id="hash-scroll-nested"]',
'`#hash-scroll-reset-target`',
'`#issue-7687-reset-probe`',
'`#issue-7687-scroller`',
()=>
typeofdocument==='undefined'
? undefined
: document.getElementById('issue-7687-stale-selector'),
'`#ssr-scroll-key-nested`',
],
🤖 Prompt for 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.
In `@e2e/react-start/scroll-restoration/src/router.tsx` around lines 10 - 21,
Update the scrollToTopSelectors entry for the nested hash scroller in the router
configuration to use that element’s data-attribute selector instead of the
ineffective ID selector, preserving its participation in reset-to-top and
cross-key carry-over handling.

Comment on lines +26 to +35
React.useLayoutEffect(() => {
if (!invalidateOnMount || invalidatedOnMount.current) {
return
}

invalidatedOnMount.current = true
void router.invalidate().then(() => {
setInvalidateCount((count) => count + 1)
})
}, [invalidateOnMount, router])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unhandled rejection if router.invalidate() fails.

void router.invalidate().then(...) has no .catch; a rejected invalidation becomes an unhandled promise rejection, which can produce noisy console errors or flaky Playwright failures if the test harness asserts on console output.

🛠️ Proposed fix
 invalidatedOnMount.current = true
- void router.invalidate().then(() => {- setInvalidateCount((count) => count + 1)- })+ void router.invalidate()+ .then(() => {+ setInvalidateCount((count) => count + 1)+ })+ .catch(() => {})
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
React.useLayoutEffect(()=>{
if(!invalidateOnMount||invalidatedOnMount.current){
return
}
invalidatedOnMount.current=true
voidrouter.invalidate().then(()=>{
setInvalidateCount((count)=>count+1)
})
},[invalidateOnMount,router])
React.useLayoutEffect(()=>{
if(!invalidateOnMount||invalidatedOnMount.current){
return
}
invalidatedOnMount.current=true
voidrouter.invalidate()
.then(()=>{
setInvalidateCount((count)=>count+1)
})
.catch(()=>{})
},[invalidateOnMount,router])
🤖 Prompt for 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.
In `@e2e/react-start/scroll-restoration/src/routes/`(tests)/hash-scroll-repro.tsx
around lines 26 - 35, Handle rejection from router.invalidate() within the
React.useLayoutEffect callback by adding a catch path to the existing promise
chain, preventing failed invalidations from becoming unhandled promise
rejections while preserving the success-only setInvalidateCount update.

@schiller-manuel
schiller-manuel merged commit e2dd204 into mainJul 13, 2026
24 of 26 checks passed
@schiller-manuel
schiller-manuel deleted the fix-7687 branch July 13, 2026 19:45
@github-actionsgithub-actionsBot mentioned this pull request Jul 13, 2026
Sign up for freeto 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.

Element scroll restoration is reset to top by scrollToTopSelectors fallback when the scroll container is an element

1 participant

@schiller-manuel