From d18ca56e4d467a3e05c253f1633d2ac0255fde05 Mon Sep 17 00:00:00 2001 From: Ishkirat-Singh Date: Thu, 10 Sep 2026 05:29:21 +0530 Subject: [PATCH] fix(router-core): leave history.scrollRestoration on the browser default setupScrollRestoration forced history.scrollRestoration to 'manual'. That setting only governs the window, and the router restores the window after paint (in the onRendered subscriber) while the browser's native restoration runs before it: forcing manual mode made iOS Safari's swipe-back preview land at the wrong offset and made Chrome paint at the top and snap down after a hard refresh. Per-element restoration and the router's own window correction are unaffected by the flag, so stop changing it. Closes #7956 --- .changeset/scroll-restoration-browser-default.md | 5 +++++ packages/router-core/src/scroll-restoration.ts | 2 -- packages/router-core/tests/scroll-restoration.test.ts | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 .changeset/scroll-restoration-browser-default.md diff --git a/.changeset/scroll-restoration-browser-default.md b/.changeset/scroll-restoration-browser-default.md new file mode 100644 index 00000000000..30f341093d4 --- /dev/null +++ b/.changeset/scroll-restoration-browser-default.md @@ -0,0 +1,5 @@ +--- +'@tanstack/router-core': patch +--- + +Stop forcing `history.scrollRestoration = 'manual'` when scroll restoration is enabled. The browser's native restoration runs before first paint, so forcing manual mode broke iOS Safari swipe-back previews and made Chrome paint at the top and snap down on hard refresh; the router's own restoration still runs afterwards and still restores individual scroll containers. diff --git a/packages/router-core/src/scroll-restoration.ts b/packages/router-core/src/scroll-restoration.ts index 5e4ae0d3ee3..e9da2eb4066 100644 --- a/packages/router-core/src/scroll-restoration.ts +++ b/packages/router-core/src/scroll-restoration.ts @@ -201,8 +201,6 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) { scroll.restoration = true ignoreScroll = false - history.scrollRestoration = 'manual' - document.addEventListener( 'scroll', (event) => { diff --git a/packages/router-core/tests/scroll-restoration.test.ts b/packages/router-core/tests/scroll-restoration.test.ts index c02a8f8783e..6facd5ff3db 100644 --- a/packages/router-core/tests/scroll-restoration.test.ts +++ b/packages/router-core/tests/scroll-restoration.test.ts @@ -70,7 +70,10 @@ describe('setupScrollRestoration', () => { expect(router._scroll.restoring).toBe(true) expect(router._scroll.restoration).toBe(true) - expect(window.history.scrollRestoration).toBe('manual') + // The browser's own restoration stays on: it handles the window before + // first paint (Safari swipe-back previews, hard refresh); the router only + // corrects afterwards and restores individual scroll containers. + expect(window.history.scrollRestoration).toBe('auto') expect( windowAddEventListener.mock.calls.some(([event]) => event === 'pagehide'), ).toBe(true)