Skip to content

Instantly share code, notes, and snippets.

@claus
Created May 14, 2020 05:35
Show Gist options
  • Star 95 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save claus/992a5596d6532ac91b24abe24e10ae81 to your computer and use it in GitHub Desktop.
Save claus/992a5596d6532ac91b24abe24e10ae81 to your computer and use it in GitHub Desktop.
Restore scroll position after navigating via browser back/forward buttons in Next.js
import useScrollRestoration from "utils/hooks/useScrollRestoration";
const App = ({ Component, pageProps, router }) => {
useScrollRestoration(router);
return <Component {...pageProps} />;
};
export default App;
import { useEffect } from 'react';
import Router from 'next/router';
function saveScrollPos(url) {
const scrollPos = { x: window.scrollX, y: window.scrollY };
sessionStorage.setItem(url, JSON.stringify(scrollPos));
}
function restoreScrollPos(url) {
const scrollPos = JSON.parse(sessionStorage.getItem(url));
if (scrollPos) {
window.scrollTo(scrollPos.x, scrollPos.y);
}
}
export default function useScrollRestoration(router) {
useEffect(() => {
if ('scrollRestoration' in window.history) {
let shouldScrollRestore = false;
window.history.scrollRestoration = 'manual';
restoreScrollPos(router.asPath);
const onBeforeUnload = event => {
saveScrollPos(router.asPath);
delete event['returnValue'];
};
const onRouteChangeStart = () => {
saveScrollPos(router.asPath);
};
const onRouteChangeComplete = url => {
if (shouldScrollRestore) {
shouldScrollRestore = false;
restoreScrollPos(url);
}
};
window.addEventListener('beforeunload', onBeforeUnload);
Router.events.on('routeChangeStart', onRouteChangeStart);
Router.events.on('routeChangeComplete', onRouteChangeComplete);
Router.beforePopState(() => {
shouldScrollRestore = true;
return true;
});
return () => {
window.removeEventListener('beforeunload', onBeforeUnload);
Router.events.off('routeChangeStart', onRouteChangeStart);
Router.events.off('routeChangeComplete', onRouteChangeComplete);
Router.beforePopState(() => true);
};
}
}, [router]);
}
@michaeljscript
Copy link

michaeljscript commented Apr 27, 2023

I've experienced a few issues with the suggested solutions, so I've built this library https://www.npmjs.com/package/next-restore-scroll-position which fixes the following.

  1. Fixes a bug where the position was restored after page refresh, as this was not the desired behavior
  2. Fixes a bug where the position was not restored because useEffect was called multiple times (my repo uses useRef instead of local variable shouldScrollRestore to solve the issue. useRef is also better than useState because useRef does not rerender components after an update)
  3. Removes the scroll position from storage after the position is restored to stop a memory leak

Example

yarn add next-restore-scroll-position
import { useScrollRestoration } from 'next-restore-scroll-position';

function App() { // This needs to be NextJS App Component
    const router = useRouter();
    useScrollRestoration(router);
}

👍 ✅

@joshua-isaac
Copy link

this is great, thanks!

@PranoySarker
Copy link

It works fine for me
Thank you for this resource

@shawnCaza
Copy link

The hook version from @MichalSzorad seems to work fairly nicely.

I'm not sure if there's a possible solution, that would be worth the complexity, but one scenario I don't see addressed is as follows:

  1. Scroll down page A click on link to page B
  2. On page B click on a link that goes back to page A
  3. We land and stay at the top of page A
  4. Click back button twice, scroll is restored to the scrolled down positions, as it was on first view of page A
  5. Click forward button twice, scroll is again restored to the scrolled down position, even though we had not originally scrolled this second instance of Page A.

How crazy/possible is it to somehow record the scroll position for each pop state version of a url? If we don't, is it better to reset a saved scroll position after arriving to the page via a regular link again?

@Amit-5111998
Copy link

const Feed = ({ navigationList }) => {
const router = useRouter();
useScrollRestoration(router);
}
I have a one-page feed should I send a router like this in the scroll ? @claus @pwfisher

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment