Scroll Restoration and Optimistic Route Transitions0%

Scroll Restoration and Optimistic Route Transitions

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Scroll Restoration and Optimistic Route Transitions

1. The Scroll Position Bug in SPAs

In traditional server-rendered websites, when a user scrolls halfway down a long catalog page, clicks a link to view a product, and then clicks the browser "Back" button:

  • The browser automatically restores the exact scroll position where they left off.

In a naive Single-Page Application:

  • Clicking a link navigates to a new view, but the viewport remains scrolled down at the previous position!
  • Clicking "Back" sends the user to the top of the catalog page, forcing them to search and scroll all over again!

This jarring UX flaw is solved in modern React Router via Scroll Restoration.

Output
Naive SPA:
Scroll to item 50 ──► Click Detail ──► Viewport stays scrolled down! (BUG)
 
With <ScrollRestoration>:
Scroll to item 50 ──► Click Detail ──► Smoothly resets to top (0,0)
Click "Back" ──► Viewport restored PRECISELY to item 50! (Expected UX)

2. Implementing <ScrollRestoration />

In React Router v6 Data Routers (createBrowserRouter), you simply render <ScrollRestoration /> inside your root layout component:

JSX
// src/layouts/RootLayout.jsx
import React from 'react';
import { Outlet, ScrollRestoration } from 'react-router-dom';
import Navbar from '../components/Navbar';
import Footer from '../components/Footer';
 
export default function RootLayout() {
return (
<div className="app-shell">
<Navbar />
 
<main className="content">
<Outlet />
</main>
 
<Footer />
 
{/* Automatically restores scroll position on navigation! */}
<ScrollRestoration />
</div>
);
}

Custom Scroll Keys (Tabs and Query Params)

By default, scroll positions are keyed by the URL pathname. If you have tabs that change query params (?tab=reviews) and want to preserve scroll independently:

JSX
<ScrollRestoration
getKey={(location, matches) => {
// Return key string to match against
return location.pathname + location.search;
}}
/>

3. Optimistic Route Transitions with useNavigation

When users click a link that loads data via a loader, there may be a 150–300ms delay while data fetches across the network.

Without visual feedback, the user wonders: "Did my click register?"

React Router provides the useNavigation() hook to inspect active in-flight transitions:

JSX
import React from 'react';
import { useNavigation } from 'react-router-dom';
 
export default function GlobalProgressBar() {
const navigation = useNavigation();
 
// navigation.state: 'idle' | 'submitting' | 'loading'
const isNavigating = navigation.state !== 'idle';
 
if (!isNavigating) return null;
 
return (
<div className="top-progress-bar" role="progressbar">
<div className="progress-indeterminate-pulse" />
</div>
);
}

4. Pending UI Styling with useNavigation

You can even dim the current screen while the next route is loading:

JSX
export default function RootLayout() {
const navigation = useNavigation();
 
return (
<div className={`app-shell ${navigation.state === 'loading' ? 'loading-opacity' : ''}`}>
<GlobalProgressBar />
<Outlet />
</div>
);
}

Interactive Quiz

Practice Quiz

Question 1
What component in React Router v6 automatically emulates the browser's native scroll behavior across SPA route transitions?
Question 2
Where should <ScrollRestoration /> be rendered in an application?
Question 3
What hook allows components to detect when an asynchronous route loader or form submission is currently in-flight?
Question 4
How can you create a top-of-screen progress bar (similar to GitHub or YouTube) during route changes?
Question 5
What does the getKey prop on <ScrollRestoration> allow developers to customize?
Next Lesson

Architecture of Server-Side Rendering vs Client-Side Rendering

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext