diff --git a/apps/desktop-ui/src/app/app/layout.tsx b/apps/desktop-ui/src/app/app/layout.tsx index dee1f9f4..6d19ddd7 100644 --- a/apps/desktop-ui/src/app/app/layout.tsx +++ b/apps/desktop-ui/src/app/app/layout.tsx @@ -1,3 +1,4 @@ +import { Suspense } from "react"; import { cookies } from "next/headers"; import { SidebarProvider } from "@/components/ui/sidebar"; import { AppContent } from "./app-content"; @@ -14,7 +15,12 @@ export default async function Layout({ children }: { children: React.ReactNode } return ( - {children} + {/* Suspense boundary so tool pages that read useSearchParams() (base64, + jwt-decoder, hash-generator, url-encode, json-formatter, qr-code) pass + the static export prerender instead of erroring on a CSR bailout. */} + + {children} + ); } diff --git a/apps/desktop-ui/src/store/pinned-tools-store.ts b/apps/desktop-ui/src/store/pinned-tools-store.ts index a3e0d79d..9034b141 100644 --- a/apps/desktop-ui/src/store/pinned-tools-store.ts +++ b/apps/desktop-ui/src/store/pinned-tools-store.ts @@ -72,13 +72,16 @@ export const usePinnedToolsStore = create()( /** Returns true once the persisted state has finished rehydrating from storage. */ export function usePinnedToolsHydrated(): boolean { - const [hydrated, setHydrated] = useState(() => usePinnedToolsStore.persist.hasHydrated()) + // `.persist` is absent during the static-export prerender (no storage on the + // server), so guard every access — "hydrated" is simply false there, and the + // real value resolves on the client where the persist API exists. + const [hydrated, setHydrated] = useState(() => usePinnedToolsStore.persist?.hasHydrated?.() ?? false) useEffect(() => { - if (usePinnedToolsStore.persist.hasHydrated()) { + if (usePinnedToolsStore.persist?.hasHydrated?.()) { setHydrated(true) return } - const unsub = usePinnedToolsStore.persist.onFinishHydration(() => setHydrated(true)) + const unsub = usePinnedToolsStore.persist?.onFinishHydration?.(() => setHydrated(true)) return unsub }, []) return hydrated