Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opt out of scroll restoration when using browser back buttons #20951

Open
controversial opened this issue Jan 10, 2021 · 37 comments
Open

Opt out of scroll restoration when using browser back buttons #20951

controversial opened this issue Jan 10, 2021 · 37 comments
Assignees
Labels
Navigation Related to Next.js linking (e.g., <Link>) and navigation.

Comments

@controversial
Copy link

Describe the feature you'd like to request

There is a clear way to opt out of Next.js scroll restoration when using Link or router.push: just pass { scroll: false }. However, I haven't found a clear way to opt out of scroll restoration when using the browser back/forward buttons.

I spent a little while reading the source of the popState handler, and it seems like the router will always jump either to the scroll position in sessionStorage or to the top of the page.

Describe the solution you'd like

Ideally, there would be some kind of router configuration option by which an app could opt out of the router messing with scroll position completely.

Describe alternatives you've considered

I've considered trying to mess with the scroll value stored in sessionStorage during onPopState, but this seems like a really convoluted workaround that might not hold up in future Next.js releases.

@controversial
Copy link
Author

I’ve found a pretty good solution in the following:

router.beforePopState((state) => {
  state.options.scroll = false;
  return true;
});

@Timer
Copy link
Member

Timer commented Jan 11, 2021

Next.js should not reset the scroll position to 0,0 on back or forwards navigation. Could you please share a reproducible demo for this behavior? We have tests covering this in Chrome.

@Timer Timer added please add a complete reproduction The issue lacks information for further investigation and removed template: story labels Jan 11, 2021
controversial added a commit to controversial/20951-repro that referenced this issue Jan 11, 2021
@controversial
Copy link
Author

controversial commented Jan 11, 2021

Sure, @Timer:

I created a repository showing the behavior by the following steps:

  1. created a new app with create-next-app
  2. I duplicated the example page to create two pages, A and B, with a link between them, and duplicated some of the content to let the pages scroll
  3. I added a useEffect hook to set window.history.scrollRestoration = 'manual', to ensure that the browser is not messing with scroll position at all.

For debugging purposes, I also went into the local node_modules and made the following modification inside this block of next/client/index.tsx, though that change is not captured in the repository. This message indicates when it is Next.js initiating the scroll up to the top of the page, rather than the browser or another piece of code.

  if (input.scroll) {
+   console.log('Scrolling to', input.scroll.x, input.scroll.y);
    window.scrollTo(input.scroll.x,input.scroll.y);
  }

Now, I can navigate from page A to page B, and observe that when I press the browser “back” button, a message is printed to the console indicating that Next.js is scrolling the page to the top. Here’s a video:

20951-repro-video.mov

You can see that the console message is printed, indicating that Next.js initiates a scroll to (0, 0).

@Timer
Copy link
Member

Timer commented Jan 12, 2021

The behavior you just displayed in video was correct, though.

The scroll should be restored to its location on Page A (the top). I agree the console log is weird, but what happens if you scroll Page A before going to B, and then back to A?

I can't reproduce this issue in a normal app (console.log aside, the scroll doesn't appear to be taking affect even though it's called).

@Timer Timer added this to the iteration 16 milestone Jan 12, 2021
@controversial
Copy link
Author

Here's the alternate test case you described. I:

  1. scroll down on page A
  2. move to page B
  3. press “back” to page A

As you can see, the page still jumps to the top of the page, and the scrollTo code in client/index.jsx still executes with (0, 0). Here's a video showing that:

Screen.Recording.2021-01-12.at.11.37.48.AM.mp4

I'm not sure that even the first behavior I showed was correct, though — given that I set history.scrollRestoration = 'manual' and your comment that “Next.js should not reset the scroll position to 0,0 on back or forwards navigation,” I wouldn't expect that the scroll position would change at all when clicking the back button. What am I missing?

@Timer
Copy link
Member

Timer commented Jan 12, 2021

Oh, this seems to be specific to window.history.scrollRestoration = 'manual' being configured as manual and not auto.

I can fix this.

@Timer Timer added kind: bug and removed kind: story please add a complete reproduction The issue lacks information for further investigation labels Jan 12, 2021
@spaghettiguru
Copy link

We are having a problem with this behavior too. @Timer, do you have any planned date when this issue is expected to be fixed? It would help us a lot.

@bayraak
Copy link

