diff --git a/app/(admin)/layout.tsx b/app/(admin)/layout.tsx index 08dceda..62d58c3 100644 --- a/app/(admin)/layout.tsx +++ b/app/(admin)/layout.tsx @@ -1,20 +1,22 @@ -import type { ReactNode } from "react"; +import { Suspense, type ReactNode } from "react"; import ConditionalNavigation from "../components/navigation/ConditionalNavigation"; import { MessagesProvider } from "../contexts/MessagesContext"; import { AuthModalProvider } from "../contexts/AuthModalContext"; import messages from "../../messages/en/index"; -// Reads the session for admin chrome (matches the HttpOnly cookie on first -// HTML response). Scoped here so `(marketing)` can render statically. -export const dynamic = "force-dynamic"; - +// `force-dynamic` removed in favor of `experimental.cacheComponents` (Next 16). +// See `(app)/layout.tsx` for the matching `` rationale +// — the fallback can't access `usePathname()` since it sits in the static shell. +// // Operator/admin dashboards (e.g. `/monitor`) intentionally render without the // public marketing footer. Auth/access is enforced upstream. export default function AdminLayout({ children }: { children: ReactNode }) { return ( - + + +
{children}
diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx index 11e8278..8797597 100644 --- a/app/(app)/layout.tsx +++ b/app/(app)/layout.tsx @@ -1,15 +1,19 @@ -import type { ReactNode } from "react"; +import { Suspense, type ReactNode } from "react"; import ConditionalNavigation from "../components/navigation/ConditionalNavigation"; import { MessagesProvider } from "../contexts/MessagesContext"; import { AuthModalProvider } from "../contexts/AuthModalContext"; import messages from "../../messages/en/index"; -// Reads `cr_session` via Server Components on every navigation so the header -// matches the HttpOnly cookie on the first HTML response (no "Log in" flash -// before `/api/auth/session`). Scoped here instead of the root layout so -// `(marketing)` can render statically. -export const dynamic = "force-dynamic"; - +// `force-dynamic` removed in favor of `experimental.cacheComponents` (Next 16). +// `ConditionalNavigation` reads `cr_session` server-side (and `usePathname()` +// transitively in `ConditionalNavigationClient`) — both are uncached, so it +// lives behind a `` boundary so the rest of the layout stays in the +// static shell while the session/pathname-aware nav streams in. The fallback +// is `null` because any non-null fallback would also need to live in the +// static shell, and the nav's chromeless decision depends on the pathname +// (e.g. `/create/*` and `/login` render no top-nav). Brief blank-nav while +// the dynamic island resolves is acceptable on signed-in product surfaces. +// // Signed-in product surfaces (`/create/*`, `/login`) run without the marketing // footer. `/profile` adds it via `profile/layout.tsx`. Per-route chrome (e.g. // CreateFlow) is composed in nested layouts. @@ -17,7 +21,9 @@ export default function AppLayout({ children }: { children: ReactNode }) { return ( - + + +
{children}
diff --git a/app/(marketing)/layout.tsx b/app/(marketing)/layout.tsx index 6155b79..48b387c 100644 --- a/app/(marketing)/layout.tsx +++ b/app/(marketing)/layout.tsx @@ -1,5 +1,5 @@ import dynamic from "next/dynamic"; -import type { ReactNode } from "react"; +import { Suspense, type ReactNode } from "react"; import MarketingNavigation from "../components/navigation/MarketingNavigation"; import { MessagesProvider } from "../contexts/MessagesContext"; import { AuthModalProvider } from "../contexts/AuthModalContext"; @@ -19,7 +19,14 @@ export default function MarketingLayout({ children }: { children: ReactNode }) { return ( - + {/* + * MarketingNavigation reads `usePathname()` to decide chromeless paths + * (uncached data under `cacheComponents`). Suspense lets the static + * shell prerender; the nav streams in with the correct visibility. + */} + + +
{children}