# React SPA Template
这是一个用于后台系统或通用 SPA 项目的前端基础脚手架。它已经接好了以下底座能力:
- `Vite + React 19 + TypeScript`
- `TanStack Router` 文件路由
- `TanStack Query + ky` 请求与缓存
- `Zustand` 会话状态基座
- `Tailwind CSS` 原子化样式
- `i18next` 多语言
- `Head / Metadata` 动态标题与元信息
- `Biome + Husky + commitlint` 代码规范与提交规范
这个模板现在已经被“净化”为一个空白 scaffold。
你拿到它之后,应该在这个基础上替换成自己的页面、接口、登录逻辑和测试,而不是继续沿用示例业务。
## 1. 快速开始
```bash
pnpm install
pnpm dev
```
常用命令:
```bash
pnpm dev
pnpm build
pnpm lint
pnpm lint:fix
pnpm generate-routes
pnpm commit
```
## 2. 目录结构
核心目录说明:
- `src/routes`
TanStack Router 文件路由目录。你新增页面,优先在这里建文件。
- `src/lib/api`
通用请求层。`api-client.ts` 是所有接口调用的基础入口。
- `src/lib/auth`
会话初始化、401 清理、refresh token、受保护路由 helper。
- `src/lib/head`
页面标题、description、Open Graph、Twitter metadata。
- `src/lib/query`
TanStack Query 全局默认配置。
- `src/store`
全局状态。目前主要是认证状态。
- `src/styles.css`
全局 Tailwind 入口。当前项目只保留这一份样式文件。
- `src/locales`
多语言文案。
## 3. 新项目落地后先改哪里
如果你要基于这个模板开一个新项目,建议按这个顺序改:
### 第一步:改首页
先改这个文件:
- `src/routes/$lang/index.tsx`
这是当前默认首页。通常你会:
- 改成你的项目欢迎页
- 或直接改成 dashboard / 工作台入口
- 或拆成自己的页面布局
同时对应修改多语言文案:
- `src/locales/zh-CN/common.ts`
- `src/locales/en-US/common.ts`
### 第二步:改接口基础地址
先看这些文件:
- `.env.development`
- `.env.production`
- `src/vite-env.d.ts`
至少确认这些变量:
- `VITE_APP_ENV`
- `VITE_API_BASE_URL`
- `VITE_ENABLE_QUERY_DEVTOOLS`
- `VITE_ENABLE_REQUEST_LOG`
### 第三步:改全局样式入口
当前模板已经切到 Tailwind CSS,并移除了旧的 `App.css` / `index.css`。
你主要会改:
- `src/styles.css`
这里通常只放:
- `@import "tailwindcss";`
- 少量全局 base 样式
- 你的主题变量
### 第四步:接你的业务模块
建议按“功能模块”建目录,而不是把所有请求、类型和 hooks 混在一起。
例如:
```text
src/features/user/api/user-api.ts
src/features/user/hooks/use-current-user.ts
src/features/user/types/user.ts
src/routes/$lang/users/index.tsx
```
### 第五步:接你的登录体系
优先改这些文件:
- `src/store/auth-store.ts`
- `src/lib/auth/auth-session.ts`
- `src/lib/api/api-client.ts`
## 4. Head / Metadata 怎么用
模板已经提供了一个通用 hook:
- `src/lib/head/document-metadata.ts`
在页面组件里直接调用:
```tsx
import { useDocumentMetadata } from '@/lib/head/document-metadata'
function UserPage() {
useDocumentMetadata({
title: '用户管理',
description: '用户管理页面',
})
return
User Page
}
```
它会自动更新:
- `document.title`
- `meta[name="description"]`
- `meta[name="robots"]`
- `og:title`
- `og:description`
- `twitter:title`
- `twitter:description`
适用场景:
- 列表页标题
- 详情页标题
- 登录页 / 404 页
- 分享卡片基础描述
## 5. 登录怎么接
这个模板的认证层不是“现成登录系统”,而是“认证骨架”。
它已经有:
- `accessToken`
- `refreshToken`
- `currentUser`
- `status`
- `401` 后清会话
- refresh token 的可插拔入口
核心文件:
- `src/store/auth-store.ts`
- `src/lib/auth/auth-session.ts`
- `src/lib/api/api-client.ts`
### 5.1 登录成功后怎么写入会话
假设你的登录接口返回:
```ts
{
accessToken: string
refreshToken: string
user: {
id: string
name: string
}
}
```
那么登录成功后可以这样写:
```ts
import { useAuthStore } from '@/store/auth-store'
useAuthStore.getState().startSession({
accessToken: response.accessToken,
refreshToken: response.refreshToken,
currentUser: response.user,
})
```
### 5.2 刷新 token 怎么接
模板里预留了 refresh 的注册入口:
```ts
import { registerRefreshSessionHandler } from '@/lib/auth/auth-session'
registerRefreshSessionHandler(async (refreshToken) => {
const response = await fetch('/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refreshToken }),
}).then((result) => result.json())
return {
accessToken: response.accessToken,
refreshToken: response.refreshToken,
currentUser: response.user ?? null,
}
})
```
当请求返回 `401` 时,请求层会:
1. 尝试执行这个 refresh handler
2. 如果 refresh 成功,则自动重试原请求一次
3. 如果 refresh 失败,则清空本地会话
### 5.3 当前用户初始化怎么接
如果你的项目在刷新页面后,需要根据已有 token 主动请求一次“当前用户信息”,可以注册:
```ts
import { registerCurrentUserInitializer } from '@/lib/auth/auth-session'
registerCurrentUserInitializer(async () => {
const response = await fetch('/auth/me').then((result) => result.json())
return response.user
})
```
这样在应用启动时,模板会在已有 token 且尚未拿到 `currentUser` 的情况下,自动初始化一次用户信息。
## 6. 怎么加受保护路由
模板已经提供了路由保护 helper:
- `src/lib/auth/require-auth.ts`
用法是,在路由的 `beforeLoad` 里调用它。
示例:
```tsx
import { createFileRoute } from '@tanstack/react-router'
import { requireAuthenticatedSession } from '@/lib/auth/require-auth'
export const Route = createFileRoute('/$lang/dashboard')({
beforeLoad: async () => {
await requireAuthenticatedSession()
},
component: DashboardPage,
})
function DashboardPage() {
return Dashboard
}
```
它的行为是:
- 如果当前已经登录,正常进入页面
- 如果当前未登录,重定向到 `/$lang`
如果你想把未登录用户导向专门的登录页,可以直接修改:
- `src/lib/auth/require-auth.ts`
把重定向目标从 `/$lang` 改成你的登录页路由,例如 `/$lang/login`。
## 7. 怎么写第一个接口模块
推荐模式是:
1. 在 `src/features/.../types` 定义类型
2. 在 `src/features/.../api` 写请求函数
3. 在 `src/features/.../hooks` 写 query / mutation hook
4. 在 `src/routes/...` 页面里消费
示例:
```ts
// src/features/user/api/user-api.ts
import { api } from '@/lib/api/api-client'
export interface CurrentUser {
id: string
name: string
}
export function getCurrentUser() {
return api.get('users/me').then((response) => response.data)
}
```
```ts
// src/features/user/hooks/use-current-user.ts
import { useQuery } from '@tanstack/react-query'
import { getCurrentUser } from '@/features/user/api/user-api'
import type { ApiError } from '@/lib/api/api-error'
export function useCurrentUserQuery() {
return useQuery>, ApiError>({
queryKey: ['current-user'],
queryFn: getCurrentUser,
})
}
```
## 8. 路由相关说明
这个模板使用 TanStack Router 文件路由。
路由目录:
- `src/routes`
生成文件:
- `src/routeTree.gen.ts`
你通常不需要手改 `src/routeTree.gen.ts`。
新增、删除、修改路由文件后,执行:
```bash
pnpm generate-routes
```
`pnpm dev` 和 `pnpm build` 也都会自动先生成一次路由树。
## 9. 代码规范和提交流程
## 9.1 Tailwind 说明
当前模板使用的是官方 Vite 接法:
- `tailwindcss`
- `@tailwindcss/vite`
核心文件:
- `vite.config.ts`
- `src/styles.css`
新增页面时,优先直接写 Tailwind utility class,而不是再恢复成多个分散的 CSS 文件。
格式化与 lint:
```bash
pnpm lint
pnpm lint:fix
pnpm format
```
提交规范:
```bash
pnpm commit
```
仓库已经包含:
- `Biome`
- `Husky`
- `lint-staged`
- `commitlint`
- `Commitizen`
## 10. 你后续最可能改动的文件
如果你刚接手这个模板,优先关注这几个文件:
- `src/routes/$lang/index.tsx`
- `src/routes/$lang/route.tsx`
- `src/lib/api/api-client.ts`
- `src/lib/auth/auth-session.ts`
- `src/lib/auth/require-auth.ts`
- `src/store/auth-store.ts`
- `src/lib/head/document-metadata.ts`
- `src/locales/zh-CN/common.ts`
- `src/locales/en-US/common.ts`
## 11. 当前状态
当前模板已经验证通过:
- `pnpm lint`
- `pnpm build`
如果你继续往前整理,下一步通常就是:
1. 接入真实登录接口
2. 增加 `login`、`dashboard`、`403` 等实际页面
3. 按业务模块拆分 `features`
4. 按项目实际需要补充测试方案