Web典型路由结构之Next.js (App Router, v13+) )(文件系统驱动的路由:File-based Routing)声明式路由:文件即路由
文章目录
- 📂 项目结构 (基于 `app` 目录)
- 🔑 核心文件实现
- 1. 根布局 (`app/layout.tsx`)
- 2. 首页 (`app/page.tsx`)
- 3. 项目列表页 (`app/projects/page.tsx`)
- 4. 项目详情页 (`app/projects/[projectId]/page.tsx`)
- 5. 设置页权限控制 (`app/settings/layout.tsx`)
- 6. 登录页 (`app/signin/page.tsx`)
- 7. 404 页面 (`app/not-found.tsx`)
- 8. 错误处理 (`app/error.tsx`)
- 🌟 与 React Router v6 的关键差异
- ✅ 为什么 Next.js 的设计更优?
- 1. **无需维护路由配置**:文件系统即路由,新增页面只需创建文件
- 2. **自动代码分割**:按路由自动分割代码块(无需 `lazy`)
- 3. **权限控制更合理**:在布局层处理(避免页面加载后再重定向)
- 4. **开发体验更好**:文件命名即路由,直观易维护
- 5. **内置 404/错误处理**:`not-found.tsx` 和 `error.tsx` 自动生效
- > 💡 提示:Next.js 的权限控制**不要**在 `page.tsx` 中做(会先加载页面再重定向),必须放在 `layout.tsx` 中(如 `settings/layout.tsx`)。
- 🚀 运行效果
- > ✨ **关键总结**:Next.js 的路由是**声明式**的(文件即路由),而 React Router 是**配置式**的(需要手动写路由表)。Next.js 的设计让路由与文件结构绑定,大幅降低维护成本,同时内置了最佳实践(权限、404、动态路由)。
以下是 Next.js (App Router, v13+) ( SSR)的典型路由结构实现,与 React Router v6( CSR) 的配置式路由不同,Next.js 采用 文件系统驱动的路由(File-based Routing),无需显式编写路由配置文件:
📂 项目结构 (基于 app
目录)
app/
├── layout.tsx # 根布局 (所有页面共享)
├── page.tsx # 根路径 / 的页面
├── projects/
│ ├── layout.tsx # 项目模块布局
│ ├── page.tsx # /projects 页面
│ └── [projectId]/ # 动态路由
│ └── page.tsx # /projects/:projectId
├── settings/
│ ├── layout.tsx # 设置页布局 (用于权限控制)
│ └── page.tsx # /settings 页面
├── signin/
│ └── page.tsx # /signin 页面
├── not-found.tsx # 404 页面 (自定义)
└── error.tsx # 全局错误页面
🔑 核心文件实现
1. 根布局 (app/layout.tsx
)
// app/layout.tsx
// 导入全局CSS样式文件(在Next.js中,全局样式通常放在这里)
import './globals.css'
// 导入Google字体(Inter字体),用于设置页面的字体
import { Inter } from 'next/font/google'// 使用Inter字体,指定子集为拉丁文(支持大部分英文字符)
const inter = Inter({ subsets: ['latin'] })// 设置页面的元数据(标题、描述等,用于SEO优化)
export const metadata = {title: 'Next.js App', // 页面标题description: 'Generated by Next.js', // 页面描述
}// 根布局组件,所有页面共享这个布局
export default function RootLayout({children,
}: {children: React.ReactNode // 代表子组件(页面内容)
}) {// 返回HTML结构,其中包含页面的根元素return (<html lang="en"> // 指定页面语言为英语<body className={inter.className}> // 将字体类名应用到body,使页面使用Inter字体<header>My App Header</header> // 页面头部(通常包含导航栏){children} // 这里是子组件(当前页面的内容)将被渲染在这里<footer>Footer</footer> // 页面底部</body></html>)
}
2. 首页 (app/page.tsx
)
// app/page.tsx
// 首页组件,处理根路径 / 的页面内容
export default function Home() {// 返回页面的主要内容return (<main> // 主要内容区域<h1>Welcome to Home</h1> // 标题<a href="/projects">Go to Projects</a> // 链接到项目列表页的链接</main>)
}
3. 项目列表页 (app/projects/page.tsx
)
// app/projects/page.tsx
// 项目列表页面组件
export default function Projects() {// 返回项目列表页面的内容return (<div> // 容器div<h1>Projects List</h1> // 项目列表标题<ul> // 无序列表<li><a href="/projects/1">Project 1</a></li> // 项目1的链接<li><a href="/projects/2">Project 2</a></li> // 项目2的链接</ul></div>)
}
4. 项目详情页 (app/projects/[projectId]/page.tsx
)
// app/projects/[projectId]/page.tsx
// 项目详情页面组件,用于显示特定项目的详细信息
import { notFound } from 'next/navigation' // 用于触发404页面的函数// 项目详情页面组件,接收参数(动态路由参数)
export default function ProjectDetail({ params }: { params: { projectId: string } }) {// 模拟项目数据(实际应用中会从API获取)const validProjects = ['1', '2', '3']// 检查传入的projectId是否有效if (!validProjects.includes(params.projectId)) {notFound() // 如果无效,触发404页面}// 返回项目详情页面return (<div> // 容器div<h1>Project Detail: {params.projectId}</h1> // 显示项目ID<a href="/projects">Back to Projects</a> // 返回项目列表的链接</div>)
}
5. 设置页权限控制 (app/settings/layout.tsx
)
关键点:权限检查放在布局层(避免页面加载后再重定向)
// app/settings/layout.tsx
import { cookies } from 'next/headers' // 用于操作HTTP cookies
import { redirect } from 'next/navigation' // 用于重定向页面// 设置页布局组件,用于权限控制
export default function SettingsLayout({children,
}: {children: React.ReactNode // 代表子组件(设置页面内容)
}) {// 从cookies中获取token(用于用户认证)const token = cookies().get('token')?.value// 如果没有token(用户未登录),重定向到登录页// encodeURIComponent确保URL安全if (!token) {redirect(`/signin?next=${encodeURIComponent('/settings')}`)}// 如果有token,渲染设置页面内容return (<div><h2>Settings</h2> // 设置页面标题{children} // 渲染子组件(实际的设置内容)</div>)
}
6. 登录页 (app/signin/page.tsx
)
// app/signin/page.tsx
import { cookies } from 'next/headers' // 用于操作HTTP cookies
import { redirect } from 'next/navigation' // 用于重定向页面// 登录页面组件
export default function SignIn() {// 模拟登录逻辑的函数const handleLogin = () => {// 设置cookie(存储token)cookies().set('token', 'dummy_token')// 从cookies中获取重定向路径(如果之前访问的是受保护页面)const next = cookies().get('next')?.value || '/'// 重定向到目标页面redirect(next)}return (<div><h1>Login</h1> // 登录页面标题<button onClick={handleLogin}>Sign In</button> // 登录按钮</div>)
}
7. 404 页面 (app/not-found.tsx
)
// app/not-found.tsx
// 404页面组件(当请求的页面不存在时显示)
export default function NotFound() {return (<main> // 主要内容区域<h1>404 - Page Not Found</h1> // 404标题<a href="/">Go Home</a> // 返回首页的链接</main>)
}
8. 错误处理 (app/error.tsx
)
// app/error.tsx
import { notFound } from 'next/navigation' // 用于触发404页面的函数// 错误处理页面组件
export default function Error() {return (<main> // 主要内容区域<h1>Something went wrong</h1> // 错误提示<button onClick={() => notFound()}>Go to 404</button> // 点击按钮跳转到404页面</main>)
}
🌟 与 React Router v6 的关键差异
特性 | React Router v6 | Next.js (App Router) |
---|---|---|
路由定义 | 显式配置文件 (routes.tsx ) | 文件系统自动映射 (app/ 目录) |
布局 | 通过 children 嵌套 | layout.tsx 文件 (自动继承) |
权限控制 | 高阶组件 (withAuth ) | 在 layout.tsx 中直接重定向 |
动态路由 | :param 语法 (/projects/:id ) | [param] 语法 (/projects/[id] ) |
404 页面 | * 通配符 + errorElement | not-found.tsx 文件 |
懒加载 | 手动 lazy + Suspense | 自动按需加载 (无需配置) |
重定向 | redirect 函数 (路由配置中) | redirect 函数 (在 layout.tsx /page.tsx 中) |
✅ 为什么 Next.js 的设计更优?
1. 无需维护路由配置:文件系统即路由,新增页面只需创建文件
2. 自动代码分割:按路由自动分割代码块(无需 lazy
)
3. 权限控制更合理:在布局层处理(避免页面加载后再重定向)
4. 开发体验更好:文件命名即路由,直观易维护
5. 内置 404/错误处理:not-found.tsx
和 error.tsx
自动生效
> 💡 提示:Next.js 的权限控制不要在 page.tsx
中做(会先加载页面再重定向),必须放在 layout.tsx
中(如 settings/layout.tsx
)。
🚀 运行效果
URL | 对应文件 | 效果 |
---|---|---|
/ | app/page.tsx | 首页 |
/projects | app/projects/page.tsx | 项目列表 |
/projects/123 | app/projects/[projectId]/page.tsx | 项目详情 (动态参数) |
/settings | app/settings/layout.tsx + page.tsx | 受保护设置页 (需登录) |
/signin | app/signin/page.tsx | 登录页 |
/non-existent | app/not-found.tsx | 自定义 404 页面 |