diff --git a/apps/web/app/(dashboard)/loading.tsx b/apps/web/app/(dashboard)/loading.tsx
new file mode 100644
index 00000000..8e96cdd5
--- /dev/null
+++ b/apps/web/app/(dashboard)/loading.tsx
@@ -0,0 +1,28 @@
+import { Skeleton } from "@/components/ui/skeleton";
+
+export default function DashboardLoading() {
+ return (
+
+ {/* Header skeleton */}
+
+
+
+
+ {/* Toolbar skeleton */}
+
+
+
+
+ {/* Content skeleton */}
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index c1df5730..26c41c34 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -1,5 +1,4 @@
import type { Metadata, Viewport } from "next";
-import { cookies } from "next/headers";
import { Geist, Geist_Mono } from "next/font/google";
import { ThemeProvider } from "@/components/theme-provider";
import { Toaster } from "@/components/ui/sonner";
@@ -8,6 +7,7 @@ import { QueryProvider } from "@core/provider";
import { AuthInitializer } from "@/features/auth";
import { WSProvider } from "@/features/realtime";
import { ModalRegistry } from "@/features/modals";
+import { LocaleSync } from "@/components/locale-sync";
import "./globals.css";
const geist = Geist({ subsets: ["latin"], variable: "--font-sans" });
@@ -51,22 +51,19 @@ export const metadata: Metadata = {
},
};
-export default async function RootLayout({
+export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
- const cookieStore = await cookies();
- const locale = cookieStore.get("multica-locale")?.value;
- const lang = locale === "zh" ? "zh" : "en";
-
return (
+
diff --git a/apps/web/components/locale-sync.tsx b/apps/web/components/locale-sync.tsx
new file mode 100644
index 00000000..e223adb3
--- /dev/null
+++ b/apps/web/components/locale-sync.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { useEffect } from "react";
+
+/**
+ * Reads the locale cookie on the client and updates .
+ * This avoids calling cookies() in the root Server Component layout,
+ * which would mark the entire app as dynamic and disable the Router Cache.
+ */
+export function LocaleSync() {
+ useEffect(() => {
+ const match = document.cookie.match(/(?:^|;\s*)multica-locale=(\w+)/);
+ const locale = match?.[1];
+ if (locale === "zh") {
+ document.documentElement.lang = "zh";
+ }
+ }, []);
+
+ return null;
+}