bayraak commented Feb 26, 2021

This issue blocking me too. There is no way to disable this unnecessary behavior for now.

@bayraak
Copy link

bayraak commented Feb 26, 2021

I can fix this.

I've tried this but it didn't work.

@lipoolock
Copy link

Hi @Timer

Can we use

module.exports = {
  experimental: {
    scrollRestoration: true
  }
}

as a workaround while this issue is not officially solved ?

@timneutkens timneutkens removed this from the Iteration 19 milestone May 3, 2021
@timneutkens timneutkens removed this from the 12.0.5 milestone Nov 17, 2021
@pwfisher
Copy link

pwfisher commented Dec 6, 2021

To override both Next.js and native scrollRestoration, try useScrollRestoration by @claus:

@timfuhrmann
Copy link

A combination of both beforePopState and window.history.scrollRestoration = 'manual' does the trick for me:

export const Transition: React.FC = ({ children }) => {
    const router = useRouter();

    useEffect(() => {
        router.beforePopState(state => {
            state.options.scroll = false;
            return true;
        });
    }, []);

    return (
         <Script>{`window.history.scrollRestoration = "manual"`}</Script>
        ...
    );
};

No scroll at all when navigation back or forth.

@zachpinfold
Copy link

To override both Next.js and native scrollRestoration, try useScrollRestoration by @claus:

This works for me!

@LarssonHenrik
Copy link

To override both Next.js and native scrollRestoration, try useScrollRestoration by @claus: https://gist.github.com/claus/992a5596d6532ac91b24abe24e10ae81

This worked well for me as well!

@batazo

This comment was marked as spam.

@aroyan

This comment was marked as off-topic.

@BiosBoy
Copy link

BiosBoy commented Jul 20, 2022

Worked fine for me:

You can enable it in your next.config.js file this way:

module.exports = {
experimental: {
scrollRestoration: true,
},
};

@nhannah
Copy link

nhannah commented Aug 30, 2022

There is an issue with all of these solutions that I am not sure has been noticed. On iOS WebKit there is a check that a snapshot should only be shown for a back or forward action if shouldRestoreScrollPosition || (currentScrollPosition == snapshot->viewScrollPosition()), shouldRestoreScrollPosition is equivalent to history.scrollRestoration = "auto". Setting history.scrollRestoration = "manual" means the current page must be scrolled to the same position as the page being transitioned to (forward or back) in order for the UI to look "normal" when navigating.

Here is an example on the Target website that uses Next:

target.mov

Notice if both screens are scrolled to top the snapshot is shown, but Target has some form of scroll restoration it seems so the manual setting screws up the page.

The only solution I found so far for my own project is to check for webkit and not set scrollRestoration to manual in that case, as window.history.scrollRestoration = 'auto' doesn't work for my project anyway this seems to give the desired result.

 if (navigator.userAgent.indexOf('AppleWebKit') != -1) {
      window.history.scrollRestoration = 'auto';
    } else {
      window.history.scrollRestoration = 'manual';
    }

@mosesoak
Copy link

@nhannah Thanks for noticing that iOS safari issue! Hopefully the Next authors will take it into account if they ever address this glaring problem. Gatsby has excellent built-in scroll restoration so we've been quite surprised to have to patch it. Users have been posting about it since 2017 🤷

Your sniff is a little too broad though, gets picked up by Firefox. I used,

      // Manual doesn't work well on iOS Safari https://github.com/vercel/next.js/issues/20951#issuecomment-1231966865
      const ua = window.navigator.userAgent.toLowerCase();
      const isMobileSafari = /safari/.test(ua) && /iphone|ipod|ipad/.test(ua);
      window.history.scrollRestoration = isMobileSafari ? 'auto' : 'manual';

I updated the hook to include that and some other niceties at the main gist here: https://gist.github.com/claus/992a5596d6532ac91b24abe24e10ae81?permalink_comment_id=4301903#gistcomment-4301903

@mosesoak
Copy link

@tomiviljanen CC @ijjk hi I tried the experimental scroll restoration and it worked pretty well.

It has a few outstanding issues though:

  1. scroll doesn't restore on page refresh
  2. when going back, if your page has a number of react components, restore happens too fast and it will end up jumping to the bottom of the page (I added a 1ms timeout workaround in my version of the community hook)
  3. scroll-behavior: smooth doesn't work well in Next. You jump to the wrong scroll location on new pages a la this post Scroll restoration happens too early before the page gets rendered after hitting browser back button #3303 (comment) and when going back, instead of the bottom of the page I get an incorrect position that's slightly higher on the page.

I know there are a number of other open issues out there on all of this but just wanted to list these out clearly. Seems that the experimental feature isn't super close to production-ready yet.

When testing the feature, I'd suggest using a full web page with a good amount of content on it, since it seems like React's hydration lifecycle interacts with this and there are little race conditions happening. (I don't mean this as a dig at all, but you might also peek at how Gatsby does theirs as it is quite solid.)

@IshmamR
Copy link

IshmamR commented Sep 16, 2022

A combination of both beforePopState and window.history.scrollRestoration = 'manual' does the trick for me:

export const Transition: React.FC = ({ children }) => {
    const router = useRouter();

    useEffect(() => {
        router.beforePopState(state => {
            state.options.scroll = false;
            return true;
        });
    }, []);

    return (
         <Script>{`window.history.scrollRestoration = "manual"`}</Script>
        ...
    );
};

No scroll at all when navigation back or forth.

Working nicely for me

@rhigdon
Copy link

rhigdon commented Dec 8, 2022

For me it was just the window history scroll restoration

window.history.scrollRestoration = "manual";

Here is a good article I found https://developer.chrome.com/blog/history-api-scroll-restoration/

@kylealwyn
Copy link

Dropping a note that experimental.scrollRestoration not working with react-query - had to opt for community hook

@ifahs1
Copy link

ifahs1 commented Apr 15, 2023

Describe the feature you'd like to request

There is a clear way to opt out of Next.js scroll restoration when using Link or router.push: just pass { scroll: false }. However, I haven't found a clear way to opt out of scroll restoration when using the browser back/forward buttons.

I spent a little while reading the source of the popState handler, and it seems like the router will always jump either to the scroll position in sessionStorage or to the top of the page.

Describe the solution you'd like

Ideally, there would be some kind of router configuration option by which an app could opt out of the router messing with scroll position completely.

Describe alternatives you've considered

I've considered trying to mess with the scroll value stored in sessionStorage during onPopState, but this seems like a really convoluted workaround that might not hold up in future Next.js releases.

Just add this in nextjsconfig
Tested and working 👍

module.exports = {
experimental: {
scrollRestoration: true,
},
};

@gregg-cbs
Copy link

gregg-cbs commented Apr 26, 2023

I am using "next": "13.1.6" and i can confirm setting scrollRestoration: true solves the page position issue when going to a previously visited page using browser back and forward buttons. The restoration does not happen when using nexts Link to go to a page that was previously visited which is expected.

module.exports = { experimental: { scrollRestoration: true, }, };

@sahanxdissanayake
Copy link

@samcx
Copy link
Member

samcx commented Feb 22, 2024

Hi everyone!

I will be closing this issue since this is resolved with scroll: false.

Feel free to submit a new bug report if you are still experiencing scroll issues!

@samcx samcx closed this as completed Feb 22, 2024
@controversial
Copy link
Author

@samcx This issue is explicitly about controlling the behavior when not using Link, as the original issue clearly describes.

The original issue notes the existence of the scroll prop on Link, and describes why scroll: false does not resolve this issue, since this issue is not about the Link component.

This issue is unresolved and should be reopened.

@samcx samcx reopened this Feb 22, 2024
@samcx
Copy link
Member

samcx commented Feb 27, 2024

@controversial Thank you for the clarification—my apologies for not reading the issue correctly.

It might be possible for us to have this experimental API (which only works with Pages Router)

experimental: {
  scrollRestoration: false
 }

to add window.history.scrollRestoration = "manual"; — let me infer with the team to see if that's possible.

@rhd-developers
Copy link

@samcx This works in Next 13 dev mode, but in production in standalone mode it doesn't work. Any ideas?

@samcx
Copy link
Member

samcx commented Mar 15, 2024

@rhd-developers Are you using Pages Router? If yes, can you provide us a simple :repro: that showcases your issue?

@samcx samcx removed the kind: bug label Apr 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Navigation Related to Next.js linking (e.g., <Link>) and navigation.
Projects
None yet
Development

Successfully merging a pull request may close this issue.