Skip to content

Commit 2911924

Browse files
committed
fix(landing-nav): scroll to top on route change in shared shells
1 parent d9dd7a3 commit 2911924

4 files changed

Lines changed: 41 additions & 0 deletions

File tree

apps/sim/app/(landing)/blog/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getNavBlogPosts } from '@/lib/blog/registry'
22
import { SITE_URL } from '@/lib/core/utils/urls'
33
import Footer from '@/app/(landing)/components/footer/footer'
44
import Navbar from '@/app/(landing)/components/navbar/navbar'
5+
import { ScrollToTop } from '@/app/(landing)/components/scroll-to-top'
56

67
export default async function StudioLayout({ children }: { children: React.ReactNode }) {
78
const blogPosts = await getNavBlogPosts()
@@ -29,6 +30,7 @@ export default async function StudioLayout({ children }: { children: React.React
2930

3031
return (
3132
<div className='flex min-h-screen flex-col bg-[var(--landing-bg)] font-[430] font-season text-[var(--landing-text)]'>
33+
<ScrollToTop />
3234
<script
3335
type='application/ld+json'
3436
dangerouslySetInnerHTML={{ __html: JSON.stringify(orgJsonLd) }}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use client'
2+
3+
import { useEffect, useRef } from 'react'
4+
import { usePathname } from 'next/navigation'
5+
6+
/**
7+
* Resets window scroll to the top on App Router pathname changes.
8+
*
9+
* Next.js's default scroll handling only brings the new Page element into view,
10+
* which often resolves to "no scroll" inside shared layouts (see vercel/next.js#64435).
11+
* Popstate-driven navigations are skipped so browser back/forward scroll restoration
12+
* is preserved.
13+
*/
14+
export function ScrollToTop() {
15+
const pathname = usePathname()
16+
const isPopNavigationRef = useRef(false)
17+
18+
useEffect(() => {
19+
const onPop = () => {
20+
isPopNavigationRef.current = true
21+
}
22+
window.addEventListener('popstate', onPop)
23+
return () => window.removeEventListener('popstate', onPop)
24+
}, [])
25+
26+
useEffect(() => {
27+
if (isPopNavigationRef.current) {
28+
isPopNavigationRef.current = false
29+
return
30+
}
31+
window.scrollTo(0, 0)
32+
}, [pathname])
33+
34+
return null
35+
}

apps/sim/app/(landing)/integrations/(shell)/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getNavBlogPosts } from '@/lib/blog/registry'
22
import { SITE_URL } from '@/lib/core/utils/urls'
33
import Footer from '@/app/(landing)/components/footer/footer'
44
import Navbar from '@/app/(landing)/components/navbar/navbar'
5+
import { ScrollToTop } from '@/app/(landing)/components/scroll-to-top'
56

67
export default async function IntegrationsLayout({ children }: { children: React.ReactNode }) {
78
const blogPosts = await getNavBlogPosts()
@@ -29,6 +30,7 @@ export default async function IntegrationsLayout({ children }: { children: React
2930

3031
return (
3132
<div className='dark flex min-h-screen flex-col bg-[var(--landing-bg)] font-[430] font-season text-[var(--landing-text)]'>
33+
<ScrollToTop />
3234
<script
3335
type='application/ld+json'
3436
dangerouslySetInnerHTML={{ __html: JSON.stringify(orgJsonLd) }}

apps/sim/app/(landing)/models/(shell)/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getNavBlogPosts } from '@/lib/blog/registry'
22
import { SITE_URL } from '@/lib/core/utils/urls'
33
import Footer from '@/app/(landing)/components/footer/footer'
44
import Navbar from '@/app/(landing)/components/navbar/navbar'
5+
import { ScrollToTop } from '@/app/(landing)/components/scroll-to-top'
56

67
export default async function ModelsLayout({ children }: { children: React.ReactNode }) {
78
const blogPosts = await getNavBlogPosts()
@@ -24,6 +25,7 @@ export default async function ModelsLayout({ children }: { children: React.React
2425

2526
return (
2627
<div className='dark flex min-h-screen flex-col bg-[var(--landing-bg)] font-[430] font-season text-[var(--landing-text)]'>
28+
<ScrollToTop />
2729
<script
2830
type='application/ld+json'
2931
dangerouslySetInnerHTML={{ __html: JSON.stringify(orgJsonLd) }}

0 commit comments

Comments
 (0)