SaaS 应用 — 项目 CLAUDE.md¶
适用于 Next.js + Supabase + Stripe 的 SaaS 应用的真实示例。 复制到项目根目录,并根据你的技术栈进行自定义。
项目概览¶
技术栈: Next.js 15 (App Router), TypeScript, Supabase (auth + DB), Stripe (billing), Tailwind CSS, Playwright (E2E)
架构: 默认使用 Server Components。Client Components 仅用于交互。API 路由用于 webhooks,server actions 用于变更操作。
关键规则¶
数据库¶
- 所有查询都使用启用 RLS 的 Supabase 客户端——绝不绕过 RLS
- 迁移文件位于
supabase/migrations/——绝不直接修改数据库 - 使用
select()并显式列出列,不要使用select('*') - 所有面向用户的查询都必须包含
.limit(),以避免无限制结果
身份验证¶
- 在 Server Components 中使用
@supabase/ssr的createServerClient() - 在 Client Components 中使用
@supabase/ssr的createBrowserClient() - 受保护路由检查
getUser()——不要仅依赖getSession()进行身份验证 middleware.ts中的中间件会在每次请求时刷新身份验证令牌
计费¶
- Stripe webhook 处理器位于
app/api/webhooks/stripe/route.ts - 绝不信任客户端价格——始终在服务端从 Stripe 获取
- 订阅状态通过
subscription_status列检查,并由 webhook 同步 - 免费套餐用户:3 个项目,100 次 API 调用/天
代码风格¶
- 代码或注释中不使用表情符号
- 仅使用不可变模式——使用展开运算符,绝不修改
- Server Components:不使用
'use client'指令,不使用useState/useEffect - Client Components:在顶部添加
'use client',并尽可能最小化——将逻辑提取到 hooks 中 - 优先使用 Zod schema 进行所有输入验证(API 路由、表单、环境变量)
文件结构¶
src/
app/
(auth)/ # Auth pages (login, signup, forgot-password)
(dashboard)/ # Protected dashboard pages
api/
webhooks/ # Stripe, Supabase webhooks
layout.tsx # Root layout with providers
components/
ui/ # Shadcn/ui components
forms/ # Form components with validation
dashboard/ # Dashboard-specific components
hooks/ # Custom React hooks
lib/
supabase/ # Supabase client factories
stripe/ # Stripe client and helpers
utils.ts # General utilities
types/ # Shared TypeScript types
supabase/
migrations/ # Database migrations
seed.sql # Development seed data
关键模式¶
API 响应格式¶
type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: string; code?: string }
Server Action 模式¶
'use server'
import { z } from 'zod'
import { createServerClient } from '@/lib/supabase/server'
const schema = z.object({
name: z.string().min(1).max(100),
})
export async function createProject(formData: FormData) {
const parsed = schema.safeParse({ name: formData.get('name') })
if (!parsed.success) {
return { success: false, error: parsed.error.flatten() }
}
const supabase = await createServerClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return { success: false, error: 'Unauthorized' }
const { data, error } = await supabase
.from('projects')
.insert({ name: parsed.data.name, user_id: user.id })
.select('id, name, created_at')
.single()
if (error) return { success: false, error: 'Failed to create project' }
return { success: true, data }
}
环境变量¶
# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY= # Server-only, never expose to client
# Stripe
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
# App
NEXT_PUBLIC_APP_URL=http://localhost:3000
测试策略¶
/tdd # Unit + integration tests for new features
/e2e # Playwright tests for auth flow, billing, dashboard
/test-coverage # Verify 80%+ coverage
关键 E2E 流程¶
- 注册 → 邮箱验证 → 创建第一个项目
- 登录 → 仪表盘 → CRUD 操作
- 套餐升级 → Stripe 结账 → 订阅激活
- Webhook:订阅取消 → 降级到免费套餐
ECC 工作流¶
# Planning a feature
/plan "Add team invitations with email notifications"
# Developing with TDD
/tdd
# Before committing
/code-review
/security-scan
# Before release
/e2e
/test-coverage
Git 流程¶
feat:新功能,fix:缺陷修复,refactor:代码重构- 功能分支从
main创建,必须提交 PR - CI 运行:lint、类型检查、单元测试、E2E 测试
- 部署:PR 中生成 Vercel 预览,合并到
main后部署到生产环境
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC