跳转至

SaaS 应用 — 项目 CLAUDE.md

Next.js + Supabase + Stripe SaaS 应用的实际示例。 请将其复制到项目根目录,并根据技术栈进行自定义。

项目概览

技术栈: Next.js 15(App Router), TypeScript, Supabase(认证 + 数据库), Stripe(计费), Tailwind CSS, Playwright(E2E)

架构: 默认使用服务器组件。客户端组件仅用于交互性。Webhook 使用 API 路由,变更操作使用服务器操作。

重要规则

数据库

  • 所有查询都使用启用 RLS 的 Supabase 客户端 — 绝不绕过 RLS
  • 迁移文件写入 supabase/migrations/ — 不要直接修改数据库
  • 使用显式列列表的 select(),而不是 select('*')
  • 所有面向用户的查询都包含 .limit(),以防止无限制的结果

认证

  • 服务器组件中使用 @supabase/ssr 的 createServerClient()
  • 客户端组件中使用 @supabase/ssr 的 createBrowserClient()
  • 受保护的路由需检查 getUser() — 不要仅依赖 getSession() 进行认证
  • middleware.ts 中的中间件会在每个请求中更新认证令牌

计费

  • Stripe Webhook 处理器放置在 app/api/webhooks/stripe/route.ts
  • 不要信任客户端的价格数据 — 始终从 Stripe 服务端获取
  • 订阅状态通过由 Webhook 同步的 subscription_status 列进行确认
  • 免费套餐用户:3 个项目、100 次 API 调用/天

代码风格

  • 代码和注释中不要使用表情符号
  • 仅使用不可变模式 — 使用展开运算符,不要修改原值
  • 服务器组件:没有 'use client' 指令,没有 useState/useEffect
  • 客户端组件:开头添加 'use client',保持最小化 — 将逻辑提取到 Hook 中
  • 优先使用 Zod 模式对所有 API 路由、表单和环境变量输入进行验证

文件结构

src/
  app/
    (auth)/          # 認証ページ(ログイン、サインアップ、パスワード忘れ)
    (dashboard)/     # 保護されたダッシュボードページ
    api/
      webhooks/      # Stripe、Supabaseウェブフック
    layout.tsx       # プロバイダー付きルートレイアウト
  components/
    ui/              # Shadcn/uiコンポーネント
    forms/           # バリデーション付きフォームコンポーネント
    dashboard/       # ダッシュボード固有のコンポーネント
  hooks/             # カスタム Reactフック
  lib/
    supabase/        # Supabaseクライアントファクトリー
    stripe/          # Stripeクライアントとヘルパー
    utils.ts         # 汎用ユーティリティ
  types/             # 共有TypeScript型
supabase/
  migrations/        # データベースマイグレーション
  seed.sql           # 開発用シードデータ

主要模式

API 响应格式

type ApiResponse<T> =
  | { success: true; data: T }
  | { success: false; error: string; code?: string }

服务器操作模式

'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=     # サーバーのみ、クライアントに公開しない

# Stripe
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=

# アプリ
NEXT_PUBLIC_APP_URL=http://localhost:3000

测试策略

/tdd                    # 新機能のユニット + 統合テスト
/e2e                    # 認証フロー、課金、ダッシュボードのPlaywrightテスト
/test-coverage          # 80%以上のカバレッジを確認

重要 E2E 流程

  1. 注册 → 邮箱验证 → 创建第一个项目
  2. 登录 → 仪表盘 → CRUD 操作
  3. 升级套餐 → Stripe 结账 → 订阅生效
  4. Webhook:取消订阅 → 降级到免费套餐

ECC 工作流

# 機能の計画
/plan "Add team invitations with email notifications"

# TDDによる開発
/tdd

# コミット前
/code-review
/security-scan

# リリース前
/e2e
/test-coverage

Git 工作流

  • feat: 新功能、fix: 修复 bug、refactor: 代码变更
  • 从 main 创建功能分支,需要 PR
  • CI 中运行:Lint、类型检查、单元测试、E2E 测试
  • 部署:PR 的 Vercel 预览,合并到 main 时部署到生产环境

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC