@@ -3,14 +3,16 @@ import type { ReactNode } from 'react'
33import { useRouter } from 'next/router'
44
55import Cookies from '@/frame/components/lib/cookies'
6+ import { useIsHydrating } from '@/frame/components/hooks/useIsHydrating'
67import { SIDEBAR_COLLAPSED_COOKIE_NAME } from '@/frame/lib/constants'
78
89// Keep two sidebar state channels separate. collapsed persists in a cookie and
910// syncs the desktop toggle with the rail layout, mirroring
1011// src/landings/components/useSidebarExpandState.tsx. mobileNavOpen never persists;
11- // the mobile trigger reads it for inline nav expansion. getMainContext reads the
12- // cookie server-side and passes initialCollapsed, so server markup and hydration
13- // match. Without an initial value, the client falls back to the SSR-safe cookie helper.
12+ // the mobile trigger reads it for inline nav expansion. Page HTML is publicly cached
13+ // and does not vary on Cookie, so the server and hydration render the rail open. The
14+ // cookie applies in the re-render right after hydration, and client-side mounts read it
15+ // on their first render.
1416
1517function readCollapsed ( ) : boolean {
1618 try {
@@ -41,30 +43,21 @@ type SidebarCollapseContextValue = {
4143
4244const SidebarCollapseContext = createContext < SidebarCollapseContextValue | null > ( null )
4345
44- export function SidebarCollapseProvider ( {
45- children,
46- initialCollapsed,
47- } : {
48- children : ReactNode
49- initialCollapsed ?: boolean
50- } ) {
46+ export function SidebarCollapseProvider ( { children } : { children : ReactNode } ) {
5147 const { asPath } = useRouter ( )
52- // Prefer the server-read cookie value so server markup and hydration match.
53- const [ collapsed , setCollapsedState ] = useState ( ( ) => initialCollapsed ?? readCollapsed ( ) )
48+ const hydrating = useIsHydrating ( )
49+ const saved = useMemo ( ( ) => ! hydrating && readCollapsed ( ) , [ hydrating ] )
50+ // Stays null until the reader toggles, so the saved state shows after hydration.
51+ const [ chosen , setChosen ] = useState < boolean | null > ( null )
52+ const collapsed = chosen ?? saved
5453 const [ mobileNavOpen , setMobileNavOpen ] = useState ( false )
5554
5655 const setCollapsed = useCallback ( ( next : boolean ) => {
57- setCollapsedState ( next )
56+ setChosen ( next )
5857 persistCollapsed ( next )
5958 } , [ ] )
6059
61- const toggleCollapsed = useCallback ( ( ) => {
62- setCollapsedState ( ( prev ) => {
63- const next = ! prev
64- persistCollapsed ( next )
65- return next
66- } )
67- } , [ ] )
60+ const toggleCollapsed = useCallback ( ( ) => setCollapsed ( ! collapsed ) , [ collapsed , setCollapsed ] )
6861
6962 const toggleMobileNav = useCallback ( ( ) => setMobileNavOpen ( ( prev ) => ! prev ) , [ ] )
7063 const closeMobileNav = useCallback ( ( ) => setMobileNavOpen ( false ) , [ ] )
0 commit comments