Quy Trình Phát Triển Sản Phẩm
Quy trình from scratch, ~100% Claude Code. Người 5 touchpoint: bootstrap, confirm PRD, approve PR, verify URL, rollback. 9 bước có flow rõ, mermaid với loop decision, stack chuẩn, defaults, filename convention, KPI outcome + process.
Quy trình triển khai web app from scratch, ~100% bằng Claude Code. Người làm 2 việc: bootstrap môi trường một lần (CLI access, secrets), và verify đầu ra mỗi PR + URL prod. Claude làm phần còn lại — draft PRD, init repo, code, migration, security wiring, deploy verify, doc.
Khi nào áp dụng
Web app từ idea → production. Có người dùng cuối, có URL công khai, vận hành dài hạn.
| Loại dự án | Áp dụng | Variation chính |
|---|---|---|
| Internal product có UI | Đầy đủ 9 bước | Auth = internal (xem Stack chuẩn) |
| External / customer product | Đầy đủ 9 bước | Auth = external (xem Stack chuẩn) |
| Backend service / API-only | Đầy đủ 9 bước | Bước 5 skip frontend. Bước 9 dùng docs/API.md thay USER_GUIDE.md |
| POC / spike ≤ 5 ngày | Bước 1-5 + 7-8 | Skip Bước 6 (security audit) + 9 (doc gate). Không deploy prod chính thức |
| Library / SDK | Không | Lifecycle khác (npm publish, versioning) — cần quy trình riêng |
Phân vai Claude vs Người
Claude Code làm (autonomous)
- Draft
docs/PRD.mdtừ user prompt. - Init repo, branch, stack,
CLAUDE.md,.env.example, Railway link. - Plan mode + implementation cho mọi feature.
- Schema, migration, API, frontend, security wiring.
- Push lên feature branch, mở PR vào
stagingrồimain. - Verify URL prod sau deploy, smoke test golden path qua
curl. - Viết tất cả doc (
README,ARCHITECTURE,USER_GUIDEhoặcAPI,CHANGELOG). - Viết postmortem khi có incident.
Người làm (5 touchpoint)
- Bootstrap (1 lần per product): CLI login, tạo Railway project, set secrets. Chi tiết ở section "Bootstrap" cuối doc.
- Confirm PRD draft (sau Bước 1): đọc draft, OK hoặc gửi revision.
- Approve PR vào
main(Bước 7): review code + plan trong PR, click merge. Đây là gate duy nhất chặn Claude push prod. - Verify URL prod (Bước 8): mở URL sau auto-deploy, test golden path manual.
- Decide rollback (Bước 8): khi Claude báo critical issue trong 30 phút đầu sau ship.
Tổng quan quy trình
flowchart TD
Boot([Bootstrap 1 lần: gh + railway + secrets])
Boot --> B1[Bước 1: Draft PRD<br/>Claude viết → người confirm]
B1 --> B2[Bước 2: Init project<br/>repo + stack + CLAUDE.md + Railway link]
B2 --> B3{Bước 3: Plan feature<br/>lưu docs/plans/}
B3 --> B4[Bước 4: Build Backend<br/>schema → API → test]
B4 --> B5[Bước 5: Build Frontend<br/>UI → tích hợp API → test]
B5 --> B6[Bước 6: Security audit<br/>checklist trước PR]
B6 --> B7[Bước 7: PR + merge<br/>Claude push → người approve]
B7 --> B8[Bước 8: Verify deploy<br/>Railway auto → curl → người mở URL]
B8 --> Loop{Còn feature<br/>trong PRD?}
Loop -->|yes| B3
Loop -->|no| B9{Bước 9: Doc gate<br/>4-5 file đầy đủ?}
B9 -->|chưa đủ| B9
B9 -->|đủ| End([Dự án xong → adoption])
classDef gate fill:#fef3c7,stroke:#b45309,color:#1f2937;
classDef human fill:#fce7f3,stroke:#be185d,color:#1f2937;
classDef phase fill:#dbeafe,stroke:#1d4ed8,color:#1f2937;
class B3,Loop,B9 gate;
class B1,B7,B8 human;
class Boot,End phase;
- Node hồng = có người touchpoint. Còn lại Claude tự chạy.
- Node vàng = decision gate. Không qua thì không đi tiếp.
- Loop sau Bước 8: check PRD còn feature nào chưa làm. Hết thì sang Bước 9 đóng project.
Quy trình
Bước 1. Draft PRD
Claude — draft docs/PRD.md từ user prompt. Người — đọc và confirm.
PRD là input cho mọi bước sau. Phải có:
- Ai dùng, dùng vào điểm nào trong workflow hiện tại.
- Baseline đo được (giờ công, chi phí, cơ hội mỗi tháng).
- Định nghĩa "MVP đã ship" và "đã có giá trị" — hai mốc tách biệt.
- Hành vi đích: thao tác cũ nào bị thay thế, thao tác mới nào xuất hiện.
- Feature list theo priority order — Claude sẽ thực thi tuần tự, lặp Bước 3-8 cho từng feature.
- Override defaults (nếu có): chỉ định stack / auth provider / theme khác mặc định.
Fail mode: PRD chỉ liệt kê feature, không spec workflow. Claude build đúng spec, sai context.
Bước 2. Init project
Claude — tự tạo repo và setup stack. Người — không có touchpoint (Bootstrap đã xử lý đầu).
- Tạo repo:
gh repo create seongon-agency/{product-slug} --private --clone. - Tạo branch:
git checkout -b staging, push lên remote. Cấu trúcmain= production,staging= đệm. - Init stack theo Stack chuẩn (xem section dưới — version exact tại thời điểm init lock vào
CLAUDE.md):bunx create-next-app@latest . --typescript --tailwind --app --src-dir falsebunx shadcn@latest initbun add drizzle-orm drizzle-kit better-sqlite3 pgbun add -d @biomejs/biome
- Setup DB: Drizzle config — schema ở
db/schema.ts, client lazy-init ởdb/index.ts. Dev = SQLite (data/dev.db), prod = Postgres (DATABASE_URLtừ Railway). - Link Railway:
railway linkvới project đã tạo lúc Bootstrap. Verify env injection:railway run --service Postgres bun -e "console.log(process.env.DATABASE_PUBLIC_URL?.slice(0,20))"ra prefix có nghĩa. - Viết
CLAUDE.mdngay với các section: Stack (version exact lock), Build commands, Env vars, Architecture overview, Naming convention, Deploy target. Reference SOP này ở đầu. - Commit
.env.examplevới key name + placeholder..env.localtrong.gitignore. - Initial commit lên
main(chỉ 1 lần — sau commit này, Claude không tự pushmainnữa). Custom domain set qua Railway dashboard.
Fail mode: Skip viết
CLAUDE.mdđể "nhanh". Feature sau Claude đoán convention, mỗi feature một style.
Bước 3. Plan feature
Claude — viết plan vào docs/plans/{date}-{slug}.md. Người — review trong PR (Bước 7).
"Non-trivial" trigger plan mode: đụng ≥ 2 file, hoặc cross-layer (DB + API hoặc API + UI), hoặc đụng auth / security / migration / billing. Edit CSS đơn lẻ hoặc fix typo: skip plan mode.
- Output plan = todo list, file paths cụ thể + mô tả thay đổi cụ thể.
- Lưu vào
docs/plans/{YYYY-MM-DD}-{feature-slug}.mdtrước khi code. - Plan + code đẩy chung 1 PR. Người review cả plan và diff.
- Plan >10 bước hoặc cross 3+ layer (DB + API + UI) → tách nhiều plan + nhiều PR nối tiếp.
- 1 plan = 1 feature. Không gộp.
Fail mode: Skip plan vì "feature đơn giản". Khi feature mở rộng, không có plan để hiểu intent → rewrite 2-3 lần.
Bước 4. Build Backend
Claude — schema + API + test. Người — review trong PR.
Backend xong trước Frontend. Thứ tự bắt buộc:
- Schema database: Drizzle migration trước, không sửa schema lén trong code. Test ở SQLite dev → deploy → chạy migration prod qua
railway run drizzle-kit migrate. (Pattern từ incident có thật.) - API routes + business logic: đặt trong
lib/, expose quaapp/api/. Convention response:{ success, data?, error? }. Logic doanh nghiệp → API Endpoint cụ thể (vd/api/images/upload). - Test API bằng
curlhoặcbun test. Test case = input + expected output cụ thể, viết trong plan. - Update
docs/ARCHITECTURE.mdtrong cùng PR — schema thay đổi → doc thay đổi.
API là contract giữa Backend và Frontend. Cấu trúc API phải ổn định trước khi Frontend bắt đầu.
Fail mode: Build frontend trước, backend uốn theo UI. Schema sai → migration sửa nhiều lần → dữ liệu cũ corrupt.
Bước 5. Build Frontend
Claude — implement UI + tích hợp API. Người — verify trên staging URL (Bước 7-8).
- App Router default: server component,
'use client'chỉ khi cần state hoặc effect. - Component primitive (button, dialog, dropdown): dùng
components/ui/có sẵn từ shadcn init. Không tự design lại. - Tích hợp API qua fetch ở server component hoặc
useEffect/SWR ở client. - Theme: light only mặc định. Implement dark mode chỉ khi PRD chỉ định (xem Defaults).
- Style guide: chỉ viết
style_guide.mdkhi đã có ≥ 2 unique UI pattern (button variant, card style, ...). Đừng viết sớm khi chưa có pattern. - Test luồng end-to-end trên local
bun devtrước khi push.
Fail mode: Mỗi component một style. App trông như patchwork. API work nhưng giao diện lỗi.
Bước 6. Security audit
Claude — chạy checklist trước khi mở PR. Người — verify trong PR review.
Security là cross-cutting concern — phần lớn rule phải được wire trong Bước 4 và 5. Bước 6 là audit cuối cùng trước khi PR — nếu có hạng mục chưa qua, quay lại Bước 4 hoặc 5 để fix, không skip.
| Check | Áp dụng từ Bước | Đánh giá |
|---|---|---|
requireAuth() trên mọi API route, public route opt-out có comment lý do | Bước 4 | Grep app/api/ — mọi route export GET/POST/PATCH/DELETE phải có auth check |
Per-user data isolation — mọi DB query filter theo user_id | Bước 4 | Grep schema có FK user_id → mọi query reference table đó phải có filter. (Pattern từ incident có thật.) |
| Secret + API key bên thứ 3 chỉ server-side | Bước 4-5 | Grep NEXT_PUBLIC_ — chỉ được dùng cho public config, không cho secret |
| CORS allow domain cụ thể, không wildcard | Bước 4 | Check middleware / next.config — domain Frontend whitelist |
.env* trong .gitignore, .env.example chỉ key name + placeholder | Bước 2 | git ls-files | grep .env ra chỉ .env.example |
| Auth provider integration đúng pattern (xem Stack chuẩn) | Bước 2 | Internal: redirect auth.seongonai.com. External: Clerk middleware ở root |
Fail mode: Bỏ qua audit, để PR review tự phát hiện. Reviewer miss → user A đọc data user B → sự cố pháp lý. (Pattern từ incident có thật.)
Bước 7. PR + merge
Claude — push branch, mở PR. Người — review code + plan trong PR, click merge khi OK.
- Push feature branch:
git checkout -b feat/{slug}, commit,git push -u origin feat/{slug}. - Mở PR vào
stagingquagh pr create. Body PR include: linkdocs/plans/{date}-{slug}.md, tóm tắt thay đổi, security checklist result (Bước 6). - Merge vào
stagingsau CI green. Nếu có Railway staging environment, auto-deploy lên đó để verify. - Mở PR
staging → mainkhi feature ổn định trên staging. - Người approve PR vào
main= trigger Railway auto-deploy prod (Bước 8 xử lý phần sau). Claude không tự mergemain.
Fail mode: Claude push thẳng vào
main. Hoặc người approve PR mà không đọc plan. Khi vỡ, không biết feature nào gây.
Bước 8. Verify deploy + rollback nếu cần
Claude — chạy verify sequence sau auto-deploy. Người — mở URL trên browser, test golden path. Người trigger rollback khi Claude báo critical.
Verify flow (chạy sau mỗi merge vào main)
- Railway báo deploy success (nếu fail → Claude phân tích log, mở incident issue, không claim ship).
- Claude chạy
railway logs --tail 50— check không có error / warning ngoài kỳ vọng. - Claude
curlURL prod golden path — verify response status + body content match expectation. - Claude báo "verified" trong PR comment + tag người.
- Người mở URL trên browser, test golden path manual (UI thật, không chỉ curl).
- "Đã ship" chỉ khi cả Claude verify + người verify đều OK. Không phải khi Railway báo build pass. (Pattern từ incident có thật.)
Rollback procedure (30 phút đầu sau ship)
Critical issue = 500 trên golden path / data corruption / auth bypass. Khi Claude phát hiện hoặc người báo:
- Người trigger rollback:
railway deployment list→railway rollback <previous-deployment-id>. - Claude note vào
CHANGELOG.md: rollback khi nào, vì lý do gì. - Claude mở incident issue trên GitHub + viết postmortem trong 48h vào
docs/postmortems/{date}-{slug}.md. - Fix root cause trên branch riêng → staging → main → re-deploy. Không re-deploy thẳng main khi chưa fix root.
Fail mode: Claude báo "deploy xong" khi Railway báo build pass nhưng chưa mở URL. Endpoint 500, user là người phát hiện đầu tiên.
Bước 9. Doc gate (đóng project)
Claude — viết và update doc trong từng PR. Bước 9 là verify cuối cùng trước khi tuyên bố project xong. Người — review checklist.
Bắt buộc cho mọi product
README.md— overview, quick start, env vars, link sangdocs/.CLAUDE.md— sync với reality. Stack đổi → update trong cùng PR.docs/ARCHITECTURE.md— schema DB, data flow, directory structure.CHANGELOG.md— version + change theo release. Update mỗi PR vào main.
Conditional theo loại product
docs/USER_GUIDE.md— bắt buộc khi product có UI end-user. Có screenshot nếu UI phức tạp.docs/API.md— bắt buộc khi API-only / backend service. List endpoints, request/response schema, curl examples.
Không có doc đủ = user không onboard được = adoption không xảy ra.
Fail mode: Code xong "để mai viết doc". Không bao giờ có "mai".
Bootstrap (1 lần per product)
Người làm 1 lần TRƯỚC khi Claude bắt đầu Bước 1.
- Anthropic API key: đăng ký key, set vào Claude Code config trên máy dev.
- GitHub CLI:
gh auth loginvào account có access orgseongon-agency. - Railway CLI:
railway login. Tạo project mới (qua dashboard hoặcrailway init). Add Postgres service. - Set secrets trên Railway dashboard cho project mới:
DATABASE_URLreference${{Postgres.DATABASE_URL}}(Railway syntax).ANTHROPIC_API_KEY/OPENAI_API_KEYnếu dùng LLM trong runtime.CLERK_SECRET_KEY+CLERK_PUBLISHABLE_KEYnếu external product dùng Clerk.- API key của bên thứ 3 sản phẩm cần (DataForSEO, ...).
- Hand-off prompt cho Claude (ví dụ):
Build sản phẩm "[tên]" theo SOP tại handbook.seongonai.com/project-management/quy-trinh-phat-trien-san-pham Bootstrap đã xong: - Railway project: [tên project] - Org GitHub: seongon-agency - Secrets đã set: DATABASE_URL, [API keys cần] Brief: [1-2 đoạn về sản phẩm — Claude sẽ draft PRD đầy đủ ở Bước 1]
Từ điểm này, Claude tự chạy. Người chỉ touchpoint khi confirm PRD, approve PR, verify URL, decide rollback.
Stack chuẩn
Khi Claude init project mới, dùng exact stack này. Version exact lock vào CLAUDE.md tại thời điểm init.
| Layer | Tech | Lệnh init |
|---|---|---|
| Framework + Language + Styling | Next.js (App Router) + TypeScript + Tailwind v4 | bunx create-next-app@latest . --typescript --tailwind --app |
| Runtime | Bun (latest 1.x) | Đã có khi Claude làm việc trên máy dev |
| Component library | shadcn/ui | bunx shadcn@latest init ngay sau Next init |
| ORM + DB driver | Drizzle + better-sqlite3 (dev) + pg (prod) | bun add drizzle-orm drizzle-kit better-sqlite3 pg |
| Dev DB | SQLite (data/dev.db) | via bun:sqlite hoặc better-sqlite3 |
| Prod DB | PostgreSQL trên Railway | Railway tự cấp khi add Postgres service |
| Lint + format | Biome | bun add -d @biomejs/biome + biome.json config |
| Deploy | Railway via nixpacks.toml | Railway auto-detect Next.js, nixpacks.toml chỉ cần khi custom build |
Backend variants — chỉ khi PRD yêu cầu
| Use case | Tech |
|---|---|
| Heavy data / AI processing | Python (FastAPI) — service riêng, gọi qua HTTP |
| Heavy concurrency / hiệu năng | Go — service riêng, gọi qua HTTP |
Auth variants
| Loại product | Provider | Integration |
|---|---|---|
| Internal (cho team SEONGON) | auth.seongonai.com | Redirect-based. Session ở Upstash Redis, cookie httpOnly, cross-domain qua redirect |
| External / customer | Clerk (default) | Clerk middleware + <ClerkProvider> ở root. Override sang Auth0 / NextAuth / Lucia chỉ khi PRD chỉ định |
Defaults khi PRD không chỉ định
Claude pick option đầu, chuyển tiếp. Override khi PRD nói khác.
| Decision | Default | Override khi |
|---|---|---|
| Theme | Light only, không implement dark mode | PRD yêu cầu dark mode |
| Dev DB | SQLite local | Schema cần feature Postgres-only (jsonb, gin index, fulltext) |
style_guide.md timing | Viết sau khi có ≥ 2 unique UI pattern | PRD chỉ định design system từ đầu |
| Test framework | bun test cho unit, curl scripts cho API | PRD cần E2E → Playwright |
| i18n | Tiếng Việt only | PRD chỉ định product đa ngôn ngữ |
| Analytics | Không add | PRD yêu cầu (vd PostHog, GA4) |
Filename convention
| File | Mục đích | Tạo trong |
|---|---|---|
docs/PRD.md | Product Requirements Document | Bước 1 |
CLAUDE.md (root) | Context contract — Claude đọc mỗi session | Bước 2 |
.env.example (root) | Template env vars (key + placeholder) | Bước 2 |
db/schema.ts | Drizzle schema | Bước 2 (empty) → Bước 4 (per feature) |
docs/plans/{YYYY-MM-DD}-{feature-slug}.md | Plan mode output per feature | Bước 3 |
docs/ARCHITECTURE.md | Schema DB, data flow, directory structure | Bước 4, update mỗi PR đụng kiến trúc |
style_guide.md (root) | CSS variables, class convention | Khi cần (xem Defaults) |
docs/USER_GUIDE.md hoặc docs/API.md | Doc cho người dùng (xem Bước 9 conditional) | Bước 9 |
CHANGELOG.md (root) | Version + change theo release | Bước 9, update mỗi PR vào main |
docs/postmortems/{date}-{slug}.md | Postmortem khi rollback / incident | Bước 8 khi có incident |
Triết lý nền
Hai quyết định chiến lược khiến team này khác team dev truyền thống. Vi phạm bất kỳ điều nào = quay về làm lại, không phải fix sau.
- Team không code bằng tay. Mọi dòng code do Claude Code viết. Vai trò của người là PM, architect, reviewer — không phải scribe gõ phím.
- Plan mode trước mọi thay đổi non-trivial. Claude lưu plan vào
docs/plans/, người review trong PR.
Convention nền
Hai quy ước infra để triết lý trên hoạt động được.
CLAUDE.mdlà contract. Mỗi project cóCLAUDE.mdở root với stack version exact, commands, env vars, kiến trúc, naming convention. Claude đọc mỗi session — không có thì Claude đoán bừa.- Claude Code có CLI access vào hạ tầng thật. Railway CLI, GitHub CLI, database CLI — đăng nhập sẵn (Bootstrap xử lý). Không có = Claude là text editor, không phải agent.
KPI đo lường
Báo cáo quarterly lên leadership.
Outcome metrics (business)
| Metric | Target | Cách đo |
|---|---|---|
| Time PRD-confirmed → MVP ship | ≤ 4 tuần | git log first commit → first deploy success |
| Critical incident 7 ngày sau ship | 0 | Railway alerts + user reports |
| MTTR khi rollback | < 30 phút | Incident issue timestamps |
Process metrics (operational)
| Metric | Target | Cách đo |
|---|---|---|
% PR có plan ở docs/plans/ | ≥ 95% | Audit PR history |
| % dự án qua gate 9 (doc đủ trước khi đóng) | ≥ 90% | Lead audit weekly |
| Rollback rate (rollback / total deploy vào main) | < 5% | Railway deployment history |
| % rule violation phát hiện trong PR review | Giảm quarter-over-quarter | Reviewer tag #sop-violation |
Lưu ý cuối
Mọi rule trong doc này sinh ra từ incident hoặc pattern thất bại của team AI cũ. Doc này không phải triết lý trừu tượng — mỗi "Fail mode" và mỗi "(Pattern từ incident có thật)" là một sự cố ai đó đã trải qua.
Push back khi rule cản trở. Nếu thấy một rule không hợp lý với case hiện tại, push back trong PR review hoặc raise lên lead — đừng âm thầm bỏ qua. Rule sửa được; bỏ qua thì rule chết.
Cập nhật doc khi có incident mới. Rule mới muốn vào doc → phải có postmortem hoặc paragraph "vì sao". Rule không có evidence = không vào doc.