refactor: move ROUTES from @/lib/routes to @/router/routes

This commit is contained in:
2026-06-10 10:53:43 +08:00
parent a4b902893e
commit 2cba315214
15 changed files with 45 additions and 12 deletions
+14
View File
@@ -0,0 +1,14 @@
/**
* Router 公共导出
*
* 集中管理:
* - 路由常量(ROUTES、ROUTE_BUILDERS、AUTH_ONLY_ROUTES、PUBLIC_ROUTES、ALL_STATIC_ROUTES
* - Next.js 16 ProxyCDN 端鉴权重定向)
*
* 业务层导入示例:
* ```ts
* import { ROUTES, AUTH_ONLY_ROUTES, type Route } from "@/router";
* ```
*/
export * from "./routes";
export { proxy, config as proxyConfig } from "./proxy";
+70
View File
@@ -0,0 +1,70 @@
/**
* Next.js 16 Proxy(原 `middleware.ts`
*
* 行为:
* - 当请求携带 NextAuth `next-auth.session-token` cookie 且命中 `/splash` 或 `/auth`
* 时,308 重定向到 `/chat`。
* - 其他情况透传(`NextResponse.next()`)。
*
* 重要约束:
* - **不**对 `/chat` 做反向重定向:游客态是合法用户。
* - NextAuth 的 session cookie 由 `src/lib/auth/config.ts` 集中管理;本 proxy 仅做
* CDN 友好的"乐观"快路径。
* - 真正的鉴权门在 Client 侧 `useAuthGate()`(用 `useSession()`)。
*
* 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 {
AUTH_ONLY_ROUTES,
ROUTES,
type StaticRoute,
} from "@/router/routes";
const SESSION_COOKIE_NAMES = [
"next-auth.session-token",
"__Secure-next-auth.session-token",
];
function isAuthOnlyRoute(pathname: string): pathname is StaticRoute {
return (AUTH_ONLY_ROUTES as readonly string[]).includes(pathname);
}
/**
* Proxy 入口
*/
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// 检查 NextAuth session cookie 存在性(cookie value 存在即认为已登录)
const hasSession = SESSION_COOKIE_NAMES.some(
(name) => Boolean(request.cookies.get(name)?.value),
);
if (hasSession && isAuthOnlyRoute(pathname)) {
const url = request.nextUrl.clone();
url.pathname = ROUTES.chat;
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)$).*)",
],
};
+51
View File
@@ -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}`;