Next.js 生产栈
构建和维护 Next.js 应用程序的有主见默认配置。在搭建新项目或扩展现有项目时,请遵循以下约定,除非用户的现有代码库明显采用不同做法——当存在冲突时,匹配现有模式而非本文档,并简要说明偏差。
0. 栈概览
| 层 | 推荐(首选,非强制) | 如果要求更改时可提供的选项 |
|---|---|---|
| 框架 | Next.js (App Router)、TypeScript、服务器组件 + 服务器动作 | 不需要,这是核心 |
| 包管理器 | pnpm | npm、yarn、bun |
| UI | Tailwind CSS、shadcn/ui、react-icons、Motion (motion.dev) | Radix UI 或 Mantine、lucide-react、CSS Modules |
| 表单 | React Hook Form + Zod | TanStack Form、Valibot(替代 Zod) |
| 数据库 | PostgreSQL + Drizzle ORM | Prisma、Kysely;小型应用可用 MySQL 或 SQLite |
| 认证 | Better Auth、RBAC | Auth.js、Clerk、Supabase Auth |
| 媒体 | Cloudinary | UploadThing、S3 + CDN、Vercel Blob |
| 测试 | Vitest(单元/集成)、Playwright(端到端) | Jest、Cypress |
| 监控 | Sentry、结构化日志 | Datadog、Highlight、Axiom + pino |
| 代码检查/格式化 | ESLint、Prettier、TypeScript strict | Biome |
| CI/CD | GitHub + GitHub Actions、Vercel、预览部署 | GitLab CI、Netlify、Cloudflare Pages |
切勿静默地将表中的包替换为替代方案(例如用 Prisma 代替 Drizzle,用 next-auth 代替 Better Auth)。如果用户的项目已使用不同选择,请继续使用并标记不一致之处,而不是混合两种解决方案。
这些工具是推荐使用的:用户偏好它们,但并非强制。
- 当后续章节提到特定工具(pnpm、Drizzle、Better Auth、Cloudinary、Sentry 等)时,请将其视为本规则下的推荐默认值。
- 在更改、替换或跳过表中的任何工具之前,请先询问用户。提供选项:保留推荐工具,或使用最后一列的替代方案(或其他最佳实践),为每个选项提供一行权衡说明以及您自己的推荐。
- 在推荐替代方案之前,请检查当前状态(维护情况、重大更改)。不要仅凭记忆。
- 在获得答案之前,切勿更改工具。在用户回答之前,请保留推荐的默认值。
1. 项目设置
- 使用
pnpm create next-app@latest搭建——启用 TypeScript、App Router、Tailwind、ESLint,拒绝src/目录,使app/保留在项目根目录(仅限新项目)。 - 已使用
src/的现有项目: 保留src/——src/app/、src/components/、src/lib/、src/actions/。仅在项目没有src/目录时使用app/。遵循现有布局和导入别名(@/*→src/*);切勿在不相关的任务中将文件移出src/。 - 每个安装/运行命令都使用 pnpm(
pnpm add、pnpm dlx、pnpm dev)。切勿混入 npm/yarn 锁文件。 - 通过
pnpm dlx shadcn@latest init初始化 shadcn/ui,然后按需添加组件(pnpm dlx shadcn@latest add button),而不是预安装整套组件。 - 基础文件夹结构(无
src/——app/位于项目根目录):
# 新项目(无 src/)
app/ # 路由、布局、error/loading/not-found 边界
components/ # 共享 UI(ui/ = shadcn 原语)
lib/ # 数据库、认证、验证、工具函数、速率限制、日志
actions/ # 按领域分组的服务器动作
hooks/
types/
drizzle/ # 迁移
现有 src/ 项目:在 src/ 下保持相同结构(src/app/、src/components/、src/lib/、src/actions/)——文件夹名称完全相同,仅前缀更改。
2. TypeScript 与代码质量
- tsconfig 中
strict: true,无any(除非有注释证明其合理性),无@ts-ignore(除非有链接的问题或理由)。 - 配置 ESLint(next/core-web-vitals + typescript-eslint)和 Prettier,并在代码被视为完成之前通过检查;运行它们,而不仅仅是假设。
- 优先为导出的函数和服务器动作添加显式返回类型。
- 保持服务器组件为默认;仅在需要交互性、浏览器 API 或 hooks 时添加
"use client",并尽可能将边界推低(到叶子级别)。 - 每个文件/模块一个职责;将路由的服务器动作放在
actions/或路由旁边的_actions.ts文件中,而不是内联在巨大的页面文件中。 - 架构约定: 每种逻辑保持在一个位置——数据访问在
lib/db,变更操作在actions/,共享 UI 在components/,横切助手在lib/。遵循项目现有的命名约定(文件大小写、组件命名),而不是中途引入新约定。 - 代码审查: 将每个非琐碎的更改视为 PR 规模——清晰的差异,在请求审查之前通过 lint/类型检查/测试,并描述更改内容和原因。在审查时(人工或 AI 辅助),检查缺失的身份验证检查、未处理的错误和约定偏离,而不仅仅是代码是否运行。
3. 渲染、数据与性能
- 静态与动态渲染: 默认静态;仅在路由真正需要每次请求的数据时选择动态(
export const dynamic = 'force-dynamic'、cookies()、headers())。 - 数据获取: 直接在服务器组件中获取;使用服务器动作进行变更,而不是客户端获取到 API 路由(除非消费者是外部的)。
- 流式传输与 Suspense: 将慢速/可流式传输的部分包裹在
<Suspense>中,使用真实的骨架回退(而非仅旋转器),以便页面的其余部分可以立即渲染和流式传输——这也就是loading.tsx在路由级别提供的功能。 - 缓存与重新验证: 对昂贵的读取使用
use cache/unstable_cache(根据 Next.js 版本);在变更后精确地使用revalidatePath/revalidateTag进行失效,而不是使用force-dynamic来“修复”过时数据。 - 预取: 预取用户可能点击的链接——
<Link>会自动执行此操作;除非有特定原因,否则不要禁用它(prefetch={false})。 - 懒加载与代码分割: 对折叠下方或很少使用的客户端组件使用
next/dynamic进行懒加载;避免仅在一个页面上使用的大型客户端捆绑包。 - 图像与字体优化: 始终对光栅图像使用
next/image(显式width/height或fill+ 尺寸容器),对字体使用next/font——切勿使用原始的<img>或指向 Google Fonts 的<link>。 - 核心 Web 指标: 将 LCP、CLS、INP 视为真实约束——没有布局偏移的后期内容,没有未调整大小的媒体,没有阻塞同步脚本。
4. UI
- 使用 Tailwind 进行样式设计;shadcn/ui 组件作为基础原语,通过项目的主题令牌(CSS 变量)进行自定义,而不是在文件中分散使用一次性内联覆盖。
- react-icons 用于图标(每个项目选择一个图标集,例如
react-icons/lu或/hi2,并保持一致)。 - Motion (motion.dev,前身为 Framer Motion) 用于动画;保持动画组件为客户端且小型,并尊重
prefers-reduced-motion。 - 默认进行响应式设计(移动端优先的 Tailwind 断点);不要提供仅限桌面的布局。
5. 表单与验证
- 每个表单/数据结构定义一个 Zod schema;对客户端验证(通过
@hookform/resolvers/zod)和服务器动作内的服务器端验证都复用相同的 schema——绝不信任仅客户端的验证。 - React Hook Form (
useForm) 驱动客户端状态;提交时,调用服务器动作,从动作的结构化返回值(而非导致通用错误的抛出异常)中显示字段错误。 - 在启动时使用 Zod schema 验证
process.env本身(一个lib/env.ts),以便缺失或错误的配置快速失败,而不是在首次使用时才出错。
6. 数据库(PostgreSQL + Drizzle)
- Schema 位于
lib/db/schema.ts(或按领域拆分),迁移通过drizzle-kit generate生成,并通过drizzle-kit migrate应用——切勿手动编辑生成的迁移文件。 - 在无服务器/边缘上下文中使用池化连接(例如通过驱动程序的池或 Neon/Supabase 池化);不要为每个请求打开没有池化的新客户端。
- 查询优化与 N+1 预防: 使用 Drizzle 的关系查询(
db.query.x.findMany({ with: {...} }))或显式连接,而不是循环和逐行查询;只选择您需要的列,而不是select *风格的全行获取;对可能返回无限结果集的查询进行分页/限制。 - 对
WHERE/ORDER BY/连接中使用的列建立索引;在 schema 中添加索引,而不是事后才考虑。 - 为生产数据库记录备份/恢复方案(至少是托管提供商的快照)——如果用户的设置没有,请指出这一点。
7. 认证与安全
- 使用 Better Auth 进行认证(会话、OAuth 提供商、电子邮件/密码根据需要);绝不手动编写密码哈希或会话令牌。
- RBAC:显式建模角色/权限(一个
role列加上一个小型权限检查辅助函数),并在 服务器动作/数据访问层 中强制执行授权,而不仅仅是隐藏 UI——隐藏的按钮不是访问控制。 - 对敏感端点进行速率限制(认证、表单提交、任何昂贵的操作)——例如通过 Upstash Redis 或等效的令牌桶/滑动窗口限制器。
- 设置安全头(通过
next.config.js头或中间件):Content-Security-Policy、X-Frame-Options、Referrer-Policy、Permissions-Policy、Strict-Transport-Security。 - Cookie:在所有认证/会话 Cookie 上设置
httpOnly、secure、sameSite: 'lax'(或'strict',如果可行)。 - 在边界处使用 Zod 验证每个外部输入(表单数据、查询参数、webhook 负载),然后再触及业务逻辑或数据库。
- 绝不记录密钥、令牌或包含凭据的完整请求体。
8. SEO
- 每条路由使用 Metadata API(
generateMetadata/ 静态metadata导出)——标题、描述、Open Graph、Twitter 卡片。 - 提供从真实路由数据生成的
app/sitemap.ts和app/robots.ts,而不是硬编码的陈旧列表。 - 通过元数据
alternates.canonical设置规范 URL,尤其是对于可以通过多个查询字符串变体访问的任何路由。 - 为受益的内容类型(文章、产品、面包屑导航、组织)添加 JSON-LD 结构化数据,使用
type="application/ld+json"的 script 标签。
9. 无障碍
- 语义 HTML 优先(
button、nav、main、header、label)——仅当语义 HTML 无法覆盖真实缺口时才使用 ARIA。 - 所有内容均可通过键盘单独到达和操作;可见的焦点状态(不要在没有替换的情况下移除
:focus-visible轮廓)。 - 每个图像都有有意义的
alt(仅对真正装饰性的图像使用空alt="")。 - 每个表单输入都有真正的关联
<label>(而不仅仅是占位符)。 - 检查文本和交互元素与 WCAG AA 的最低色彩对比度。
10. 错误处理与加载状态
每个可能失败或耗时的路由段都应具有:
error.tsx——段级错误边界,带有重试操作。global-error.tsx——error.tsx无法捕获的错误的高级回退。not-found.tsx——用于 404,包括数据查找中的notFound()调用。loading.tsx——Suspense 回退;使其成为匹配最终布局的真实骨架,而不是裸旋转器。
11. 测试
- Vitest 用于单元测试(纯函数、schema 验证、工具逻辑)和组件测试(React Testing Library)——放在
*.test.ts(x)或__tests__/下。 - Playwright 用于端到端流程:认证、关键用户旅程、表单提交。针对预览部署或本地构建运行,而不是针对生产环境。
- 新的服务器动作和验证 schema 应附带至少一个基本测试;不要让关键业务逻辑默认未经测试。
12. 监控与日志记录
- Sentry 用于客户端和服务器的错误跟踪(instrumentation 文件 +
sentry.client.config/sentry.server.config),部署时上传 source map。 - 结构化日志(JSON 格式,包含请求 ID、路由、用户 ID(如果可用)),而不是临时的
console.log。 - 在生产环境中跟踪核心 Web 指标(Vercel Analytics 或 Sentry Performance),并在部署的 URL 上设置正常运行时间监控。
13. DevOps
- Git 具有常规的范围提交;受保护的
main分支。 - 每个 PR 上的 GitHub Actions CI:安装 → 代码检查 → 类型检查 → 单元测试 → 构建(较大更改添加端到端测试)。
- 部署到 Vercel;每个 PR 在合并前都有一个预览部署。
- 环境变量在 Vercel 中按环境管理,在启动时通过第 5 节的 Zod env schema 进行验证。
14. 媒体
- Cloudinary 用于用户上传的动态媒体:通过签名、服务器颁发的上传参数上传(绝不公开 API 密钥到客户端),并通过 Cloudinary 的转换 URL 提供,而不是重新实现调整大小。
15. Claude 的工作约定
当被要求在使用此栈的项目中添加功能时:
- 在创建新 schema/动作/组件之前检查现有的——扩展,不要重复。
- 新的数据库字段 → 更新
schema.ts,生成迁移,不要手动编辑 SQL。 - 新表单 → Zod schema → RHF 表单 → 带有服务器端重新验证和授权检查的服务器动作。
- 新路由 → 有意决定静态还是动态;如果缺少
loading.tsx/error.tsx,添加它们;添加元数据。 - 变更后,重新验证受影响的具体路径/标签——不要全面重新验证整个应用。
- 在提供代码之前,在严格模式下进行 lint/类型检查的心理审查;标记任何
any、未检查的输入或缺失的身份验证检查,而不是静默交付。 - 在引入、替换或跳过第 0 节中的任何工具之前,向用户提供选项(参见第 0 节中的“推荐,非强制”规则)。
16. 配套规则文件
AGENTS.md——项目工作流程规则(主流程、功能流程、审批流程……)
…
[为翻译截断]