Prompt Engineering for Next.js

同样的 AI 工具,不同的提示词,产出质量可能差 10 倍。Prompt Engineering 不是"说话的艺术",而是一套可复制的工程方法——明确任务、提供上下文、约束输出格式、拆分复杂步骤。本章专门针对 Next.js 全栈开发场景,提供经过验证的提示词模板和策略。

1. 提示词基本原则

1.1 为什么"帮我写一个 XX"是糟糕的提示词

❌ "帮我写一个登录页面"

AI 不知道:
- 用什么认证方案(Clerk? NextAuth? 自建?)
- 是 Server Component 还是 Client Component?
- 用什么 UI 库(shadcn/ui? Material UI? 手写?)
- 登录方式(邮箱密码? OAuth? Magic Link?)
- 登录后跳转到哪里?
- 错误处理怎么做?

这种提示词的问题是 缺少约束——AI 会按照它训练数据中最常见的模式生成代码,而这个模式大概率不符合你的项目。

1.2 CRAFT 提示词框架

针对编程场景,我总结了 CRAFT 框架:

要素说明示例
Context项目背景和技术栈"这是一个 Next.js 15 App Router 项目"
Reference参考文件或模式"参考 @lib/actions/project.ts 的模式"
Ask明确的任务描述"创建用户邀请功能"
Format输出格式和约束"使用 Server Action,返回 { error?: string }"
Test验收标准"邀请链接 24 小时过期,同一邮箱不能重复邀请"
✅ CRAFT 示例:

Context: 这是一个 Next.js 15 SaaS 项目,使用 Drizzle ORM + PostgreSQL,
         Clerk 认证,多租户架构。

Reference: 参考 @lib/actions/project.ts 的权限检查模式,
           使用 @lib/db/schema.ts 中的 invitations 表。

Ask: 实现团队邀请功能——管理员输入邮箱,系统发送邀请链接,
     受邀者点击链接加入团队。

Format: 
- Server Action 在 lib/actions/invitation.ts
- 邮件发送用 Resend
- 邀请页面在 app/invite/[token]/page.tsx

Test:
- 邀请链接 24 小时后过期
- 同一租户下不能重复邀请同一邮箱
- 受邀者注册后自动加入对应租户
- 只有 admin 角色可以发送邀请

1.3 上下文注入策略

AI 的输出质量 = f(提示词质量, 上下文质量)。上下文越精确,输出越贴合:

策略何时使用效果
@file 精准引用你知道要参考哪个文件★★★★★
@folder 目录引用AI 需要理解某个模块★★★★
@Codebase 搜索你不确定相关代码在哪★★★
复制粘贴代码片段关键代码 + 错误信息★★★★
描述架构没有参考文件时★★★

关键原则:给 AI 看代码,而不是描述代码

❌ "我的项目用 Drizzle ORM,请按照我的风格写查询"

✅ "参考这个查询的风格:
    @lib/db/queries/project.ts
    给 users 表写一个按角色分页查询"

2. Next.js 专用提示词模板

2.1 Server Component 页面

创建一个 [页面名称] 页面。

技术要求:
- Server Component(不要加 'use client')
- 数据在组件内直接 await 查询(不要 useEffect)
- 使用 Suspense 包裹异步部分,提供 Skeleton fallback
- 分页使用 URL searchParams
- 参考 @app/(dashboard)/projects/page.tsx 的结构

功能:
- [具体功能列表]

文件位置:app/(dashboard)/[路径]/page.tsx

2.2 Server Action

创建一个 Server Action:[动作描述]

要求:
- 文件:lib/actions/[模块].ts
- 'use server' 声明
- 开头调用 requirePermission('[resource]:[action]')
- 输入用 zod schema 验证
- 返回类型:{ data?: T, error?: string }
- 成功后 revalidatePath('[受影响的路径]')
- 参考 @lib/actions/project.ts 的模式

业务逻辑:
- [具体逻辑]

边界情况:
- [列出需要处理的异常]

2.3 API Route(Webhook)

创建一个 Webhook 处理路由:[来源] Webhook

要求:
- 文件:app/api/[service]/webhook/route.ts
- 只导出 POST 方法
- 验证 webhook 签名([签名验证方式])
- 使用 raw body(不要 JSON parse 之前验证)
- 错误不暴露内部信息,返回 200 避免重试风暴
- 参考 @app/api/stripe/webhook/route.ts 的模式

处理的事件:
- [event.type]: [处理逻辑]

2.4 Client Component(表单)

创建一个表单组件:[表单名称]

要求:
- 'use client' 组件
- React Hook Form + zod resolver
- 提交调用 Server Action:@lib/actions/[对应].ts
- 提交中禁用按钮,显示 loading
- 成功后 toast 提示 + 可选 redirect
- 错误显示在表单下方
- 使用 @components/ui/ 的 shadcn 组件(Input, Button, Form, Label)
- 参考 @components/create-project-form.tsx 的模式

字段:
- [字段名]: [类型] [验证规则]

2.5 数据库 Schema 变更

给 [表名] 表添加以下字段:

字段定义:
- [字段名]: [类型] [约束]

要求:
- 修改 @lib/db/schema.ts
- 新字段先设为 nullable(避免迁移失败)
- 添加必要的索引
- 更新相关的 TypeScript 类型
- 列出需要更新的 Server Actions 和查询
- 生成迁移命令:pnpm db:generate

2.6 Bug 修复

修复以下错误:

错误信息:
[粘贴完整错误堆栈]

复现步骤:
1. [步骤]

期望行为 vs 实际行为:
- 期望:[...]
- 实际:[...]

相关文件:
- @[相关文件路径]

要求:
- 先分析根因,再给出修复方案
- 不要改变现有 API 接口
- 修复后不应影响其他功能

3. 任务拆分策略

3.1 为什么要拆分

AI 工具的一次性处理能力有上限——任务越复杂,输出质量越低。拆分的本质是 降低每次交互的认知负荷

任务规模推荐方式原因
单文件修改一次完成AI 的舒适区
2-5 文件功能Composer 一次在 Agent 能力范围内
5-10 文件模块拆成 2-3 步避免遗漏
10+ 文件重构拆成 5+ 步每步独立可验证
全新功能模块从数据模型开始自底向上构建

3.2 自底向上拆分法

对于一个新功能模块(如"通知系统"),推荐按依赖顺序拆分:

Step 1: 数据模型(最底层,无依赖)
  "在 schema.ts 中设计 notifications 表,包含 [字段列表]"

Step 2: 数据操作(依赖数据模型)
  "基于刚创建的 notifications 表,实现 CRUD Server Actions"

Step 3: 后端逻辑(依赖数据操作)
  "实现通知触发逻辑——当 [事件] 发生时,创建通知记录"

Step 4: API/推送(依赖后端逻辑)
  "实现通知的 SSE 推送,客户端实时收到新通知"

Step 5: 前端展示(依赖 API)
  "实现通知列表组件和未读角标,参考 @components/..."

Step 6: 集成测试(依赖全部)
  "给通知系统编写测试,覆盖创建、推送、标记已读"

每一步完成后都可以验证——数据模型可以用 Drizzle Studio 检查,Server Actions 可以用测试验证,UI 可以在浏览器预览。

3.3 增量式提示

对于长会话,用增量方式让 AI 保持上下文:

// 第一轮
"创建 notifications 表的 schema"

// 第二轮(引用上一步结果)
"基于你刚创建的 notifications 表,实现 createNotification 和 
 markAsRead 两个 Server Actions"

// 第三轮(引用前两步)
"现在参考 @lib/actions/notification.ts 中的 createNotification,
 在用户被邀请加入团队时(@lib/actions/invitation.ts 的 sendInvitation)
 自动创建通知"

