Skip to content

Commit 1f67317

Browse files
committed
fix(landing-nav): gate initial-mount skip on document.readyState
1 parent e156dba commit 1f67317

1 file changed

Lines changed: 17 additions & 8 deletions

File tree

apps/sim/app/(landing)/components/scroll-to-top.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,21 @@ import { usePathname } from 'next/navigation'
1818
let lastPopstateAt = Number.NEGATIVE_INFINITY
1919
const 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
*/
2837
let 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
*/
4756
export 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

Comments
 (0)