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)