本教程面向有基础前端经验但从未用AI辅助开发的工程师,完整演示如何用Claude Code从空白目录开始,构建一个生产级Next.js全栈应用。你将学会CLAUDE.md项目配置、App Router组件开发模式、Server Actions数据流、以及前端特有的调试与质量保障技巧。学完后能独立用Claude Code交付中小型前端项目。
适用人群:有1年以上React/TypeScript经验的前端开发者,希望将AI编程纳入日常工作流。
学习目标:掌握Claude Code在Next.js项目中的完整开发循环——从初始化、CLAUDE.md配置、组件开发、数据层对接到质量验证。
| 项目 | 要求 |
|---|---|
| Node.js | v20+(推荐v22 LTS) |
| Claude Code | 已安装并登录(claude 命令可用) |
| 前端基础 | React、TypeScript、npm/pnpm |
| 编辑器 | VS Code(可选,Claude Code在终端运行) |
| API Key | Anthropic API Key已配置 |
确认环境就绪:
node -v # v22.x
claude --version # 确认CLI已安装
在终端启动Claude Code,让它帮你脚手架项目:
mkdir my-saas-app && cd my-saas-app
claude
进入Claude Code交互界面后,输入:
请帮我初始化一个Next.js 15项目,要求:
- App Router(不用Pages Router)
- TypeScript严格模式
- Tailwind CSS v4
- 使用pnpm作为包管理器
- 包含ESLint和Prettier配置
Claude Code会执行类似以下命令:
pnpm create next-app@latest . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
预期结果:项目目录生成完毕,包含 src/app、tailwind.config.ts、tsconfig.json 等标准文件。
问题:Claude Code选择了Pages Router而非App Router。
解决:在提示中明确指定"使用App Router,不要Pages Router",或在命令后追加 --app 标志。如果已经初始化错误,直接告诉Claude Code:"这个项目用了错误的路由模式,请重新用App Router初始化"。
CLAUDE.md是Claude Code理解项目的核心配置文件。对于Next.js前端项目,一份好的CLAUDE.md能减少50%以上的重复提问。
在项目根目录创建 .claude/CLAUDE.md(项目级)或 CLAUDE.md(根目录级):
# 项目概览
SaaS任务管理应用,技术栈:Next.js 15 + App Router + TypeScript + Tailwind CSS v4 + Prisma + NextAuth.js v5
## 技术约定
- 路由:App Router,所有页面在 `src/app/` 下
- 组件:函数组件 + Hooks,禁止class组件
- 样式:Tailwind CSS,禁止内联style属性,禁止CSS Modules
- 状态管理:React Context + useReducer,不引入Redux
- 数据获取:Server Components优先,客户端用SWR
- 表单:React Hook Form + Zod验证
- 数据库:Prisma ORM,schema文件在 `prisma/schema.prisma`
- 认证:NextAuth.js v5,Auth.js适配器
## 文件结构约定
src/ ├── app/ # 路由页面(每个路由 folder 含 page.tsx + loading.tsx + error.tsx) ├── components/ # 共享UI组件 │ ├── ui/ # 基础组件(Button, Input, Modal等) │ └── features/ # 业务组件(TaskList, UserAvatar等) ├── lib/ # 工具函数和配置 │ ├── db.ts # Prisma客户端单例 │ ├── auth.ts # NextAuth配置 │ └── utils.ts # 通用工具函数 ├── types/ # TypeScript类型定义 └── actions/ # Server Actions
## 编码规范
- 组件文件名使用PascalCase:`TaskCard.tsx`
- 工具函数文件名使用camelCase:`formatDate.ts`
- 每个组件必须有TypeScript props类型定义
- Server Component不加'use client'指令
- Client Component必须以'use client'开头
- API路由返回类型化的JSON响应
- 数据库查询必须用Prisma,禁止原始SQL
## 测试要求
- 单元测试:Vitest + React Testing Library
- E2E测试:Playwright
- 测试文件放在对应组件同目录的 `__tests__/` 下
## 禁止事项
- 不要安装新的状态管理库(zustand、jotai等)
- 不要使用axios,用原生fetch
- 不要创建CSS文件,只用Tailwind类
- 不要修改 prisma/schema.prisma 除非明确要求
为什么在CLAUDE.md中写这么多约定?
Claude Code每次对话开始时会读取CLAUDE.md,这些约定相当于给AI助手一份"项目宪法"。没有它,Claude Code可能: - 在App Router项目中生成Pages Router代码 - 引入你不需要的依赖(如Redux) - 用CSS Modules而非Tailwind - 写class组件而非函数组件
在 ~/.claude/CLAUDE.md 中配置个人偏好(所有项目共享):
# 全局偏好
- 默认使用中文回复
- 代码注释用英文
- 不要自动 git commit
- 优先编辑现有文件而非创建新文件
- 修改代码后主动运行测试验证
告诉Claude Code你的数据模型需求:
请设计Prisma schema,包含以下模型:
- User:用户,含email、name、avatar
- Project:项目,属于User,含title、description、color
- Task:任务,属于Project,含title、description、status(TODO/IN_PROGRESS/DONE)、priority(LOW/MEDIUM/HIGH)、dueDate
- 支持软删除(deletedAt字段)
Claude Code会编辑 prisma/schema.prisma,生成类似:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
avatar String?
projects Project[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
model Project {
id String @id @default(cuid())
title String
description String?
color String @default("#3B82F6")
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tasks Task[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
model Task {
id String @id @default(cuid())
title String
description String?
status Status @default(TODO)
priority Priority @default(MEDIUM)
dueDate DateTime?
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
enum Status {
TODO
IN_PROGRESS
DONE
}
enum Priority {
LOW
MEDIUM
HIGH
}
Next.js App Router推荐用Server Actions处理数据变更。让Claude Code生成标准化的actions:
在 src/actions/ 下创建以下Server Actions:
1. task-actions.ts - 任务的CRUD操作
2. project-actions.ts - 项目的CRUD操作
要求:每个action都有Zod输入验证,使用revalidatePath刷新缓存,统一错误处理
Claude Code生成的 src/actions/task-actions.ts 示例:
'use server'
import { revalidatePath } from 'next/cache'
import { prisma } from '@/lib/db'
import { z } from 'zod'
const createTaskSchema = z.object({
title: z.string().min(1, '标题不能为空').max(100),
description: z.string().max(500).optional(),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH']).default('MEDIUM'),
dueDate: z.string().datetime().optional(),
projectId: z.string().cuid(),
})
export async function createTask(formData: FormData) {
const raw = {
title: formData.get('title'),
description: formData.get('description'),
priority: formData.get('priority'),
dueDate: formData.get('dueDate'),
projectId: formData.get('projectId'),
}
const validated = createTaskSchema.safeParse(raw)
if (!validated.success) {
return { error: validated.error.flatten().fieldErrors }
}
try {
const task = await prisma.task.create({
data: validated.data,
})
revalidatePath(`/projects/${validated.data.projectId}`)
return { data: task }
} catch (error) {
return { error: '创建任务失败,请稍后重试' }
}
}
export async function updateTaskStatus(taskId: string, status: 'TODO' | 'IN_PROGRESS' | 'DONE') {
try {
const task = await prisma.task.update({
where: { id: taskId },
data: { status },
})
revalidatePath(`/projects/${task.projectId}`)
return { data: task }
} catch {
return { error: '更新状态失败' }
}
}
export async function deleteTask(taskId: string) {
try {
const task = await prisma.task.update({
where: { id: taskId },
data: { deletedAt: new Date() },
})
revalidatePath(`/projects/${task.projectId}`)
return { success: true }
} catch {
return { error: '删除任务失败' }
}
}
关键技巧:让Claude Code一次生成一组关联组件,而非逐个创建。
请创建任务看板视图,包含以下组件:
1. TaskBoard - 看板容器,三列布局(TODO/IN_PROGRESS/DONE)
2. TaskColumn - 单列,显示状态标题和任务计数
3. TaskCard - 任务卡片,显示标题、优先级标签、截止日期
4. TaskForm - 新建/编辑任务的弹窗表单
要求:
- TaskBoard是Server Component,获取初始数据
- TaskCard和TaskForm是Client Component(需要交互)
- 优先级用颜色区分:HIGH红色、MEDIUM黄色、LOW绿色
- 使用Tailwind实现响应式布局(移动端单列,桌面端三列)
Claude Code会自动在正确的目录下创建文件:
src/components/features/
├── TaskBoard.tsx # Server Component
├── TaskColumn.tsx # Client Component
├── TaskCard.tsx # Client Component
└── TaskForm.tsx # Client Component
问题1:Claude Code在Server Component中使用了 useState。
解决:提醒Claude Code检查 'use client' 指令。在CLAUDE.md中已有约定,但偶尔会遗漏。直接说:"这个文件是Server Component,不能使用客户端Hook,请修复"。
问题2:Server Actions中缺少 'use server' 指令。
解决:Claude Code通常会在文件顶部自动添加,但如果遗漏,直接指出:"请在文件顶部加上 'use server' 指令"。
问题3:Tailwind类名冲突或样式不一致。
解决:在提示中给出设计约束,例如"所有卡片使用 rounded-lg shadow-sm border border-gray-200 p-4 基础样式"。
Claude Code最适合小步快跑的开发模式。不要一次描述整个应用,而是分批交付:
# 第1轮:基础框架
先完成项目页面的列表视图,显示所有项目卡片,点击进入项目详情
# 第2轮:核心交互
给任务卡片添加拖拽排序功能(使用 @dnd-kit/core),支持跨列拖动改变状态
# 第3轮:表单优化
TaskForm添加富文本编辑器(Tiptap),支持Markdown预览
# 第4轮:实时更新
用Server-Sent Events实现任务状态的实时推送
每一轮完成后运行验证:
pnpm build # 确保编译通过
pnpm lint # 检查代码规范
当你需要基于已有组件开发新功能时,用 @ 引用文件:
参考 @src/components/features/TaskCard.tsx 的设计风格,
创建一个 ProjectCard 组件,显示项目名称、任务数量、进度百分比
@ 是Claude Code的文件引用语法,等同于让Claude Code先读取该文件再响应。
Claude Code可以直接读取浏览器开发者工具的输出:
页面报错 "Hydration failed because the server rendered HTML didn't match the client"。
请检查 @src/app/projects/[id]/page.tsx 中可能导致hydration mismatch的原因并修复。
Claude Code会检查常见的hydration问题:
- 服务端/客户端渲染内容不一致
- typeof window 条件判断
- Date.now() 或 Math.random() 在服务端调用
- 第三方库的客户端副作用
为 @src/components/features/TaskCard.tsx 编写Vitest单元测试,覆盖:
1. 正确渲染标题和优先级标签
2. 高优先级显示红色标签
3. 点击卡片触发onClick回调
4. 截止日期已过时显示警告样式
Claude Code生成的测试示例:
// src/components/features/__tests__/TaskCard.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { TaskCard } from '../TaskCard'
const mockTask = {
id: 'clx123',
title: '完成用户认证',
description: '实现JWT登录流程',
status: 'TODO' as const,
priority: 'HIGH' as const,
dueDate: new Date('2026-06-01'),
projectId: 'proj123',
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
}
describe('TaskCard', () => {
it('renders title and priority badge', () => {
render(<TaskCard task={mockTask} onClick={vi.fn()} />)
expect(screen.getByText('完成用户认证')).toBeInTheDocument()
expect(screen.getByText('HIGH')).toBeInTheDocument()
})
it('shows red badge for high priority', () => {
render(<TaskCard task={mockTask} onClick={vi.fn()} />)
const badge = screen.getByText('HIGH')
expect(badge.className).toContain('bg-red')
})
it('calls onClick when clicked', () => {
const handleClick = vi.fn()
render(<TaskCard task={mockTask} onClick={handleClick} />)
fireEvent.click(screen.getByRole('article'))
expect(handleClick).toHaveBeenCalledWith(mockTask.id)
})
it('shows warning for overdue tasks', () => {
const overdueTask = {
...mockTask,
dueDate: new Date('2025-01-01'),
}
render(<TaskCard task={overdueTask} onClick={vi.fn()} />)
expect(screen.getByText('已逾期')).toBeInTheDocument()
})
})
运行测试:
pnpm test
在每次重要修改后,让Claude Code运行完整的类型检查:
pnpm tsc --noEmit
如果出现类型错误,直接把错误信息粘贴给Claude Code修复。
前端开发中有大量重复模式。创建自定义Skill来加速:
.claude/skills/frontend-component.md:
---
name: frontend-component
description: 创建标准化的React组件,包含TypeScript类型、Tailwind样式和单元测试骨架
---
## 使用场景
当需要创建新的React组件时使用此skill。
## 组件模板
创建组件时遵循以下结构:
1. 文件位置:根据组件类型放在对应目录
- UI基础组件 → `src/components/ui/`
- 业务组件 → `src/components/features/`
2. 组件模板:
```typescript
import { type FC } from 'react'
interface [ComponentName]Props {
// props定义
}
export const [ComponentName]: FC<[ComponentName]Props> = ({ /* props */ }) => {
return (
<div className="/* Tailwind classes */">
{/* 内容 */}
</div>
)
}
```
3. 必须包含:
- TypeScript props接口定义
- FC类型标注
- 命名导出(非默认导出)
- 如需客户端交互,文件顶部加 `'use client'`
4. 同步创建测试文件:`__tests__/[ComponentName].test.tsx`
当功能涉及多个文件修改时,先进入Plan Mode:
请进入plan mode,我需要实现以下功能:
"任务评论系统——用户可以在任务下方添加评论,支持@提及、Markdown格式、文件附件"
Plan Mode下Claude Code会: 1. 分析现有代码结构 2. 列出需要创建/修改的所有文件 3. 设计数据模型变更 4. 规划实现顺序 5. 标注潜在风险点
确认计划后退出Plan Mode,Claude Code按计划逐步实现。
前端项目文件多,容易撑爆上下文窗口。三个关键技巧:
技巧1:使用 /compact 命令压缩历史对话
/compact 保留当前任务需求和最近的代码变更
技巧2:用 @ 精确引用而非让Claude Code全局搜索
# 不好 - 让Claude Code自己找文件
请修复登录页面的样式问题
# 好 - 精确指定文件
请修复 @src/app/(auth)/login/page.tsx 中登录表单的响应式布局问题
技巧3:分阶段开发,完成一个功能后开新会话
# 完成任务看板后
exit # 退出当前会话
# 开始新功能时重新启动
claude # 新会话,上下文干净
flowchart TD
A[项目初始化] --> B[编写CLAUDE.md]
B --> C[数据库Schema设计]
C --> D[Server Actions开发]
D --> E[UI组件开发]
E --> F[页面路由组装]
F --> G[测试与验证]
G --> H{构建通过?}
H -->|是| I[部署准备]
H -->|否| J[修复错误]
J --> G
I --> K[生产环境上线]
style A fill:#3B82F6,color:#fff
style B fill:#3B82F6,color:#fff
style I fill:#10B981,color:#fff
style K fill:#10B981,color:#fff
以下是5轮对话交付一个完整功能的实录:
第1轮:页面骨架
创建 /projects 页面,展示当前用户的所有项目。
使用Server Component获取数据,每个项目显示为卡片。
如果用户没有项目,显示空状态引导。
第2轮:详情页
创建 /projects/[id] 页面,展示项目下的任务看板。
参考 @src/components/features/TaskBoard.tsx 的组件。
页面顶部显示项目信息,下方是三列看板。
第3轮:交互功能
给 TaskCard 添加快捷操作:
1. 点击复选框直接标记为DONE
2. 右键菜单显示编辑/删除选项
3. 拖拽到其他列改变状态
第4轮:表单完善
TaskForm增加以下功能:
1. 日期选择器(截止日期)
2. 优先级下拉选择
3. 项目选择器(创建任务时选择归属项目)
4. 表单提交后自动关闭并刷新列表
第5轮:测试与收尾
为本次新增的所有功能编写测试:
1. 项目列表页渲染测试
2. 任务CRUD操作的集成测试
3. 拖拽功能的E2E测试
然后运行 pnpm build 确认一切正常。
my-saas-app/
├── .claude/
│ ├── CLAUDE.md # 项目AI配置
│ └── skills/
│ └── frontend-component.md # 自定义Skill
├── prisma/
│ └── schema.prisma # 数据模型
├── src/
│ ├── app/
│ │ ├── layout.tsx # 根布局
│ │ ├── page.tsx # 首页
│ │ ├── (auth)/
│ │ │ ├── login/page.tsx
│ │ │ └── register/page.tsx
│ │ ├── projects/
│ │ │ ├── page.tsx # 项目列表
│ │ │ └── [id]/
│ │ │ └── page.tsx # 项目详情+看板
│ │ └── api/auth/[...nextauth]/route.ts
│ ├── components/
│ │ ├── ui/
│ │ │ ├── Button.tsx
│ │ │ ├── Input.tsx
│ │ │ └── Modal.tsx
│ │ └── features/
│ │ ├── ProjectCard.tsx
│ │ ├── TaskBoard.tsx
│ │ ├── TaskColumn.tsx
│ │ ├── TaskCard.tsx
│ │ └── TaskForm.tsx
│ ├── actions/
│ │ ├── task-actions.ts
│ │ └── project-actions.ts
│ ├── lib/
│ │ ├── db.ts
│ │ ├── auth.ts
│ │ └── utils.ts
│ └── types/
│ └── index.ts
├── CLAUDE.md # 根目录配置(可选)
├── package.json
├── tailwind.config.ts
└── tsconfig.json
症状:Claude Code在 src/app/ 下创建了 getServerSideProps。
根因:训练数据中Pages Router代码更密集,Claude Code容易回退到旧模式。
解决:在CLAUDE.md中强调"只使用App Router",发现错误时直接指出。
症状:编译报错 " useState is not a function in Server Component"。
根因:Claude Code未正确区分服务端/客户端边界。
解决:明确告诉Claude Code哪些组件需要 'use client',或让它在生成前先确认组件类型。
症状:单个元素的className超过200字符。
根因:Claude Code倾向于内联所有Tailwind类而非抽象为组件变体。
解决:创建 cn() 工具函数(clsx + tailwind-merge),让Claude Code使用变体模式:
// src/lib/utils.ts
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
症状:数据更新后页面不刷新。
根因:Server Actions中忘记调用 revalidatePath 或 revalidateTag。
解决:在CLAUDE.md中约定"所有数据变更操作必须调用revalidatePath"。
症状:大量 any 类型和类型断言。
根因:Claude Code在不确定类型时倾向用 any 快速通过编译。
解决:在CLAUDE.md中约定"禁止使用any,所有props必须有interface定义"。
症状:生成的代码不完整、逻辑混乱。
根因:上下文窗口有限,一次处理过多需求会导致Claude Code"忘记"部分要求。
解决:遵循"一轮一个功能"原则,复杂功能用Plan Mode拆分。
症状:生成的代码有运行时错误。
根因:Claude Code生成的代码未经测试直接使用。
解决:每次修改后运行 pnpm build && pnpm lint,养成验证习惯。
当项目进入优化阶段时,让Claude Code系统化处理:
请对本项目进行前端性能审计,包括:
1. 分析 bundle size,找出过大的依赖
2. 检查是否有不必要的客户端组件
3. 确认图片使用了 next/image 优化
4. 检查是否有缺失的 loading.tsx(Suspense边界)
5. 验证 Server Components 正确使用
Claude Code会逐项分析并给出优化建议,常见优化包括:
- 将静态内容提取为Server Component
- 用 dynamic() 懒加载重型组件
- 添加 loading.tsx 提供流式加载体验
- 用 next/image 替代 <img> 标签
让Claude Code执行最终验证:
请执行以下部署前检查:
1. pnpm build 是否通过(零错误零警告)
2. pnpm lint 是否通过
3. pnpm test 是否全部通过
4. 环境变量是否都有 .env.example 文档
5. 是否有 console.log 残留
6. 是否有 TODO/FIXME 注释未处理
| 维度 | 评估 |
|---|---|
| 适合训练营类型 | 前端开发训练营、全栈开发训练营、AI辅助编程训练营 |
| 学员基础要求 | React + TypeScript 6个月以上经验 |
| 预计教学时长 | 6-8小时(含实操练习) |
| 实操比例 | 70%实操 + 30%讲解 |
| 硬件要求 | 每人一台笔记本,Node.js 20+,网络通畅 |
| 推荐分组 | 2人一组,一人操作Claude Code,一人Review代码 |
| 产出物 | 每组完成一个可部署的Next.js全栈应用 |
| 时段 | 内容 | 时长 |
|---|---|---|
| 第1节 | 环境准备 + CLAUDE.md编写 | 1小时 |
| 第2节 | 数据层:Schema + Server Actions | 1.5小时 |
| 第3节 | UI组件开发 + 交互功能 | 2小时 |
| 第4节 | 测试编写 + 调试技巧 | 1.5小时 |
| 第5节 | 性能优化 + 部署 + 自由练习 | 1小时 |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
