vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.61k stars 26.82k forks source link

Scroll restoration triggers before navigation after revalidation #70716

Closed JeremieLeblanc closed 1 week ago

JeremieLeblanc commented 1 week ago

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/revalidate-scroll-restoration-n9ry7r

To Reproduce

Start the application in a pop up window (Scroll restoration seems to be broken in the iframe) Scroll to the bottom of the Home page and navigate to "Page 2" Click on the "Back" button either by using the browser or the button on the page You will be sent back to the Home page at the correct scroll position.

Now if you go back to Page 2 Click on "Revalidate" to revalidate the Home page Click on Back

You will notice that the scroll restoration will happen before the page revalidates and navigates to the Home page. The new scroll position of the Home page will be at the height of Page 2 because the scroll position got overridden by the second page.

Current vs. Expected behavior

I would expect the scroll restoration to wait for the navigation to have completed before being set.

Provide environment information

Next 15
React 19

Which area(s) are affected? (Select all that apply)

Navigation

Which stage(s) are affected? (Select all that apply)

next dev (local), next build (local), next start (local), Vercel (Deployed), Other (Deployed)

Additional context

No response

ztanner commented 1 week ago

Hi @JeremieLeblanc,

Next.js app router doesn't do anything special to restore the scroll position when clicking the back button (or router.back()). This is handled by the browser.

Browsers restore scroll position right after the popstate event. When you revalidate the previous page, you're clearing the router cache, so it will have to fetch from the server the latest component information. This means that when the browser tries to synchronously read the scroll position, it will get the current position rather than the position after the data is refetched from the server, as we're not able to instantly restore the previous UI. By design, React will not switch to the previous UI until the pending transitions have finished (in this case, the missing RSC data), to prevent CLS. And we can't tell the browser to wait to decide where to snapshot the scroll position.

You will likely need to implement custom scroll restoration behavior in userland if this isn't a desired behavior. Closing this as I don't believe it's currently actionable as a bug report.