Capítulo 15 de 24

Chapter 15: Scroll Restoration

Core Idea

Scroll restoration remembers and reapplies scroll position per route (and per scrollable element) as the user navigates back and forth, so long lists don't reset to the top on every visit. TanStack Router handles this automatically at the window level via a router option, and offers useElementScrollRestoration plus a data attribute for restoring scroll inside specific inner containers (e.g. a virtualized list).

Setup

  • Routing style: code-based (createRoute / createRootRoute / createRouter)
  • Key dependencies: @tanstack/react-router (useElementScrollRestoration), @tanstack/react-virtual (for the virtualized panel), @tanstack/react-router-devtools
  • Structure: single src/main.tsx with three routes (/, /about, /by-element), each rendering a tall list of items so scroll position is observable; /by-element also renders a virtualized panel to demonstrate element-scoped restoration.

Code Example

const router = createRouter({
  routeTree,
  defaultPreload: 'intent',
  scrollRestoration: true,
})

Opting a specific Link out of the default scroll-to-top behavior:

<Link to="/about" resetScroll={false}>
  About (No Reset)
</Link>

Restoring scroll inside one scrollable element (used with a virtualizer's initialOffset):

const scrollRestorationId = 'myVirtualizedContent'

const scrollEntry = useElementScrollRestoration({ id: scrollRestorationId })

const virtualizer = useVirtualizer({
  count: 10000,
  getScrollElement: () => virtualizerParentRef.current,
  estimateSize: () => 100,
  initialOffset: scrollEntry?.scrollY,
})

// ...
<div
  ref={virtualizerParentRef}
  data-scroll-restoration-id={scrollRestorationId}
  className="flex-1 border rounded-lg overflow-auto relative"
>
  • What it demonstrates: scrollRestoration: true on createRouter covers window scroll automatically; per-element restoration requires a stable data-scroll-restoration-id attribute on the scrollable node plus useElementScrollRestoration({ id }) to read back the saved offset for consumers like a virtualizer.

Key Takeaways

  1. Turn on scrollRestoration: true once at the router level; it covers window-level scroll for every route with no per-route code.
  2. Use resetScroll={false} on a Link when navigating within the same conceptual view should preserve scroll instead of jumping to top (the default on route change).
  3. For custom scroll containers (virtualized lists, inner panels with overflow-auto), tag the element with data-scroll-restoration-id and read its saved position with useElementScrollRestoration({ id }); feed that into the scrolling library's initial offset.
  4. Loaders that return promises (as in these route loader functions) let the router wait for data before restoring scroll, avoiding a restore-then-jump flash on slow-loading routes.

Connects To

  • View Transitions (ch016): scroll restoration and view transitions both shape the felt continuity of a navigation; enabling both together produces a smoother "same page, scrolled and animated" feel across route changes.
  • Location Masking (ch013): the masked photo list in that example relies on the list route staying mounted (and thus keeping its scroll position) while the modal route layers on top.