feat(frontend+backend): 实现SSR登录态优化与字体自托管

1. 后端调整refresh cookie路径与注释,完善认证cookie处理逻辑
2. 前端全局layout新增next/font自托管字体,消除FOUT闪动
3. 实现SSR直出登录态与未读通知数,避免页面加载闪动
4. 新增中间件处理access token静默轮转,保障SSR登录态
5. 优化登录、改密流程,添加页面刷新同步登录态
6. 重构通知铃铛组件支持初始未读数传入
This commit is contained in:
2026-09-12 04:59:26 +08:00
parent 1eaf3903d4
commit 1bb565c687
9 changed files with 226 additions and 30 deletions

View File

@@ -1,9 +1,33 @@
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 { fetchMe, fetchUnreadCount, type User } from "@/lib/api";
// 构建期下载并自托管字体:同源加载 + 自动 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({
weight: ["400", "500", "700", "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: {
@@ -27,21 +51,34 @@ export default async function RootLayout({
// 浅色优先的设计系统,仅在显式选择暗色时挂 .dark
const theme = cookieStore.get("j13-theme")?.value === "dark" ? "dark" : "light";
// SSR 直出登录态(access 过期时 middleware 已先行轮转,见 middleware.ts),
// 保证 F5 初始 HTML 中的右上角就是正确的用户区,而非先闪"登录/注册"
const cookieHeader = cookieStore.toString();
let user: User | null = null;
let unread = 0;
if (cookieStore.get("j13_refresh")?.value) {
try {
const [me, count] = await Promise.all([
fetchMe(cookieHeader),
fetchUnreadCount(cookieHeader),
]);
user = me.user;
unread = count;
} catch {
// 后端不可用时降级为游客视图,不阻塞页面渲染
}
}
return (
<html lang="zh-CN" className={theme === "dark" ? "dark" : ""}>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700;900&display=swap"
rel="stylesheet"
/>
</head>
<html
lang="zh-CN"
className={`${theme === "dark" ? "dark" : ""} ${jakarta.variable} ${notoSC.variable} ${jetbrainsMono.variable}`}
>
<body className="min-h-screen flex flex-col">
<a href="#main" className="skip-link">
跳到主要内容
</a>
<Header />
<Header initialUser={user} initialUnread={unread} initialTheme={theme} />
<main
id="main"
tabIndex={-1}