[NEXT-1147] Scroll position is reset when search params are updated
Problem
Verify canary release - [X] I verified that the issue exists in the latest Next.js canary release Provide environment information [code block] Link to the code that reproduces this issue https://codesandbox.io/p/sandbox/modest-gould-h4nlvd?file=%2Fapp%2Fpage.tsx&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A7%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A7%7D%5D To Reproduce Click the counter to update client state. Scroll a little. Click the second button to update search params. The client state is preserved, but the scroll position is lost. Describe the Bug Context In Next <= 13.2.4, updating search params was working as intended : client state was kept, and scroll position was kept too. In Next 13.2.5, a regression made the client components unmount and remount when search params were updated. @feedthejim fixed that unmounting in https://github.com/vercel/next.js/pull/49047 (it's testable on `13.3.5-canary.2`), but there is still an issue now: scroll position is lost on search params updates. Problem: persisting state in search params is very important with the App router - that's a clean way for client components to request updated data from the RSC. Reproduction Codesandbox Here's a codesandbox reproducing the bug: https://codesandbox.io/p/sandbox/modest-gould-h4nlvd?file=%2Fapp%2Fpage.tsx&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A7%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A7%7D%5D In video https://user-
Unverified for your environment
Select your OS to check compatibility.
1 Fix
Persist Scroll Position on Search Params Update in Next.js
In Next.js versions 13.2.5 and later, updating search parameters causes a remount of client components, which results in the loss of scroll position. This behavior is due to the way the App Router handles state and component lifecycle during URL updates.
Awaiting Verification
Be the first to verify this fix
- 1
Store Scroll Position
Before updating the search parameters, capture the current scroll position and store it in a state variable. This allows you to restore the scroll position after the component remounts.
typescriptconst [scrollPos, setScrollPos] = useState(0); const handleUpdateSearchParams = () => { setScrollPos(window.scrollY); // Update search params logic here }; - 2
Restore Scroll Position
After the component has remounted, use the stored scroll position to scroll back to the previous position. This can be done using the useEffect hook to trigger the scroll restoration after the component updates.
typescriptuseEffect(() => { window.scrollTo(0, scrollPos); }, [scrollPos]); - 3
Debounce Scroll Position Capture
To improve performance and avoid excessive state updates, debounce the scroll position capture. This can be achieved using a simple debounce function that limits how often the scroll position is recorded.
typescriptconst debounce = (func, delay) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), delay); }; }; const handleScroll = debounce(() => setScrollPos(window.scrollY), 100); useEffect(() => { window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, []); - 4
Test Scroll Persistence
After implementing the above changes, test the application by updating the search parameters and verifying that the scroll position is maintained. Ensure that both the client state and scroll position persist across updates.
none// No specific code needed, just manual testing required.
Validation
Confirm that after updating the search parameters, the scroll position remains consistent and does not reset. Perform multiple updates to ensure the behavior is stable across different interactions.
Sign in to verify this fix
Environment
Submitted by
Alex Chen
2450 rep