import type { Metadata } from "next"; import { cookies } from "next/headers"; import { Plus_Jakarta_Sans, Noto_Sans_SC, JetBrains_Mono } from "next/font/google"; import "./globals.css"; import Header from "@/components/Header"; import Footer from "@/components/Footer"; import Toaster from "@/components/Toaster"; import TipHost from "@/components/TipHost"; import AccountGuard from "@/components/AccountGuard"; import ThemeSync from "@/components/ThemeSync"; import RealtimeProvider from "@/components/RealtimeProvider"; import type { User } from "@/lib/api"; import { getMeCached, getPublicSettingsCached } from "@/lib/serverData"; import { TOKEN_COOKIE, authCookieHeader } from "@/lib/cookies"; import { accentStyleCSS } from "@/lib/theme"; // 构建期下载并自托管字体:同源加载 + 自动 preload + fallback 度量校正, // 消除 F5 时 Google CDN 字体到达后整页"由粗变细"的 FOUT 闪动 const jakarta = Plus_Jakarta_Sans({ subsets: ["latin"], weight: ["400", "500", "600", "700", "800"], variable: "--font-jakarta", display: "swap", }); const notoSC = Noto_Sans_SC({ // 须覆盖站内实际用到的字重:缺档时浏览器会伪粗(synthetic bold),中文在 2K 屏发虚发糊 weight: ["400", "500", "600", "700", "800", "900"], variable: "--font-noto-sc", display: "swap", }); const jetbrainsMono = JetBrains_Mono({ subsets: ["latin"], weight: ["400", "500", "700"], variable: "--font-jetbrains", display: "swap", }); export const metadata: Metadata = { title: { default: "姜十三论坛 · J13 FORUM", template: "%s · 姜十三论坛", }, description: "姜十三论坛 - 技术分享与讨论社区", openGraph: { title: "姜十三论坛 · J13 FORUM", description: "姜十三论坛 - 技术分享与讨论社区", type: "website", }, }; export default async function RootLayout({ children, }: { children: React.ReactNode; }) { const cookieStore = await cookies(); // 浅色优先的设计系统,仅在显式选择暗色时挂 .dark const theme = cookieStore.get("j13-theme")?.value === "dark" ? "dark" : "light"; // SSR 直出登录态(access 过期时 middleware 已先行轮转并注入请求头,见 middleware.ts), // 保证 F5 初始 HTML 中的右上角就是正确的用户区,而非先闪"登录/注册"。 // 无 access cookie 即按游客渲染;客户端 Header 在 CSRF 可读时再静默校正一次。 // 站点主题色(公开设置):派生浅/暗两套 accent CSS 变量后随首屏 HTML 内联, // 覆盖 globals.css 默认令牌——无主题闪动(FOUC),后端不可用时降级为默认靛蓝 const { accent } = await getPublicSettingsCached(); const accentCSS = accentStyleCSS(accent || ""); let user: User | null = null; let unread = 0; let chatUnread = 0; const hasAuthCookie = !!cookieStore.get(TOKEN_COOKIE)?.value; if (hasAuthCookie) { // 用户信息与未读数合并为一次请求;只转发最小化的 access cookie const me = await getMeCached(authCookieHeader(cookieStore) || undefined); user = me.user; unread = me.unread_count; chatUnread = me.chat_unread_count ?? 0; } return (
{accentCSS ? ( // React 托管的 SSR 主题节点;客户端换肤走 j13-accent-runtime,禁止对此外挂 remove ) : null} 跳到主要内容