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