Scroll Restoration in React Router

Published: (January 17, 2026 at 04:30 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

When building Single Page Applications (SPAs) with React Router, a common UX issue appears almost immediately: navigation changes the URL, but the page does not scroll to the top. This breaks user expectations, especially on content‑heavy pages like blogs, docs, or dashboards.

Traditional multi‑page websites vs. SPAs

Traditional multi‑page sitesSPAs
NavigationTriggers a full page reloadHappens client‑side
DOM reloadYesNo
Browser scroll resetAutomatically to (0, 0)Preserved by default

Consequently, navigating from /blog/about leaves you scrolled halfway down the page.

Simple and correct implementation

import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";

export function ScrollToTop() {
  const { pathname } = useLocation();

  useLayoutEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
}
  • useLocation() provides the current location object.
  • pathname changes only when the route changes (not on query‑param or hash changes unless you include them).
  • useLayoutEffect runs before the browser paints, preventing a visible jump/glitch.

Tip: Use useLayoutEffect for layout‑related side effects such as scroll, focus, or DOM measurement.

Where to mount the component

import { BrowserRouter } from "react-router-dom";
import { ScrollToTop } from "./ScrollToTop";

<BrowserRouter>
  <ScrollToTop />
  {/* ...your routes */}
</BrowserRouter>
  • Mount it once at the root of your router.
  • Do not place it inside individual pages or wrap routes individually.

Handling query parameters

If you want the scroll to reset when the query string changes:

import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";

export function ScrollToTop() {
  const { pathname, search } = useLocation();

  useLayoutEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname, search]);

  return null;
}

Preserving anchor (hash) navigation

When the URL contains a hash (e.g., /docs#getting-started), the component above would override the browser’s default anchor scrolling. Fix it by ignoring hash changes:

import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";

export function ScrollToTop() {
  const { pathname, hash } = useLocation();

  useLayoutEffect(() => {
    if (hash) return; // let the browser handle anchor scrolling
    window.scrollTo(0, 0);
  }, [pathname, hash]);

  return null;
}

Note: Browsers automatically remember scroll position on back/forward navigation. This component can interfere with that behavior, so it’s best used only for forward navigation or disabled on specific routes.

iOS and alternative scrolling methods

On some iOS devices, window.scrollTo may fail due to momentum scrolling. A safer fallback:

document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;

Use the fallback only if you encounter issues.

Scrolling inside a custom container

If your app scrolls inside a specific element, target that element directly:

document.getElementById("scroll-root")?.scrollTo(0, 0);

Development quirks

  • In development mode, useLayoutEffect runs twice due to React’s strict mode. This does not affect production and can be ignored.
  • The effect runs only on navigation, performs a single synchronous operation, and causes zero re‑renders.

When not to use ScrollToTop

Preserving scroll position is preferable in certain UI patterns:

  • Infinite‑scrolling pages
  • Chat applications
  • Forms with autosave
  • Map‑based interfaces

In these cases, disabling automatic scroll‑to‑top improves the user experience.

Full example (including hash handling)

import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";

export function ScrollToTop() {
  const { pathname, hash } = useLocation();

  useLayoutEffect(() => {
    if (hash) return;
    window.scrollTo({ top: 0, left: 0, behavior: "instant" });
  }, [pathname, hash]);

  return null;
}

Conclusion

Scroll restoration is not optional UI polish—it’s a core navigation expectation. This small component:

  • Fixes a fundamental SPA flaw
  • Improves perceived performance
  • Makes your app feel “native”

If you’re using React Router and don’t have this behavior, users will notice—even if they don’t tell you.

Back to Blog

Related posts

Read more »

Fundamentals of react app

Introduction Today we’re going to look at the reasons and uses of the files and folders that are visible when creating a React app. !React app structurehttps:/...