Next.js — Keep page components mounted between page transitions and maintain scroll position

Angus Russell
2 min readApr 12, 2020

I use my own app — NightCafe Creator — a lot, and the thing that annoys me the most is when I am scrolling through people’s creations, then I click/tap on a ‘creation’ to view more about it, I lose the loaded creations and scroll position from the previous page. On Instagram and other apps, when I tap a link from the feed, then later go back to the feed, the feed state and scroll position is retained so I can keep on scrolling.

NightCafe Creator uses Next.js, and every view is a ‘page’, that gets unmounted whenever you leave it. When you return, whether via ‘back’ or clicking a link, the page is mounted again from scratch, so it’s local state is lost and needs to be re-fetched.

I’ve come up with a reasonable (though not perfect) solution that is fairly simple. Shown here in a gist.

Essentially what’s happening here is:

  • We define a few top-level pages that we want to retain. The rest will always be re-mounted.
  • We keep an object in React refs — retainedComponents — that keeps track of the components we want to retain.
  • When one of the pages we want to retain is mounted for the first time, we add it to retainedComponents
  • We add a listener to the router that saves the scroll position of the current page component before navigating away from it.
  • We always render all the retained components, but we hide the inactive ones.
  • After a retained component is rendered, we set the scroll position back to the saved position.

This works, but there are couple of issues I’d ideally like to fix:

  1. Next will still run the page’s getInitialProps function before transitioning the route (even though we’re not using it)
  2. The scroll position runs after the render, so it can result in a flash of the ‘above the fold’ before scrolling to the saved position

I think the only way to solve issue 1 is to keep the state in redux (or another global state manager) and check if the state exists before running any async tasks in the page’s getInitialProps function.

A better way to manage scroll position would be to put each page inside its own absolutely positioned container that can keep its own scroll position. The container div could be positioned off-screen rather than hidden.

--

--