第 29 章 接单开发网站
第 28 章把分而治之搬到了写作上,本章把它搬到了商业交付上:接单做网站不是「聊一个做一个」,而是把整条交付链——从需求、报价、合同,到开发、验收、部署、维护——固化成可复用的脚手架 + 命令 + 子代理 + SOP。素材全部来自一套已落地的
web-studio工作流,不是凑出来的案例。
29.1 结论:接单的核心是把交付流程固化成一条龙
用 Claude Code 接单开发网站,核心策略只有一句话:把交付流程固化成「脚手架 + 命令 + 子代理 + SOP + Docker 部署」一条龙,每个客户项目都是这条流水线的一次实例化。
这条策略解决了接单开发的三个根本矛盾:
- 重复矛盾:每个客户项目都要从零搭脚手架、配数据库、写后台、部署上线,80% 的工作是重复劳动。固化脚手架把这部分成本摊到一次。
- 质量矛盾:一人接单最大风险是「这单做完了下一单质量下滑」。固化命令与子代理把质量标准从「我当时状态」变成「流水线默认值」。
- 边界矛盾:接单最容易扯皮的是「这个算不算范围内」「维护包不包含改文案」。固化 SOP 与商务文档把边界写死,减少纠纷。
本章给出的 web-studio 工作流已落地在 /Users/mba/claude/web-studio/,主打企业官网/落地页,全包交付(开发 + 部署 + 维护)。技术栈:Next.js 16 (App Router) + React 19 + Prisma 7 + PostgreSQL 16 + NextAuth v5 + shadcn/ui + Tailwind v4 + Docker(standalone + Caddy)。
本章与第 06 章的可执行闭环、第 19 章的子代理一脉相承:整条流水线就是把闭环七步(限定范围 → 事实模型 → 修改 → 验证 → 读失败 → 自我修正 → 人类审查)从「一次任务」放大到「一次交付」。
29.2 web-studio 三件套总览
web-studio/
├── template/ # ① 全栈脚手架(新单复制其副本)
│ ├── src/ # 前台 (site) + 后台 (admin) + actions + lib
│ ├── prisma/ # schema.prisma + seed.ts
│ ├── scripts/ # backup.sh / restore.sh / deploy.sh / migrate.sh
│ ├── Dockerfile # 多阶段 standalone
│ ├── docker-compose.yml # app + db + caddy
│ ├── Caddyfile # 自动 HTTPS
│ ├── CLAUDE.md # 工程约定(规则至上)
│ └── AGENTS.md # Next.js 16 breaking changes
├── commands/web/ # ② 9 个 /web:* slash command
├── agents/ # ② 3 个子代理:ui-builder / api-builder / qa-reviewer
├── docs/ # ③ 商务文档模板(报价/合同/验收/维护/迁移)
├── SOP.md # ③ 端到端接单流程
└── install.sh # 把命令/agent 软链到 ~/.claude/三件套对应三个固化维度:代码固化(template)、操作固化(commands + agents)、商务固化(SOP + docs)。三者缺一不可:只有脚手架没有命令,Claude 每次都要重新理解项目;只有命令没有 SOP,技术做完了但商务扯皮;只有 SOP 没有脚手架,每单都从零开始。
安装一次,全局可用:
bash /Users/mba/claude/web-studio/install.shinstall.sh 用软链而非复制——改源文件即同步生效,便于升级。安装后重启 Claude Code,/web:* 命令与三个子代理在任意客户项目目录都可用。
预期输出:
✓ 已安装 slash commands(/web:new 等):
new.md spec.md model.md ui.md admin.md check.md up.md deploy.md handoff.md
✓ 已安装 subagents(ui-builder 等):
api-builder.md qa-reviewer.md ui-builder.md
重启 Claude Code(或新开会话)后即可使用 /web:<name>。验证:ls ~/.claude/commands/web/ 与 ls ~/.claude/agents/ 看到软链;新会话里 /agents 列出三个子代理。
失败边界:不要把 commands/web/*.md 裸拷进 ~/.claude/commands/——会丢失 namespace 保护,可能与未来内置 /web 冲突。install.sh 用 commands/web/ 子目录正是为了强制 web: namespace(第 16 章的 namespace 纪律)。
29.3 脚手架:Next.js 16 全栈 + Prisma 7 driver adapter
脚手架是流水线的物理基础。template/ 不是空目录,而是一个已通过 tsc + eslint + prisma generate/validate 的最小可运行全栈应用:前台 6 页(首页/关于/服务/案例/新闻/联系)+ 后台 CMS 增删改查 + NextAuth 登录 + SEO(sitemap/robots)+ seed + Docker 部署。业务模型已预置:SiteSetting / Page / Post / Product / Work / Inquiry / User。
技术栈选型理由
| 层 | 选型 | 理由 |
|---|---|---|
| 框架 | Next.js 16 App Router | 全栈一体,server actions 免写 API 路由,SSR/SSG 对 SEO 友好 |
| UI | shadcn/ui + Tailwind v4 | 复制源码进项目,可定制无锁定;企业官网审美门槛低 |
| 数据库 | PostgreSQL 16 + Prisma 7 | 关系型契合 CMS 结构;Prisma 类型安全 |
| 鉴权 | NextAuth v5(credentials + jwt) | 仅后台 CMS 需登录,前台免登录,最小化攻击面 |
| 部署 | Docker standalone + Caddy | standalone 镜像轻量;Caddy 自动 HTTPS,免证书运维 |
两个非显然的坑
坑一:Prisma 7 用 driver adapter,不是 PrismaPostgres。Prisma 7 的 PostgreSQL driver adapter 类名是 PrismaPg,从 @prisma/adapter-pg 导入,传 { connectionString }:
// src/lib/db.ts
import { PrismaClient } from "@/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
export const prisma = globalForPrisma.prisma ?? new PrismaClient({ adapter });脚手架里的 AGENTS.md 早期版本写成 PrismaPostgres,首次 tsc 报错已修正。这个坑很隐蔽——Prisma 5/6 训练数据里的写法不适用,模型会反复回退到旧 API。AGENTS.md 必须把正确写法钉死,子代理开工前必读。
坑二:Next.js 16 的 params/searchParams 是 Promise。这是 Next 16 的 breaking change,动态路由须 await:
// src/app/(site)/news/[slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params; // 必须 await
const post = await prisma.post.findUnique({ where: { slug } });
// ...
}AGENTS.md 里钉死这条:「params/searchParams 是 Promise,须 await;不确定的 API 先查 node_modules/next/dist/docs/」。这正是第 06 章「命令真实性」的延伸——不照搬训练数据,以运行时版本为准。
脚手架本地验证
cd /Users/mba/claude/web-studio/template
cp .env.example .env
docker compose up -d --wait db
npm install
npx prisma migrate dev --name init
npm run db:seed
npm run dev # http://localhost:3000 后台 /admin验证命令:npx tsc --noEmit && npm run lint && npm run build。tsc 与 lint 必过;build 在国内可能因 Docker Hub(registry-1.docker.io)拉取受阻失败——需在 Docker Desktop → Settings → Docker Engine 的 registry-mirrors 配镜像加速器(如 docker.m.daocloud.io)。
失败边界:脚手架与客户需求不匹配是最大风险。本脚手架强项是「企业官网/落地页 + 轻量 CMS」,遇到复杂 SaaS、电商、多语言、支付分账,要评估接或转介,不要硬塞脚手架。SOP 第 1 步(线索与初评)就是干这件事。
29.4 9 个 /web:* 命令:从脚手架到部署
9 个命令对应交付链的 9 个节点。顺序链:
/web:new → /web:spec → /web:model → /web:ui → /web:admin
→ /web:check → /web:up → /web:deploy → /web:handoff| 命令 | 用途 | 产出 | 委托 |
|---|---|---|---|
/web:new <项目名> | 接单初始化:复制脚手架、init git、草拟 PRD | 客户项目目录 + docs/PRD.draft.md | 主代理 |
/web:spec | 从草稿生成结构化 PRD + 报价单 | docs/PRD.md + docs/QUOTE.md | 主代理 |
/web:model | 按 PRD 增删改 Prisma schema + 迁移 | prisma/schema.prisma + migration | 主代理 |
/web:ui | 前台页面与组件 | src/app/(site)/* + src/components/site/* | ui-builder |
/web:admin | 后台 CMS 与鉴权 | src/actions/cms.ts + src/app/(admin)/* | api-builder |
/web:check | 交付前自检(类型/构建/安全/SEO) | docs/CHECKLIST.md | qa-reviewer |
/web:up | 本地起完整环境 | db + app + caddy 运行态 | 主代理 |
/web:deploy | 上线部署(需人工二次确认) | docs/DEPLOY.md | 主代理 |
/web:handoff | 生成交付包 | docs/handoff/* + 源码包 | 主代理 |
关键命令示例:/web:check
/web:check 是第 06 章可执行闭环的典型落地——它把「完成」从一句话变成 6 项机器可检查状态:
执行(委托 qa-reviewer):
1. 类型与规范:npx tsc --noEmit、npm run lint 必须通过
2. 构建:npm run build 必须通过(需 db 运行)
3. 安全 checklist:
- .env 未进版本控制;无硬编码密钥
- 后台所有写操作有鉴权(middleware + requireAdmin)
- 表单输入经 zod 校验
- 无未消毒的 dangerouslySetInnerHTML
4. 响应式:关键页面移动端可用
5. SEO:每页 metadata、sitemap、robots 正确
6. 内容:占位素材/文案待替换项已列出
产出:docs/CHECKLIST.md — 逐项结果 + 阻塞项清单
阻塞项全部清零后方可 /web:deploy预期输出:docs/CHECKLIST.md,每项 ✅/❌ + 证据 + 阻塞项清单。
验证:cat docs/CHECKLIST.md 看到逐项结果;阻塞项计数为 0。
失败边界:阻塞项未清零就进 /web:deploy 是最贵的返工——线上修 Bug 比本地贵 10 倍。/web:deploy 命令自身会校验 CHECKLIST.md 无阻塞项,作为第二道闸门。
关键命令示例:/web:deploy(红线命令)
/web:deploy 是典型的红线命令——执行到「准备就绪」即停,列出将执行的生产命令,等用户明确确认:
⚠️ 推送到生产属用户红线。本命令执行到「准备就绪」即停,
列出将执行的命令,等待用户明确确认后再执行。
执行(准备阶段):
1. 校验 .env:AUTH_SECRET 已生成(非占位);
NEXTAUTH_URL/SITE_URL/SITE_DOMAIN 为生产域名
2. 校验 docs/CHECKLIST.md 无阻塞项
3. 备份(若已有数据):bash scripts/backup.sh
4. 列出将执行的生产命令:
bash scripts/deploy.sh(git pull → pnpm install
→ prisma migrate deploy → docker compose build → up -d)
5. 停下来,向用户展示命令清单,请求确认
红线:未确认前不执行任何推送/上线命令;部署前必须 backup.sh这呼应了 CLAUDE.md 的红线边界:部署到生产环境即使在 auto-accept 模式下也必须绝对中止并向用户二次确认。命令的真实形态不是「一键部署」,而是「一键准备 + 人工确认 + 执行」。
29.5 3 个子代理:各司其职
三个子代理是流水线的「工位」。每个工位有明确的职责边界、开工前必读、完成后自证。这是第 19 章子代理的实战展开。
ui-builder:前台 UI
职责: src/app/(site)/* 前台页面 + src/components/site/* 业务组件
开工前必读: CLAUDE.md、AGENTS.md(Next 16 breaking changes)
工具: Read, Write, Edit, Glob, Grep, Bash
约束: 不写后台代码(交给 api-builder);占位素材不外链版权图
自证: npx tsc --noEmitapi-builder:后台 CMS 与数据层
职责: server actions(CRUD,经 requireAdmin)+ src/app/(admin)/*
开工前必读: CLAUDE.md、AGENTS.md、prisma/schema.prisma、src/actions/cms.ts
工具: Read, Write, Edit, Glob, Grep, Bash
约束: Prisma 7 用 PrismaPg;改 schema 必须 migrate + generate;
后台路径受 middleware 保护,写操作二次 requireAdmin
自证: npx tsc --noEmitqa-reviewer:交付前质量把关
职责: 类型/构建/安全/响应式/SEO checklist
开工前必读: 无(只读核验)
工具: Read, Glob, Grep, Bash(无 Write/Edit)
约束: 默认怀疑,不给「差不多就行」
产出: docs/CHECKLIST.md三个设计要点
要点一:工具权限最小化。qa-reviewer 不给 Write/Edit——它是裁判,不能下场踢球。这是第 05 章权限模型在子代理层的延伸:读操作放开,写操作按职责收紧。
要点二:开工前必读。每个子代理 frontmatter 下第一段就是「开工前必读」,强制读 CLAUDE.md 与 AGENTS.md。这把第 06 章「用 CLAUDE.md 提供稳定上下文」落到了子代理层——子代理不继承父对话历史,必须自己读项目约定。
要点三:完成后自证。ui-builder 与 api-builder 都以 npx tsc --noEmit 自证。这不是装饰——子代理返回「我做完了」不算数,类型检查通过才算数。可执行闭环的第七步(人类审查)由 qa-reviewer 接棒。
失败边界:agent 越权。ui-builder 跑去改后台代码、api-builder 跑去改 UI、qa-reviewer 自己动手修 Bug——这都是越权。frontmatter 的 description 与「职责」段是契约,主代理派任务时要把边界复述一遍。发现越权立即叫停,回退到第 06 章闭环的步骤 1(重新限定范围)。
29.6 SOP:需求 → 报价 → 合同 → 开发 → 验收 → 部署 → 维护
SOP.md 把交付链拆成 10 阶段,每阶段标注命令 / 产出 / 检查 / 红线。这是第 06 章可执行闭环的工程化放大:闭环七步从「一次任务」放大到「一次交付」,每阶段都是一个迷你闭环。
| 阶段 | 命令 | 产出 | 红线 |
|---|---|---|---|
| 0. 前置(一次) | install.sh | 命令/agent 装好 | Docker Desktop 可用 |
| 1. 线索与初评 | — | 接/不接判断 | 不轻易承诺范围与工期 |
| 2. 需求确认 | /web:spec | PRD.md + QUOTE.md | PRD 必须有「验收标准」与「不在范围内」 |
| 3. 报价与合同 | — | 合同签署 | 未收定金不开工 |
| 4. 初始化 | /web:new | 项目目录 + git | 不改 template 本身 |
| 5. 开发 | /web:model → /web:ui → /web:admin | 代码 | 每模块完成跑 tsc --noEmit |
| 6. 自检 | /web:check | CHECKLIST.md | 阻塞项未清零不验收/部署 |
| 7. 客户验收 | — | 验收确认 | 范围外走变更计费 |
| 8. 部署 | /web:deploy | DEPLOY.md | 部署前 backup;需人工确认 |
| 9. 交付 | /web:handoff | docs/handoff/* | 凭据加密渠道交付,不入 git |
| 10. 收款与维护 | — | 收尾款 + 维护月费 | 维护边界以 docs/维护月费范围.md 为准 |
阶段 2 的关键:PRD 必须有边界
/web:spec 生成的 docs/PRD.md 强制包含两节:验收标准(逐条可勾选)与不在范围内(明确边界,防需求蔓延)。这两节是后续验收与变更计费的依据。没有这两节,客户可以说「这个不就算送的吗」,你没有反驳依据。
阶段 3 的红线:未收定金不开工
这是商业红线,不是技术红线。SOP.md 明确:付款节点为定金 40% → 中期 30% → 尾款 30%,开工以收到定金为准。即使 Claude Code 让你「一天就能出原型」,也不代表你可以免定金开工——定金是诚意金,不是工钱。
29.7 商务要素:报价/合同/验收/维护月费
docs/ 下五份模板把商务固化。这不是法律文本,是核对清单——用客户或平台的标准合同,以此清单核对遗漏。
报价单:WBS + 付款节点 + 变更计费
报价单核心是工作分解(WBS),基准工时(企业官网/落地页):
| 模块 | 人日 |
|---|---|
| 需求/设计 | 1–2 |
| 前台页面 | 0.5–1/页 |
| 后台 CMS | 2–4 |
| 表单/留言 | 0.5 |
| SEO | 0.5 |
| 部署上线 | 1 |
| 培训/交付 | 0.5 |
加维护月费选项(全包托管),不含项(域名、服务器、素材版权),变更计费规则(范围外按人日计)。这三段——不含项、变更计费、维护边界——是防扯皮的三道闸门。
合同要点:九节核对清单
合同要点分九节:标的与范围、价款与付款、工期与里程碑、知识产权、维护、变更、验收、违约与终止、保密与数据。其中两节最常被忽略:
知识产权:自定义业务源码交付客户;开发者保留脚手架/通用模块复用权与作品集展示权(脱敏)。这一条是「一人公司」复用模式的法律基础——web-studio/template 本身是你的资产,不随单交付。
验收:以 PRD「验收标准」为准;客户约定期限内不反馈视为通过。这一条防止「客户拖着不验收也不付尾款」。
维护月费:标准维护 + 不含项 + SLA
维护月费模板明确「含」与「不含」:
- 含:内容更新(限 N 篇/月)、Bug 修复、定期备份、可用性监控、SSL 续期、安全补丁
- 不含(另计):新功能开发、大规模改版、内容批量录入、第三方服务费用
SLA:响应工作日 X 小时、紧急故障 X 小时、可用性 99.5%。续费按月/季/年预付,逾期暂停维护。
失败边界:维护边界不清。最典型的扯皮是「改个文案算不算维护」「加个 banner 算不算新功能」。模板用「限 N 篇/月」「新功能另计」把灰色地带量化,但 N 必须在签维护合同时填死,不能口头约定。
29.8 Docker 部署:compose + Caddyfile
部署是流水线的最后一公里,也是客户最关心的「能不能上线」。
docker-compose:app + db + caddy 三件套
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
app:
build: .
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
AUTH_SECRET: ${AUTH_SECRET}
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro本地与生产共用一份 compose,差异仅在 .env 与 Caddyfile 的 SITE_DOMAIN。本地 SITE_DOMAIN 缺省走 localhost(http),生产设为真实域名(Caddy 自动签 HTTPS)。这是「一份配置两种环境」的极简实现。
Dockerfile:多阶段 standalone
多阶段构建,最终镜像只含 node server.js + standalone 产物 + static 资源,无 prisma CLI、无 node_modules 全量。迁移由 scripts/deploy.sh 在服务器上用 prisma migrate deploy 执行,不进容器——容器是无状态的,迁移是有状态的,两者必须分离。
prisma generate 在 builder 阶段跑,用占位 DATABASE_URL(不连库);运行期由 compose 注入真实值。这是 Prisma 7 driver adapter 模式的正确姿势——adapter 在运行期才创建连接。
Caddyfile:自动 HTTPS
{$SITE_DOMAIN:localhost} {
encode gzip zstd
reverse_proxy app:3000
}两行配置,localhost 走 http,真实域名自动申请并续期 Let's Encrypt 证书。Caddy 比手动 certbot 省一个运维项,对一人公司接单是最佳选择。
迁移:换服务器标准流程
客户换服务器是接单常见需求,docs/迁移手册.md 标准化八步:
1. 旧服务器 bash scripts/backup.sh → backups/db-YYYYMMDD-HHMMSS.sql.gz
2. 上传新服务器:git clone + .env + public/uploads + backups/xxx.sql.gz
3. docker compose up -d db
4. bash scripts/restore.sh backups/xxx.sql.gz
5. bash scripts/deploy.sh (migrate deploy + build + up)
6. DNS A 记录指向新 IP
7. 验证:访问域名、登录后台、核对数据
8. 确认无误后停旧实例回滚:迁移失败,DNS 切回旧 IP(旧实例未停即可恢复)。
验证:curl -I https://域名 返回 200;后台登录正常;数据库记录数与旧服务器一致。
失败边界:部署配置泄密。.env 含 AUTH_SECRET、数据库密码、管理员密码,绝不进 git、不进镜像、不进日志。Dockerfile 不 COPY .env;.gitignore 必含 .env;/web:handoff 的账号交接表单独加密渠道交付。AUTH_SECRET 用 openssl rand -base64 32 生成,每个客户独立。一次泄密毁所有客户信任。
29.9 验证与失败边界
验证矩阵
整条流水线的验证分三层:
| 层 | 命令 | 通过标准 |
|---|---|---|
| 类型 | npx tsc --noEmit | 0 error |
| 规范 | npm run lint | 0 error(eslint flat config) |
| 构建 | npm run build | 成功(需 db 运行) |
| 安全 | /web:check | CHECKLIST.md 阻塞项为 0 |
| 部署 | curl -I https://域名 | 200 + HTTPS 有效 |
ui-builder 与 api-builder 每次完成后跑第一层;qa-reviewer 跑前三层 + 安全 checklist;/web:deploy 上线后跑第五层。三层之间不替代——类型过不等于构建过,构建过不等于安全过。
四类失败边界
一、脚手架与客户需求不匹配。本脚手架强项是企业官网/落地页 + 轻量 CMS。遇到复杂 SaaS(多租户、工作流)、电商(库存、支付、对账)、多语言(i18n 路由)、社交(实时、用户关系),要评估接或转介。硬塞脚手架的结果是:80% 的时间在改脚手架的预设模型,反而比从零写更慢。SOP 第 1 步(线索与初评)就是干这件事,不要跳过。
二、agent 越权。ui-builder 改后台、api-builder 改 UI、qa-reviewer 动手修 Bug。子代理的 frontmatter description 与「职责」段是契约,主代理派任务时把边界复述一遍。发现越权立即叫停,回退到闭环步骤 1。预防手段:工具权限最小化(qa-reviewer 不给 Write/Edit)。
三、部署配置泄密。.env 入 git、AUTH_SECRET 用占位上线、数据库密码硬编码、账号表明文交付——任何一条都是红线。/web:check 会扫「.env 未进版本控制、无硬编码密钥」,但这是机器检查,人工复核不可省。/web:handoff 的账号交接表必须加密渠道单独交付,提醒客户改密。
四、维护边界不清。「改个文案算不算维护」「加个 banner 算不算新功能」「服务器挂了算不算故障」——没有 docs/维护月费范围.md 锁死边界,这些问题会耗掉你所有利润。维护合同必须填死:N 篇/月、SLA 响应时间、可用性目标、不含项清单。口头约定等于没约定。
一条容易被忽略的边界:作品集展示权
合同要点里有一条「开发者保留作品集展示权(脱敏)」。这是「一人公司」复用模式的法律基础——你做的每一个客户项目,都可以(脱敏后)放进作品集,作为接下一单的信用背书。没有这一条,客户可以以「保密」为由阻止你展示,你的获客成本就会越来越高。这一条必须在签合同时写入,不要事后补。
29.10 小结
接单开发网站的 Claude Code 用法,可以浓缩成一句:
脚手架固化代码,命令固化操作,子代理固化质量,SOP 固化商务,Docker 固化部署。
五层固化,每层都把「我当时状态」换成「流水线默认值」。这正是一人公司对抗产能波动的唯一办法——你状态最好的时候把流程做对,状态下滑时流水线替你兜底。
本章与第 06 章的可执行闭环、第 19 章的子代理是同一条主线:闭环七步从「一次任务」放大到「一次交付」;子代理从「主代理的助手」升级为「流水线的工位」。第 32 章会从成本角度重新审视这条流水线——每个子代理的 token 消耗、每次 /web:check 的迭代次数,都会进到接单的成本核算里。
上一章:第 28 章 内容生产工作流