@@ -18,12 +18,21 @@ import { usePathname } from 'next/navigation'
1818let lastPopstateAt = Number . NEGATIVE_INFINITY
1919const POPSTATE_WINDOW_MS = 200
2020
21+ /**
22+ * Captured at module evaluation time. When this module is bundled into the
23+ * initial page payload (direct load / reload of a shelled route), readyState
24+ * is still `loading` or `interactive`, the browser will restore scroll on
25+ * reload, and we should skip the first reset. When the module is dynamically
26+ * imported during a client-side navigation (e.g., user clicked from `/` into
27+ * `/blog/x`), readyState is already `complete` and the first mount is a real
28+ * route change that should scroll to top.
29+ */
30+ const wasInitialPageLoad = typeof document !== 'undefined' && document . readyState !== 'complete'
31+
2132/**
2233 * Tracks whether any `ScrollToTop` instance has run its mount effect yet.
2334 * Module-scoped so cross-section navigation (which mounts a fresh instance)
24- * still scrolls — only the very first mount on page load is treated as the
25- * initial render, letting the browser's native scroll restoration win on
26- * reload.
35+ * doesn't re-trigger the initial-mount guard.
2736 */
2837let hasMounted = false
2938
@@ -39,18 +48,18 @@ if (typeof window !== 'undefined') {
3948 * Next.js's default scroll handling only brings the new Page element into view,
4049 * which often resolves to "no scroll" inside shared layouts (see vercel/next.js#64435).
4150 *
42- * Skipped on the initial mount (so browser scroll restoration on reload wins),
43- * when the pathname change closely follows a popstate (preserving browser
44- * back/forward scroll restoration), and when a hash anchor is targeted (letting
45- * the browser's native anchor scroll win).
51+ * Skipped on the very first mount of an initial page load (so browser scroll
52+ * restoration on reload wins), when the pathname change closely follows a
53+ * popstate (preserving browser back/forward restoration), and when a hash
54+ * anchor is targeted (letting the browser's native anchor scroll win).
4655 */
4756export function ScrollToTop ( ) {
4857 const pathname = usePathname ( )
4958
5059 useEffect ( ( ) => {
5160 if ( ! hasMounted ) {
5261 hasMounted = true
53- return
62+ if ( wasInitialPageLoad ) return
5463 }
5564 if ( performance . now ( ) - lastPopstateAt < POPSTATE_WINDOW_MS ) {
5665 lastPopstateAt = Number . NEGATIVE_INFINITY
0 commit comments