Next.js 最佳实践
适用于 Next.js App Router 开发的原则。
1. 服务端组件 vs 客户端组件
决策树
是否需要……?
│
├── useState、useEffect、事件处理函数
│ └── 客户端组件('use client')
│
├── 直接数据获取,无需交互
│ └── 服务端组件(默认)
│
└── 两者都需要?
└── 拆分:服务端父组件 + 客户端子组件
默认用法
| 类型 | 用途 |
|---|
| 服务端 | 数据获取、布局、静态内容 |
| 客户端 | 表单、按钮、交互式 UI |
2. 数据获取模式
获取策略
| 模式 | 用途 |
|---|
| 默认 | 静态(构建时缓存) |
| Revalidate | ISR(基于时间的刷新) |
| No-store | 动态(每次请求) |
数据流向
| 来源 | 模式 |
|---|
| 数据库 | 服务端组件 fetch |
| API | 带缓存的 fetch |
| 用户输入 | 客户端状态 + 服务端操作 |
3. 路由原则
文件约定
| 文件 | 用途 |
|---|
page.tsx | 路由 UI |
layout.tsx | 共享布局 |
loading.tsx | 加载状态 |
error.tsx | 错误边界 |
not-found.tsx | 404 页面 |
路由组织
| 模式 | 用途 |
|---|
路由组 (name) | 组织结构,不影响 URL |
并行路由 @slot | 同级别多页面渲染 |
拦截路由 (.) | 模态框叠加 |
4. API 路由
路由处理器
| 方法 | 用途 |
|---|
| GET | 读取数据 |
| POST | 创建数据 |
| PUT/PATCH | 更新数据 |
| DELETE | 删除数据 |
最佳实践
- 使用 Zod 验证输入
- 返回正确的状态码
- 优雅地处理错误
- 尽可能使用 Edge 运行时
5. 性能原则
图片优化
- 使用 next/image 组件
- 为首屏内容设置 priority
- 提供模糊占位符
- 使用响应式尺寸
包体积优化
- 对重型组件使用动态导入
- 基于路由的代码分割(自动)
- 使用 bundle analyzer 分析
6. 元数据
静态 vs 动态
| 类型 | 用途 |
|---|
| 静态导出 | 固定元数据 |
| generateMetadata | 每条路由动态生成 |
必要标签
- title(50-60 字符)
- description(150-160 字符)
- Open Graph 图片
- 规范 URL
7. 缓存策略
缓存层
| 层级 | 控制方式 |
|---|
| 请求 | fetch 选项 |
| 数据 | revalidate / tags |
| 完整路由 | 路由配置 |
重新验证
| 方法 | 用途 |
|---|
| 基于时间 | revalidate: 60 |
| 按需 | revalidatePath / revalidateTag |
| 无缓存 | no-store |
8. 服务端操作
使用场景
最佳实践
- 使用 'use server' 标记
- 验证所有输入
- 返回类型化响应
- 处理错误
9. 反模式
| ❌ 不要做 | ✅ 推荐做法 |
|---|
| 到处使用 'use client' | 默认使用服务端组件 |
| 在客户端组件中 fetch | 在服务端组件中 fetch |
| 忽略加载状态 | 使用 loading.tsx |
| 忽略错误边界 | 使用 error.tsx |
| 大型客户端包 | 使用动态导入 |
10. 项目结构
app/
├── (marketing)/ # 路由组
│ └── page.tsx
├── (dashboard)/
│ ├── layout.tsx # 仪表盘布局
│ └── page.tsx
├── api/
│ └── [resource]/
│ └── route.ts
└── components/
└── ui/
记住: 服务端组件是默认选择,这是有原因的。先从服务端入手,仅在必要时添加客户端功能。