diff --git a/src/app/auth/page.tsx b/src/app/auth/page.tsx
new file mode 100644
index 00000000..b53b9e02
--- /dev/null
+++ b/src/app/auth/page.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+/**
+ * Auth 页(路由骨架版)
+ *
+ * 对齐 Flutter `lib/ui/auth/auth_screen.dart` 的语义:
+ * - 已登录用户:自动跳 `/chat`
+ * - 未登录:渲染返回 splash + disabled 登录表单占位
+ */
+
+import { useEffect } from "react";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+
+import { useAuthGate } from "@/lib/auth/use-auth-gate";
+import { ROUTES } from "@/lib/routes";
+
+export default function AuthPage() {
+ const router = useRouter();
+ const { isAuthed } = useAuthGate();
+
+ useEffect(() => {
+ if (isAuthed) {
+ router.replace(ROUTES.chat);
+ }
+ }, [isAuthed, router]);
+
+ if (!isAuthed) {
+ return (
+
+ Auth
+
+ Auth placeholder · login form coming soon.
+
+
+
+
+
+ Back to Splash
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/app/chat/deviceid/[deviceId]/DeepLinkPersist.tsx b/src/app/chat/deviceid/[deviceId]/DeepLinkPersist.tsx
new file mode 100644
index 00000000..1c1db495
--- /dev/null
+++ b/src/app/chat/deviceid/[deviceId]/DeepLinkPersist.tsx
@@ -0,0 +1,65 @@
+"use client";
+
+/**
+ * 深链落地:把 deviceId + fbid 写进 AuthStorage,然后跳 `/chat`。
+ *
+ * 对齐 Flutter `lib/router/app_router.dart` 中 `/chat/deviceid/:deviceId` 的
+ * `redirect` 回调(`app_router.dart:60-78`)。
+ *
+ * 设计:
+ * - **不**走 Server `redirect()`:避免把 fbid 暴露在 URL 里。
+ * - deviceId 走 sync `authStorage.setDeviceId`(与 `token_interceptor.ts` 同源;
+ * 写入键 `cozsweet.deviceId`)。
+ * - fbid 走 `localStorage.setItem("facebook_id", ...)`,对齐 async
+ * `StorageKeys.facebookId`。Sync 单例**没有** `setFacebookId` 方法,本轮
+ * 不重构 storage(见 plan §6.1),仅留 TODO。
+ * - 写入完成后 `router.replace('/chat')`,URL 不留深链痕迹。
+ */
+
+import { useEffect, useState } from "react";
+import { useRouter } from "next/navigation";
+
+import { authStorage } from "@/data/storage/auth_storage";
+import { StorageKeys } from "@/data/storage/storage_keys";
+import { ROUTES } from "@/lib/routes";
+
+interface DeepLinkPersistProps {
+ deviceId: string;
+ fbid: string | null;
+}
+
+export default function DeepLinkPersist({ deviceId, fbid }: DeepLinkPersistProps) {
+ const router = useRouter();
+ const [done, setDone] = useState(false);
+
+ useEffect(() => {
+ try {
+ authStorage.setDeviceId(deviceId);
+ if (fbid && fbid.length > 0) {
+ // TODO: 统一 sync/async AuthStorage 后改为 `authStorage.setFacebookId(fbid)`。
+ // 当前 sync 单例未暴露该方法;使用 `StorageKeys.facebookId` 保持与 async 层
+ // `src/data/storage/auth/auth_storage.ts` 键名一致。
+ window.localStorage.setItem(StorageKeys.facebookId, fbid);
+ }
+ } catch (err) {
+ // 写不进 localStorage 时(例如隐私模式)也不阻塞跳转;记录到 console 即可。
+ console.warn("[DeepLinkPersist] failed to persist", err);
+ } finally {
+ setDone(true);
+ router.replace(ROUTES.chat);
+ }
+ }, [deviceId, fbid, router]);
+
+ return (
+
+ );
+}
diff --git a/src/app/chat/deviceid/[deviceId]/page.tsx b/src/app/chat/deviceid/[deviceId]/page.tsx
new file mode 100644
index 00000000..31de2bb0
--- /dev/null
+++ b/src/app/chat/deviceid/[deviceId]/page.tsx
@@ -0,0 +1,26 @@
+/**
+ * 深链入口(Server Component)
+ *
+ * 对齐 Flutter `lib/router/app_router.dart:60-78` 的 `GoRoute(path: '/chat/deviceid/:deviceId')`:
+ * 把 `deviceId` 路径参数与 `fbid` 查询参数透传给 Client 组件 ``,
+ * 由它把数据写入 localStorage 后 `router.replace('/chat')`。
+ *
+ * Next.js 15+ 约定:`params` / `searchParams` 是 Promise,必须 await。
+ * Next.js 16 全局 helper `PageProps<'/literal'>` 由 `next dev` / `next build` /
+ * `next typegen` 生成。
+ *
+ * 这里**不**调用 `redirect()`:把 fbid 暴露在 URL 上对用户不友好;
+ * 跳转推迟到 Client 完成持久化之后。
+ */
+
+import DeepLinkPersist from "./DeepLinkPersist";
+
+export default async function ChatDeviceIdPage(
+ props: PageProps<"/chat/deviceid/[deviceId]">,
+) {
+ const { deviceId } = await props.params;
+ const { fbid } = await props.searchParams;
+ const fbidStr = Array.isArray(fbid) ? fbid[0] : fbid ?? null;
+
+ return ;
+}
diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx
new file mode 100644
index 00000000..0e4471cc
--- /dev/null
+++ b/src/app/chat/page.tsx
@@ -0,0 +1,53 @@
+"use client";
+
+/**
+ * Chat 页(路由骨架版)
+ *
+ * **不**做鉴权重定向:Flutter 端允许游客态直接进聊天(`auth_refresh_interceptor`
+ * 会同时处理登录与游客 token)。这里只放占位 UI。
+ */
+
+import Link from "next/link";
+
+import { ROUTES } from "@/lib/routes";
+
+export default function ChatPage() {
+ return (
+
+ Chat
+
+ Chat placeholder · guest mode supported · real chat UI coming soon.
+
+
+
+
+ Open Sidebar
+
+
+ Log in
+
+
+
+ );
+}
diff --git a/src/app/error.tsx b/src/app/error.tsx
new file mode 100644
index 00000000..6411898b
--- /dev/null
+++ b/src/app/error.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+/**
+ * 根级错误边界
+ *
+ * Next.js 16 约定:必须为 Client Component;reset prop 在 v16 中被重命名为
+ * `unstable_retry`(参见 `node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/error.md`)。
+ *
+ * 行为:把 `error` 打到 console(保留 digest 便于服务端聚合),并提供"重试"
+ * 与"返回首页"两个动作。
+ */
+
+import { useEffect } from "react";
+import Link from "next/link";
+
+import { ROUTES } from "@/lib/routes";
+
+interface ErrorProps {
+ error: Error & { digest?: string };
+ unstable_retry: () => void;
+}
+
+export default function GlobalError({ error, unstable_retry }: ErrorProps) {
+ useEffect(() => {
+ // 把错误送到 console;后续接 Sentry/Datadog 时改这里。
+ console.error("[app/error.tsx]", error);
+ }, [error]);
+
+ return (
+
+
+ Something went wrong
+
+
+ {error.message || "Unexpected error"}
+ {error.digest ? ` (digest: ${error.digest})` : null}
+
+
+
+
+ Back to start
+
+
+
+ );
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 3181a82c..fbfc7c35 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import { RootProviders } from "@/providers/root-providers";
+
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
@@ -23,11 +25,16 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
+ // suppressHydrationWarning:next/font + Tailwind v4 偶发类名差异;详见
+ // `src/lib/auth/use-auth-gate.tsx` 的"水合闪屏"说明。
- {children}
+
+ {children}
+
);
}
diff --git a/src/app/loading.tsx b/src/app/loading.tsx
new file mode 100644
index 00000000..769b82c8
--- /dev/null
+++ b/src/app/loading.tsx
@@ -0,0 +1,21 @@
+/**
+ * 根级 Suspense fallback
+ *
+ * Server Component,渲染于任一未 ready 子树之上。
+ * 设计为最小化的居中 spinner,复用 `globals.css` 的 `--color-accent`。
+ */
+
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
new file mode 100644
index 00000000..e354a4e3
--- /dev/null
+++ b/src/app/not-found.tsx
@@ -0,0 +1,39 @@
+/**
+ * 根级 404
+ *
+ * Server Component。覆盖未匹配路由(结合 `notFound()` 调用)。
+ * 不启用 v16 实验性 `global-not-found.js`,避免 `next.config.ts` 变更。
+ */
+
+import Link from "next/link";
+
+import { ROUTES } from "@/lib/routes";
+
+export default function NotFound() {
+ return (
+
+
+ Page not found
+
+
+ The page you're looking for doesn't exist or has been moved.
+
+
+ Return to start
+
+
+ );
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 3f36f7c5..8c53f016 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,65 +1,18 @@
-import Image from "next/image";
+/**
+ * 根路径处理
+ *
+ * Server Component:把访客直接送入 `/splash`,由 splash 页(Client)根据
+ * `authStorage.hasLoginToken()` 决定是否再跳 `/chat`。
+ *
+ * 这里**不**做鉴权重定向:Server 端读不到 localStorage,且当前没有 HttpOnly
+ * cookie(见 `src/data/storage/auth/auth_storage.ts` 的 TODO)。
+ * `src/proxy.ts` 已为未来 cookie 化预留了基于 `login_token` cookie 的 short-circuit。
+ */
+
+import { redirect } from "next/navigation";
+
+import { ROUTES } from "@/lib/routes";
export default function Home() {
- return (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
-
-
- );
+ redirect(ROUTES.splash);
}
diff --git a/src/app/sidebar/page.tsx b/src/app/sidebar/page.tsx
new file mode 100644
index 00000000..a69c0c66
--- /dev/null
+++ b/src/app/sidebar/page.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+/**
+ * Sidebar 页(路由骨架版)
+ *
+ * 对齐 Flutter `lib/ui/sidebar/sidebar_screen.dart` 的 `onBackPressed` 行为:
+ * 顶部一个"返回 /chat"按钮。
+ */
+
+import { useRouter } from "next/navigation";
+
+import { ROUTES } from "@/lib/routes";
+
+export default function SidebarPage() {
+ const router = useRouter();
+
+ return (
+
+ Sidebar
+
+ Sidebar placeholder · profile / settings coming soon.
+
+
+
+ );
+}
diff --git a/src/app/splash/page.tsx b/src/app/splash/page.tsx
new file mode 100644
index 00000000..fcf5d295
--- /dev/null
+++ b/src/app/splash/page.tsx
@@ -0,0 +1,94 @@
+"use client";
+
+/**
+ * Splash 页(路由骨架版)
+ *
+ * 对齐 Flutter `lib/ui/splash/splash_screen.dart` 的语义:
+ * - 已登录用户:自动跳 `/chat`(替代 Flutter 的 `BlocListener`)
+ * - 未登录:渲染品牌占位 + Skip 按钮 + Facebook 登录占位
+ *
+ * 占位 UI 不复刻 Flutter 视觉(待 `src/components/` 落地后再做),仅做路由行为验证。
+ */
+
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+
+import { useAuthGate } from "@/lib/auth/use-auth-gate";
+import { ROUTES } from "@/lib/routes";
+
+export default function SplashPage() {
+ const router = useRouter();
+ const { isAuthed } = useAuthGate();
+
+ // 已登录 → 自动跳 /chat。
+ useEffect(() => {
+ if (isAuthed) {
+ router.replace(ROUTES.chat);
+ }
+ }, [isAuthed, router]);
+
+ // `useAuthGate` 在 SSR / 客户端水合首帧都返回 `isAuthed=false`,
+ // 因此这里会先渲染 spinner 占位(也避免水合闪屏),等水合完成、
+ // React 自动重读 localStorage 拿到真值后,若仍为 false 则展示下方 UI。
+ if (!isAuthed) {
+ return (
+
+ cozsweet
+
+ Splash placeholder · authed → {String(isAuthed)}
+
+
+
+
+
+
+
+ );
+ }
+
+ // 已登录:上面的 useEffect 会触发 router.replace;这里短暂渲染占位。
+ return (
+
+ );
+}
diff --git a/src/lib/auth/route-guards.ts b/src/lib/auth/route-guards.ts
new file mode 100644
index 00000000..d40dae5c
--- /dev/null
+++ b/src/lib/auth/route-guards.ts
@@ -0,0 +1,36 @@
+/**
+ * 纯函数路由守卫
+ *
+ * 不依赖 React/Next.js,可被 `src/proxy.ts`(边缘运行时)与
+ * `src/lib/auth/use-auth-gate.tsx`(客户端 hook)共用。
+ *
+ * 对齐 Flutter `lib/router/app_router.dart` 中 `_handleAuthRedirect` 的语义:
+ * 已登录用户访问 `/splash` 或 `/auth` 时,重定向到 `/chat`。
+ */
+
+import { AUTH_ONLY_ROUTES, ROUTES, type StaticRoute } from "../routes";
+
+/**
+ * 判定路径是否为"仅未登录态可见"的入口路由。
+ */
+export function isAuthOnlyRoute(pathname: string): pathname is StaticRoute {
+ return (AUTH_ONLY_ROUTES as readonly string[]).includes(pathname);
+}
+
+/**
+ * 判定路径是否为公开路由(游客态也允许访问)。
+ */
+export function isPublicRoute(pathname: string): pathname is StaticRoute {
+ return pathname === ROUTES.chat || pathname === ROUTES.sidebar;
+}
+
+/**
+ * 已登录用户访问入口路由时返回应跳转到的目标;其余情况返回 null。
+ *
+ * 当前实现仅做"splash/auth → chat"的重定向;未来若新增入口路由(如 onboarding),
+ * 只需在此函数追加分支。
+ */
+export function resolveAuthedRedirect(pathname: string): string | null {
+ if (isAuthOnlyRoute(pathname)) return ROUTES.chat;
+ return null;
+}
diff --git a/src/lib/auth/use-auth-gate.tsx b/src/lib/auth/use-auth-gate.tsx
new file mode 100644
index 00000000..53239541
--- /dev/null
+++ b/src/lib/auth/use-auth-gate.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+/**
+ * 客户端鉴权门 hook
+ *
+ * 单一真值 = `authStorage`(与 `src/data/api/interceptor/token_interceptor.ts` 同源)。
+ * 故意不引入 Context/Zustand:本轮只有 splash/auth 两处使用,直接 hook 调用更直白。
+ *
+ * 实现要点(React 19 / Next.js 16 约定):
+ * - 用 `useSyncExternalStore` 而非 `useEffect + setState`,避免 React 19
+ * `react-hooks/set-state-in-effect` 规则的级联渲染告警。
+ * - `getServerSnapshot` 返回 `false` → SSR 与客户端水合首帧都拿到一致值;
+ * 水合完成后 React 自动重读 `getSnapshot()`(localStorage 真值),触发单次
+ * 自然重渲染 → 调用方 `useEffect([isAuthed], ...)` 即捕获"已登录"事件。
+ * - subscribe 用空函数(no-op):localStorage 在本应用生命周期内不会主动变化
+ * (登录跳转走的是另一条 navigation 路径,重新进入 splash 时会重读)。
+ *
+ * 使用模式:
+ * const { isAuthed } = useAuthGate();
+ * useEffect(() => { if (isAuthed) router.replace(ROUTES.chat); }, [isAuthed]);
+ * if (!isAuthed) return ; // SSR / 水合首帧 / 未登录 都会落在这
+ *
+ * 注:本轮不暴露 `ready` 标志——`useSyncExternalStore` 自身的 SSR / 客户端差异
+ * 处理已经能保证水合首帧拿到 `false`(与 SSR 一致),无需调用方再判 ready。
+ */
+
+import { useSyncExternalStore } from "react";
+
+import { authStorage } from "@/data/storage/auth_storage";
+
+export interface AuthGateState {
+ /** 已登录(持有非空 `cozsweet.loginToken`) */
+ isAuthed: boolean;
+}
+
+// 静态 subscribe:localStorage 在本应用生命周期内不会主动变化。
+const subscribe = (): (() => void) => () => {};
+
+const getSnapshot = (): boolean => authStorage.hasLoginToken();
+
+const getServerSnapshot = (): boolean => false;
+
+export function useAuthGate(): AuthGateState {
+ const isAuthed = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
+ return { isAuthed };
+}
diff --git a/src/lib/index.ts b/src/lib/index.ts
new file mode 100644
index 00000000..d3f39631
--- /dev/null
+++ b/src/lib/index.ts
@@ -0,0 +1,10 @@
+/**
+ * `src/lib/` 手写 barrel
+ *
+ * 注意:故意**不**把 `src/lib/auth/use-auth-gate.tsx`(Client-only)放进来,
+ * 避免 Server bundle 误把 React 客户端代码拉进去。
+ * 如需导入 hook,请直接 `import { useAuthGate } from "@/lib/auth/use-auth-gate";`。
+ */
+
+export * from "./routes";
+export * from "./auth/route-guards";
diff --git a/src/lib/routes.ts b/src/lib/routes.ts
new file mode 100644
index 00000000..34f20772
--- /dev/null
+++ b/src/lib/routes.ts
@@ -0,0 +1,51 @@
+/**
+ * 类型化路由常量
+ *
+ * 集中管理应用中使用的路由路径,与 Flutter `lib/router/app_router.dart` 的
+ * `AppRoutes` 一一对应。被服务端组件、客户端组件、`src/proxy.ts` 三端共用。
+ *
+ * 关键设计:
+ * - `as const` 让每个值都是字符串字面量类型,可用于 `PageProps<'/literal'>`。
+ * - 不导出 Client-only 模块(`use-auth-gate` 走独立路径导入),保证 Server bundle 不被污染。
+ * - 动态深链通过 `ROUTE_BUILDERS` 函数生成,避免手拼字符串。
+ */
+
+/** 静态路由字面量 */
+export const ROUTES = {
+ root: "/",
+ splash: "/splash",
+ chat: "/chat",
+ auth: "/auth",
+ sidebar: "/sidebar",
+} as const;
+
+export type StaticRoute = (typeof ROUTES)[keyof typeof ROUTES];
+
+/** 动态深链构造器 */
+export const ROUTE_BUILDERS = {
+ chatDeviceId: (deviceId: string): `/chat/deviceid/${string}` =>
+ `/chat/deviceid/${encodeURIComponent(deviceId)}` as const,
+} as const;
+
+/** 仅未登录态可见的路由(命中后已登录用户应跳走) */
+export const AUTH_ONLY_ROUTES: readonly StaticRoute[] = [
+ ROUTES.splash,
+ ROUTES.auth,
+] as const;
+
+/** 公开路由(游客态也允许访问) */
+export const PUBLIC_ROUTES: readonly StaticRoute[] = [
+ ROUTES.chat,
+ ROUTES.sidebar,
+] as const;
+
+/** 所有静态路由,供 `proxy.ts` matcher 与未来 sitemap 使用 */
+export const ALL_STATIC_ROUTES: readonly StaticRoute[] = [
+ ROUTES.splash,
+ ROUTES.chat,
+ ROUTES.auth,
+ ROUTES.sidebar,
+] as const;
+
+/** 联合路由类型,包含静态路由与已知动态路由 */
+export type Route = StaticRoute | `/chat/deviceid/${string}`;
diff --git a/src/providers/root-providers.tsx b/src/providers/root-providers.tsx
new file mode 100644
index 00000000..f4ae1fad
--- /dev/null
+++ b/src/providers/root-providers.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+/**
+ * 根级 Client Providers 包装
+ *
+ * 本轮只做两件事:
+ * 1. 透传 `children`,让 `app/layout.tsx` 保持 Server Component 的同时,
+ * 在子树中按需渲染 Client-only 组件(如未来 ``)。
+ * 2. 预置一个 `` 挂载点,供后续 toast 系统
+ * 通过 `createPortal(..., document.getElementById("toast-portal"))` 渲染。
+ *
+ * 不引入任何运行时依赖;不挂载任何 Context。
+ */
+
+import type { ReactNode } from "react";
+
+export interface RootProvidersProps {
+ children: ReactNode;
+}
+
+export function RootProviders({ children }: RootProvidersProps) {
+ return (
+ <>
+ {children}
+
+ >
+ );
+}
diff --git a/src/proxy.ts b/src/proxy.ts
new file mode 100644
index 00000000..f9836388
--- /dev/null
+++ b/src/proxy.ts
@@ -0,0 +1,62 @@
+/**
+ * Next.js 16 Proxy(原 `middleware.ts`)
+ *
+ * 行为:
+ * - 当请求携带 `login_token` cookie 且命中 `/splash` 或 `/auth` 时,308 重定向到 `/chat`。
+ * - 其他情况透传(`NextResponse.next()`)。
+ *
+ * 重要约束:
+ * - **不**对 `/chat` 做反向重定向:游客态是合法用户(`auth_refresh_interceptor`
+ * 同时处理登录与游客 token),proxy 端无法识别 `guest_token`。
+ * - 当前没有 `login_token` cookie 被写入(token 走 localStorage),proxy 实际是 no-op;
+ * 保留骨架便于未来 HttpOnly Cookie 迁移(`src/data/storage/auth/auth_storage.ts`
+ * 的 TODO 标记)时无需重构 proxy。
+ * - 边缘代理**读不到 localStorage**,因此真正的鉴权门是 Client 侧的
+ * `useAuthGate` + `authStorage.hasLoginToken()`,proxy 只是 CDN 友好的"乐观"快路径。
+ *
+ * Next 16 重要变更(参见 `node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/proxy.md`):
+ * - 文件从 `middleware.ts` 重命名为 `proxy.ts`;`middleware` 仍可用但已 deprecated。
+ * - 默认 Node.js runtime;**不**支持 `runtime` 配置(设置会抛错)。
+ * - 函数导出名建议为 `proxy`(可默认导出)。
+ *
+ * 用 `src/` 时 proxy 必须放在 `src/` 内(参见 `src-folder.md`)。
+ */
+
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+
+import { resolveAuthedRedirect } from "@/lib/auth/route-guards";
+
+const LOGIN_COOKIE = "login_token";
+
+/**
+ * Proxy 入口
+ */
+export function proxy(request: NextRequest) {
+ const { pathname } = request.nextUrl;
+ const hasLoginCookie = Boolean(request.cookies.get(LOGIN_COOKIE)?.value);
+
+ if (hasLoginCookie) {
+ const target = resolveAuthedRedirect(pathname);
+ if (target) {
+ const url = request.nextUrl.clone();
+ url.pathname = target;
+ // 308 = 永久。注销后若浏览器/CDN 缓存了 308,可能黏住。
+ // 若担心可改 307(临时);本轮按 plan 口径用 308 并在 plan 中记录风险。
+ return NextResponse.redirect(url, 308);
+ }
+ }
+
+ return NextResponse.next();
+}
+
+/**
+ * 匹配器:排除 API、Next 静态资源、图标与常见静态文件扩展名。
+ * 不排除 `_next/data`,因为 proxy 文档明确说明"即使在排除列表中 proxy 仍会跑"
+ * (安全兜底)。
+ */
+export const config = {
+ matcher: [
+ "/((?!api|_next/static|_next/image|_next/data|favicon.ico|icons|.*\\.(?:png|jpg|jpeg|gif|svg|webp|ico|css|js|woff2?|ttf|otf)$).*)",
+ ],
+};