4. 多轮对话技巧

4.1 纠错模式

AI 第一次输出不完美是常态。关键是高效纠错:

// ❌ 模糊纠错
"这不对,重新写"

// ✅ 精确纠错
"第 23 行的查询缺少 tenantId 过滤,所有查询都必须包含 
 .where(eq(table.tenantId, tenantId))
 另外第 45 行的错误应该 return { error: message } 而不是 throw"

4.2 迭代优化模式

// 第一轮:让 AI 生成基础版本
"实现用户搜索功能,使用 SQL LIKE 查询"

// 第二轮:添加分页
"给搜索结果添加游标分页,每页 20 条"

// 第三轮:优化性能
"在 name 和 email 字段上添加 GIN 索引,
 搜索改为 pg_trgm 模糊匹配"

// 第四轮:添加缓存
"将搜索结果缓存到 Redis,5 分钟过期,
 用户修改信息后清除缓存"

4.3 Review 模式

让 AI 审查自己或其他人的代码:

"Review 以下代码,重点检查:
 1. 安全性(SQL 注入、XSS、权限绕过)
 2. 性能(N+1 查询、缺少索引)
 3. 错误处理(遗漏的边界情况)
 4. 是否符合项目的编码规范(参考 @.cursorrules)

 @[待审查的文件]"

5. 高级提示词技巧

5.1 Few-shot:给示例

当项目有特定模式时,给一个示例比千言万语更有效:

"按照以下模式创建 lib/actions/notification.ts:

示例(@lib/actions/project.ts 第 1-30 行):
[自动引用的代码]

要求:
- 同样的权限检查模式
- 同样的输入验证模式
- 同样的错误返回模式
- 替换为 notifications 表的 CRUD 操作"

5.2 Chain of Thought:让 AI 先思考

"我想给系统添加实时通知功能。

先不要写代码,先帮我:
1. 列出技术方案的选项(SSE vs WebSocket vs Polling)
2. 分析每个方案在 Next.js + Vercel 上的可行性
3. 推荐方案并说明理由
4. 列出实现步骤

确认方案后我再让你开始写代码。"

5.3 角色扮演

"你是一个 Next.js 安全审计专家。
 请审查 @app/api/ 目录下的所有路由,
 重点检查:
 - 认证缺失(未检查 session)
 - 授权绕过(未检查 tenantId)
 - 输入注入(未用 zod 验证)
 - 敏感数据泄露(返回了不该返回的字段)
 
 输出格式:
 | 文件 | 问题 | 严重等级 | 修复建议 |"

5.4 负面约束

告诉 AI "不要做什么"有时比"要做什么"更有效:

"创建用户列表页面。

不要:
- 不要使用 useEffect 获取数据(用 Server Component)
- 不要使用 API Route(用 Server Action)
- 不要使用 Prisma(用 Drizzle ORM)
- 不要创建新的 UI 组件(用 @components/ui/ 的 shadcn)
- 不要使用 offset 分页(用 cursor 分页)
- 不要在 Client Component 中直接查数据库"

本章小结

  • CRAFT 框架:Context + Reference + Ask + Format + Test,结构化提示词
  • @引用是核心:给 AI 看代码比描述代码有效 10 倍
  • 专用模板:Server Component / Server Action / Webhook / 表单 / Schema 变更 / Bug 修复
  • 自底向上拆分:数据模型 → 数据操作 → 后端逻辑 → API → 前端 → 测试
  • 精确纠错:指出具体行号和问题,不要说"重新写"
  • 迭代优化:先基础版本,再逐步添加分页、性能、缓存
  • Few-shot:给一个好的示例比解释模式更直接
  • Chain of Thought:复杂任务先让 AI 分析方案,确认后再写代码
  • 负面约束:"不要用 Prisma / 不要用 useEffect" 减少常见错误