Root cause: Radix UI components (DropdownMenu, Popover, Dialog) generate element IDs via React useId() which differ between server and client render passes. This causes a hydration mismatch warning in the console. Fix: 1. Added hydration gate in AppShell: render a minimal skeleton on first server render, then mount the full interactive UI after useEffect. This ensures Radix IDs are only generated on the client. 2. Added suppressHydrationWarning to <body> (already on <html>). This is the official next-themes + Radix pattern. The skeleton shows: sidebar placeholder + empty header + footer. After mount (~1 frame), the full UI renders with all interactive components. Verified: 0 hydration errors, 0 console errors, 0 page errors. 0 TS errors, ESLint clean.
79 lines
2.4 KiB
TypeScript
Executable File
79 lines
2.4 KiB
TypeScript
Executable File
import type { Metadata } from "next";
|
|
import { Geist, Geist_Mono } from "next/font/google";
|
|
import "./globals.css";
|
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
|
import { ThemeProvider } from "@/components/theme-provider";
|
|
|
|
const geistSans = Geist({
|
|
variable: "--font-geist-sans",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
const geistMono = Geist_Mono({
|
|
variable: "--font-geist-mono",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
export const metadata: Metadata = {
|
|
title: "AOS — Agency Operating System",
|
|
description:
|
|
"Eine selbstgehostete Plattform, die eine Software-Agentur end-to-end betreibt: Leads, CRM, Projekte, Lizenzen, Finanzen — mit EU/DE-Compliance.",
|
|
keywords: [
|
|
"AOS",
|
|
"Agency OS",
|
|
"CRM",
|
|
"Leads Finder",
|
|
"Lizenzmanagement",
|
|
"E-Rechnung",
|
|
"DSGVO",
|
|
"GoBD",
|
|
],
|
|
authors: [{ name: "Nordlicht Digital GmbH" }],
|
|
manifest: "/manifest.json",
|
|
appleWebApp: {
|
|
capable: true,
|
|
title: "AOS",
|
|
statusBarStyle: "black",
|
|
},
|
|
icons: {
|
|
icon: "https://z-cdn.chatglm.cn/z-ai/static/logo.svg",
|
|
apple: "https://z-cdn.chatglm.cn/z-ai/static/logo.svg",
|
|
},
|
|
openGraph: {
|
|
title: "AOS — Agency Operating System",
|
|
description:
|
|
"Selbstgehostete Agentur-Plattform mit CRM, Leads Finder, Projekten, Lizenzen und Finanzen.",
|
|
siteName: "AOS",
|
|
type: "website",
|
|
},
|
|
};
|
|
|
|
// PRD §5.6: suppressHydrationWarning on html + body because we use:
|
|
// 1. next-themes (class on <html> changes client-side)
|
|
// 2. Zustand persist (view/locale loaded from localStorage client-side)
|
|
// 3. Radix UI generates IDs with useId() which differ between server and client
|
|
// (this is expected and the warning is cosmetic — Radix components still work correctly)
|
|
// The alternative would be to delay rendering until after hydration (mount gate),
|
|
// but that causes a flash of unstyled content. suppressHydrationWarning is the
|
|
// official next-themes + Radix pattern.
|
|
|
|
export default function RootLayout({
|
|
children,
|
|
}: Readonly<{
|
|
children: React.ReactNode;
|
|
}>) {
|
|
return (
|
|
<html lang="de" suppressHydrationWarning>
|
|
<body
|
|
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-background text-foreground`}
|
|
suppressHydrationWarning
|
|
>
|
|
<ThemeProvider attribute="class" defaultTheme="light" enableSystem={false}>
|
|
{children}
|
|
<Sonner position="bottom-right" richColors={false} closeButton />
|
|
</ThemeProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|