约定式路由
@feoe/fs-router 采用基于文件系统的约定式路由,通过文件和文件夹的组织结构自动生成路由配置。
基本概念
约定式路由是一种通过文件系统结构来定义路由的方式,无需手动配置路由表。每个文件和文件夹都有特定的含义和作用。
文件命名约定
页面文件 (page.tsx)
page.tsx
文件定义路由的页面组件:
src/routes/page.tsx → /
src/routes/about/page.tsx → /about
src/routes/user/profile/page.tsx → /user/profile
布局文件 (layout.tsx)
layout.tsx
文件定义路由段的布局组件:
src/routes/layout.tsx → 根布局
src/routes/user/layout.tsx → /user/* 的布局
src/routes/admin/layout.tsx → /admin/* 的布局
错误文件 (error.tsx)
error.tsx
文件定义错误边界组件:
src/routes/error.tsx → 根错误边界
src/routes/user/error.tsx → /user/* 的错误边界
加载文件 (loading.tsx)
loading.tsx
文件定义加载状态组件:
src/routes/loading.tsx → 根加载状态
src/routes/user/loading.tsx → /user/* 的加载状态
目录结构规范
基础结构
src/routes/
├── layout.tsx # 根布局
├── layout.data.ts # 根布局数据请求,仅支持 DataRouter
├── page.tsx # 首页 (/)
├── page.data.ts # 首页数据请求,仅支持 DataRouter
├── loading.tsx # 根加载状态
├── error.tsx # 根错误边界
└── not-found.tsx # 404 页面
嵌套路由
src/routes/
├── layout.tsx
├── page.tsx # /
├── about/
│ └── page.tsx # /about
├── blog/
│ ├── layout.tsx # /blog/* 布局
│ ├── page.tsx # /blog
│ ├── [slug]/
│ │ └── page.tsx # /blog/:slug
│ └── category/
│ ├── page.tsx # /blog/category
│ └── [name]/
│ └── page.tsx # /blog/category/:name
└── user/
├── layout.tsx # /user/* 布局
├── page.tsx # /user
├── profile/
│ └── page.tsx # /user/profile
└── settings/
└── page.tsx # /user/settings
动态路由
单个参数
使用方括号 []
创建动态路由段:
src/routes/user/[id]/page.tsx → /user/:id
src/routes/post/[slug]/page.tsx → /post/:slug
多个参数
src/routes/blog/[category]/[slug]/page.tsx → /blog/:category/:slug
src/routes/user/[id]/post/[postId]/page.tsx → /user/:id/post/:postId
可选参数
使用双方括号 [[]]
创建可选参数:
src/routes/shop/[[category]]/page.tsx → /shop 和 /shop/:category
src/routes/docs/[[...slug]]/page.tsx → /docs 和 /docs/*
捕获所有路由
使用三个点 ...
捕获所有剩余路径:
src/routes/docs/[...slug]/page.tsx → /docs/*
src/routes/api/[...path]/page.tsx → /api/*
路由生成规则
路径映射
文件路径 |
生成路由 |
说明 |
page.tsx |
/ |
根路由 |
about/page.tsx |
/about |
静态路由 |
[id]/page.tsx |
/:id |
动态参数 |
[[id]]/page.tsx |
/ 和 /:id |
可选参数 |
[...slug]/page.tsx |
/* |
捕获所有 |
布局嵌套
布局文件会自动包装其子路由:
src/routes/
├── layout.tsx # 包装所有路由
├── page.tsx # /
└── user/
├── layout.tsx # 包装 /user/* 路由
├── page.tsx # /user
└── profile/
└── page.tsx # /user/profile
生成的路由结构:
1{
2 path: '/',
3 element: <RootLayout />,
4 children: [
5 {
6 index: true,
7 element: <HomePage />
8 },
9 {
10 path: 'user',
11 element: <UserLayout />,
12 children: [
13 {
14 index: true,
15 element: <UserPage />
16 },
17 {
18 path: 'profile',
19 element: <UserProfilePage />
20 }
21 ]
22 }
23 ]
24}
特殊文件
根文件
layout.tsx
- 应用根布局
layout.data.ts
- 应用根布局数据请求,仅支持 DataRouter
page.tsx
- 首页 (/)
page.data.ts
- 首页数据请求,仅支持 DataRouter
error.tsx
- 全局错误边界
loading.tsx
- 全局加载状态
not-found.tsx
- 404 页面
路由组
使用括号 ()
创建路由组,不影响 URL 结构:
src/routes/
├── (auth)/
│ ├── login/
│ │ └── page.tsx # /login
│ └── register/
│ └── page.tsx # /register
└── (dashboard)/
├── layout.tsx # 仅包装 dashboard 相关路由
├── analytics/
│ └── page.tsx # /analytics
└── settings/
└── page.tsx # /settings
文件导出约定
默认导出
页面和布局组件必须使用默认导出:
1// ✅ 正确
2export default function HomePage() {
3 return <div>首页</div>
4}
5
6// ❌ 错误
7export function HomePage() {
8 return <div>首页</div>
9}
数据加载
可以导出 loader
函数进行数据预加载:
1// src/routes/user/[id]/page.tsx
2export async function loader({ params }) {
3 const user = await fetchUser(params.id)
4 return { user }
5}
6
7export default function UserPage() {
8 const { user } = useLoaderData()
9 return <div>{user.name}</div>
10}
操作处理
可以导出 action
函数处理表单提交:
1// src/routes/contact/page.tsx
2export async function action({ request }) {
3 const formData = await request.formData()
4 await sendEmail(formData)
5
6 return redirect('/thank-you')
7}
8
9export default function ContactPage() {
10 return (
11 <Form method="post">
12 {/* 表单内容 */}
13 </Form>
14 )
15}
最佳实践
1. 保持结构清晰
✅ 推荐
src/routes/
├── layout.tsx
├── page.tsx
├── about/
│ └── page.tsx
└── user/
├── layout.tsx
├── page.tsx
└── [id]/
└── page.tsx
❌ 不推荐
src/routes/
├── index.tsx
├── about.tsx
└── user-detail.tsx
2. 合理使用布局
为相关页面创建共享布局:
1// src/routes/admin/layout.tsx
2export default function AdminLayout() {
3 return (
4 <div className="admin-layout">
5 <AdminSidebar />
6 <main>
7 <Outlet />
8 </main>
9 </div>
10 )
11}
3. 错误边界分层
在不同层级设置错误边界:
src/routes/
├── error.tsx # 全局错误
├── user/
│ ├── error.tsx # 用户模块错误
│ └── [id]/
│ └── page.tsx
4. 组件复用
将可复用组件放在 src/components
文件夹:
src/
├── components/ # 组件文件
│ ├── Header.tsx
│ ├── Footer.tsx
│ └── Sidebar.tsx
├── routes/ # 路由文件
│ ├── layout.tsx
│ └── page.tsx