Automatic scroll restoration in Single Page Applications (SPA)

January 31, 2019

The ability to restore scroll position is often critical for website usability. It helps users keep the flow of navigation when going back and forth between different pages. Most modern browsers take care of restoring the scroll position automatically, but it doesn’t always work for Single Page Applications where the content is generated on the client’s side, often asynchronously.

Sites like medium and cnbc store the scroll position of each page, and then, when the user navigates back they restore it. The user experience is not ideal, since there’s a clear jump in the scroll position.

In the case of refine.bio, we came across this issue on our search page, where we have a lengthy page with up to 50 results. Users became frustrated after clicking on one of the experiments, then trying to go back to the results only to realize they were at the top of the page again.

We were able to solve this for Chrome by caching the results, so that the results page could be rendered synchronously. However, this didn’t work on Firefox, because this browser tries to restore the scroll position before the page is fully rendered. I made an example to illustrate this scenario: try opening it on chrome and firefox to see the difference.

React-router, the framework we use for routing, once provided scroll restoration out of the box; however, since browsers have started to handle scroll restoration better, the framework maintainers decided to drop support for this and let users implement their own solutions.

We considered several approaches and decided to go with this polyfill, it’s the simplest solution that we found and it seems to work well for our case with no jumps when navigating back. We installed it as a node package, but if you’re looking for a one line solution this might work:

<script src="https://unpkg.com/delayed-scroll-restoration-polyfill@0.1.1/index.js"> </script>

This worked well because we were able to generate the content of the search page synchronously, for websites where this isn’t possible a custom scroll restoration logic will have to be added.

Each site is different, but there’s a chance that the custom scroll restoration logic will start competing with the native browser implementation. This creates a janky behavior where the scroll position will change several times when the user navigates back. After Chrome 46 and Firefox 46, a new modification was introduced to the history api to address this issue: history.scrollRestorationMode. When set to `manual` the user agent will not attempt to restore the scroll position automatically, leaving all responsibility to the site’s custom logic.

Conclusion

Keeping the scroll position consistent when the user navigates back and forth in a SPA can be a hard problem, but not if your pages are generated synchronously and you use this polyfill. As an added bonus, this polyfill keeps the scroll position consistent even if the user leaves your SPA, something that even Chrome's native behavior doesn’t do.

The ability to restore scroll position is often critical for website usability. It helps users keep the flow of navigation when going back and forth between different pages. Most modern browsers take care of restoring the scroll position automatically, but it doesn’t always work for Single Page Applications where the content is generated on the client’s side, often asynchronously.

Sites like medium and cnbc store the scroll position of each page, and then, when the user navigates back they restore it. The user experience is not ideal, since there’s a clear jump in the scroll position.

In the case of refine.bio, we came across this issue on our search page, where we have a lengthy page with up to 50 results. Users became frustrated after clicking on one of the experiments, then trying to go back to the results only to realize they were at the top of the page again.

We were able to solve this for Chrome by caching the results, so that the results page could be rendered synchronously. However, this didn’t work on Firefox, because this browser tries to restore the scroll position before the page is fully rendered. I made an example to illustrate this scenario: try opening it on chrome and firefox to see the difference.

React-router, the framework we use for routing, once provided scroll restoration out of the box; however, since browsers have started to handle scroll restoration better, the framework maintainers decided to drop support for this and let users implement their own solutions.

We considered several approaches and decided to go with this polyfill, it’s the simplest solution that we found and it seems to work well for our case with no jumps when navigating back. We installed it as a node package, but if you’re looking for a one line solution this might work:

<script src="https://unpkg.com/delayed-scroll-restoration-polyfill@0.1.1/index.js"> </script>

This worked well because we were able to generate the content of the search page synchronously, for websites where this isn’t possible a custom scroll restoration logic will have to be added.

Each site is different, but there’s a chance that the custom scroll restoration logic will start competing with the native browser implementation. This creates a janky behavior where the scroll position will change several times when the user navigates back. After Chrome 46 and Firefox 46, a new modification was introduced to the history api to address this issue: history.scrollRestorationMode. When set to `manual` the user agent will not attempt to restore the scroll position automatically, leaving all responsibility to the site’s custom logic.

Conclusion

Keeping the scroll position consistent when the user navigates back and forth in a SPA can be a hard problem, but not if your pages are generated synchronously and you use this polyfill. As an added bonus, this polyfill keeps the scroll position consistent even if the user leaves your SPA, something that even Chrome's native behavior doesn’t do.

Back To Blog