nextjs-supabase-saas-planner

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.js + Supabase SaaS Planner

Next.js + Supabase SaaS 规划工具

Overview

概述

Expert SaaS planning assistant that transforms product ideas into actionable technical roadmaps. Specializes in the Next.js 15 + Supabase stack, covering architecture design, database schemas, authentication patterns, multi-tenancy, billing integration, and launch strategies.
When to use this skill:
  • Planning a new SaaS product from scratch
  • Converting a product idea into technical specifications
  • Designing database schemas for SaaS applications
  • Setting up authentication and authorization
  • Implementing multi-tenant architecture
  • Integrating subscription billing (Stripe, Paddle)
  • Planning feature rollout and MVP priorities
  • Creating development roadmaps and timelines

专业的SaaS规划助手,可将产品创意转化为可执行的技术路线图。专注于Next.js 15 + Supabase技术栈,涵盖架构设计、数据库架构、认证模式、多租户方案、账单集成及上线策略。
何时使用该工具:
  • 从零开始规划新的SaaS产品
  • 将产品创意转化为技术规格
  • 为SaaS应用设计数据库架构
  • 搭建认证与授权体系
  • 实现多租户架构
  • 集成订阅账单系统(Stripe、Paddle)
  • 规划功能上线优先级与MVP范围
  • 创建开发路线图与时间线

Planning Workflow

规划工作流

Phase 1: Discovery and Requirements

第一阶段:需求调研

Ask these questions first:
  1. Product Overview
    • What problem does your SaaS solve?
    • Who is your target user?
    • What's your unique value proposition?
  2. Scale Expectations
    • How many users in first year? (0-1K, 1K-10K, 10K-100K, 100K+)
    • Expected growth rate?
    • Geographic distribution?
  3. Team and Timeline
    • Team size and skills?
    • Launch timeline? (MVP date)
    • Budget constraints?
  4. Business Model
    • Pricing tiers?
    • Free trial or freemium?
    • What features differentiate paid tiers?
  5. Core Features
    • Must-have features for MVP?
    • Nice-to-have features for v1.1?
    • Future roadmap ideas?
首先询问以下问题:
  1. 产品概述
    • 你的SaaS解决什么问题?
    • 目标用户是谁?
    • 核心价值主张是什么?
  2. 规模预期
    • 第一年预计用户量?(0-1K、1K-10K、10K-100K、100K+)
    • 预期增长率?
    • 用户地域分布?
  3. 团队与时间线
    • 团队规模与技能配置?
    • 上线时间线?(MVP发布日期)
    • 预算限制?
  4. 商业模式
    • 定价层级?
    • 是否提供免费试用或免费增值模式?
    • 付费层级的差异化功能有哪些?
  5. 核心功能
    • MVP必备功能?
    • v1.1版本的可选功能?
    • 未来路线图创意?

Phase 2: Technical Architecture Planning

第二阶段:技术架构规划

Based on requirements, create:
  1. System Architecture Diagram
  2. Database Schema
  3. File/Folder Structure
  4. Authentication Flow
  5. Multi-Tenancy Strategy (if applicable)
  6. Billing Integration Plan
  7. Deployment Architecture
基于需求,创建以下内容:
  1. 系统架构图
  2. 数据库架构
  3. 文件/文件夹结构
  4. 认证流程
  5. 多租户策略(如适用)
  6. 账单集成方案
  7. 部署架构

Phase 3: Development Roadmap

第三阶段:开发路线图

Create week-by-week plan:
  • Week 1-2: Setup and infrastructure
  • Week 3-4: Core features
  • Week 5-6: Authentication and billing
  • Week 7-8: Polish and testing
  • Week 9: Launch preparation

创建按周划分的计划:
  • 第1-2周:项目搭建与基础设施
  • 第3-4周:核心功能开发
  • 第5-6周:认证与账单系统集成
  • 第7-8周:优化与测试
  • 第9周:上线准备

Next.js 15 + Supabase Stack (2025)

Next.js 15 + Supabase 技术栈(2025)

Recommended Tech Stack

推荐技术栈

Frontend:
├─ Next.js 15 (App Router)
├─ React 19
├─ TypeScript
├─ Tailwind CSS
└─ shadcn/ui components

Backend:
├─ Next.js API Routes (Server Actions)
├─ Supabase Edge Functions (when needed)
└─ Stripe/Paddle webhooks

Database:
├─ PostgreSQL (Supabase)
├─ Row Level Security (RLS)
└─ Supabase Realtime (optional)

Authentication:
├─ Supabase Auth
├─ OAuth providers (Google, GitHub, etc.)
└─ Magic links

Storage:
└─ Supabase Storage (files, images)

Deployment:
├─ Vercel (frontend + API)
├─ Supabase (database, auth, storage)
└─ Cloudflare (CDN, DNS)

Payments:
├─ Stripe (recommended)
└─ Paddle (alternative)

Monitoring:
├─ Vercel Analytics
├─ Sentry (error tracking)
└─ PostHog (product analytics - optional)
Frontend:
├─ Next.js 15 (App Router)
├─ React 19
├─ TypeScript
├─ Tailwind CSS
└─ shadcn/ui components

Backend:
├─ Next.js API Routes (Server Actions)
├─ Supabase Edge Functions (when needed)
└─ Stripe/Paddle webhooks

Database:
├─ PostgreSQL (Supabase)
├─ Row Level Security (RLS)
└─ Supabase Realtime (optional)

Authentication:
├─ Supabase Auth
├─ OAuth providers (Google, GitHub, etc.)
└─ Magic links

Storage:
└─ Supabase Storage (files, images)

Deployment:
├─ Vercel (frontend + API)
├─ Supabase (database, auth, storage)
└─ Cloudflare (CDN, DNS)

Payments:
├─ Stripe (recommended)
└─ Paddle (alternative)

Monitoring:
├─ Vercel Analytics
├─ Sentry (error tracking)
└─ PostHog (product analytics - optional)

Why This Stack?

选择该技术栈的原因

Next.js 15:
  • Server Components for performance
  • Server Actions for mutations
  • Built-in caching strategies
  • Excellent DX and fast iteration
Supabase:
  • Instant REST API
  • Real-time subscriptions
  • Built-in authentication
  • Row-level security
  • Open source (no vendor lock-in risk)
TypeScript:
  • Type safety reduces bugs
  • Better IDE support
  • Self-documenting code
Stripe:
  • Industry standard for payments
  • Excellent documentation
  • Subscription management
  • Invoice handling

Next.js 15:
  • Server Components提升性能
  • Server Actions处理数据变更
  • 内置缓存策略
  • 出色的开发体验与快速迭代能力
Supabase:
  • 即时生成REST API
  • 实时订阅功能
  • 内置认证系统
  • 行级安全机制
  • 开源(无供应商锁定风险)
TypeScript:
  • 类型安全减少Bug
  • 更好的IDE支持
  • 自文档化代码
Stripe:
  • 支付领域行业标准
  • 完善的文档
  • 订阅管理功能
  • 发票处理能力

File Structure Template

文件结构模板

my-saas/
├─ app/
│  ├─ (auth)/
│  │  ├─ login/
│  │  │  └─ page.tsx
│  │  ├─ signup/
│  │  │  └─ page.tsx
│  │  ├─ forgot-password/
│  │  │  └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ (marketing)/
│  │  ├─ page.tsx                 # Landing page
│  │  ├─ pricing/
│  │  │  └─ page.tsx
│  │  ├─ about/
│  │  │  └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ (dashboard)/
│  │  ├─ dashboard/
│  │  │  └─ page.tsx
│  │  ├─ settings/
│  │  │  ├─ profile/
│  │  │  │  └─ page.tsx
│  │  │  ├─ billing/
│  │  │  │  └─ page.tsx
│  │  │  └─ team/
│  │  │     └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ api/
│  │  ├─ webhooks/
│  │  │  └─ stripe/
│  │  │     └─ route.ts
│  │  └─ og/                     # Open Graph images
│  │     └─ route.tsx
│  │
│  ├─ layout.tsx                  # Root layout
│  └─ globals.css
├─ components/
│  ├─ ui/                         # shadcn components
│  │  ├─ button.tsx
│  │  ├─ card.tsx
│  │  ├─ dialog.tsx
│  │  └─ ...
│  ├─ auth/
│  │  ├─ login-form.tsx
│  │  └─ signup-form.tsx
│  ├─ dashboard/
│  │  ├─ sidebar.tsx
│  │  └─ navbar.tsx
│  ├─ marketing/
│  │  ├─ hero.tsx
│  │  ├─ features.tsx
│  │  └─ pricing-cards.tsx
│  └─ shared/
│     ├─ user-avatar.tsx
│     └─ loading-spinner.tsx
├─ lib/
│  ├─ supabase/
│  │  ├─ client.ts              # Client-side Supabase
│  │  ├─ server.ts              # Server-side Supabase
│  │  └─ middleware.ts          # Auth middleware
│  ├─ stripe/
│  │  ├─ client.ts
│  │  └─ server.ts
│  ├─ db/
│  │  └─ queries.ts             # Database queries
│  ├─ auth.ts                   # Auth helpers
│  └─ utils.ts                  # General utilities
├─ hooks/
│  ├─ use-user.ts               # User session hook
│  ├─ use-subscription.ts       # Subscription status
│  └─ use-toast.ts
├─ actions/                      # Server Actions
│  ├─ auth/
│  │  ├─ login.ts
│  │  ├─ signup.ts
│  │  └─ logout.ts
│  ├─ billing/
│  │  ├─ create-checkout.ts
│  │  └─ cancel-subscription.ts
│  └─ profile/
│     └─ update-profile.ts
├─ types/
│  ├─ database.ts               # Generated from Supabase
│  ├─ supabase.ts
│  └─ stripe.ts
├─ supabase/
│  ├─ migrations/               # Database migrations
│  │  └─ 20250101000000_initial.sql
│  ├─ functions/                # Edge Functions (optional)
│  └─ config.toml
├─ public/
│  ├─ images/
│  └─ icons/
├─ middleware.ts                 # Next.js middleware (auth)
├─ next.config.js
├─ tailwind.config.js
├─ tsconfig.json
├─ package.json
└─ .env.local

my-saas/
├─ app/
│  ├─ (auth)/
│  │  ├─ login/
│  │  │  └─ page.tsx
│  │  ├─ signup/
│  │  │  └─ page.tsx
│  │  ├─ forgot-password/
│  │  │  └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ (marketing)/
│  │  ├─ page.tsx                 # 落地页
│  │  ├─ pricing/
│  │  │  └─ page.tsx
│  │  ├─ about/
│  │  │  └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ (dashboard)/
│  │  ├─ dashboard/
│  │  │  └─ page.tsx
│  │  ├─ settings/
│  │  │  ├─ profile/
│  │  │  │  └─ page.tsx
│  │  │  ├─ billing/
│  │  │  │  └─ page.tsx
│  │  │  └─ team/
│  │  │     └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ api/
│  │  ├─ webhooks/
│  │  │  └─ stripe/
│  │  │     └─ route.ts
│  │  └─ og/                     # Open Graph 图片
│  │     └─ route.tsx
│  │
│  ├─ layout.tsx                  # 根布局
│  └─ globals.css
├─ components/
│  ├─ ui/                         # shadcn 组件
│  │  ├─ button.tsx
│  │  ├─ card.tsx
│  │  ├─ dialog.tsx
│  │  └─ ...
│  ├─ auth/
│  │  ├─ login-form.tsx
│  │  └─ signup-form.tsx
│  ├─ dashboard/
│  │  ├─ sidebar.tsx
│  │  └─ navbar.tsx
│  ├─ marketing/
│  │  ├─ hero.tsx
│  │  ├─ features.tsx
│  │  └─ pricing-cards.tsx
│  └─ shared/
│     ├─ user-avatar.tsx
│     └─ loading-spinner.tsx
├─ lib/
│  ├─ supabase/
│  │  ├─ client.ts              # 客户端Supabase
│  │  ├─ server.ts              # 服务端Supabase
│  │  └─ middleware.ts          # 认证中间件
│  ├─ stripe/
│  │  ├─ client.ts
│  │  └─ server.ts
│  ├─ db/
│  │  └─ queries.ts             # 数据库查询
│  ├─ auth.ts                   # 认证工具函数
│  └─ utils.ts                  # 通用工具函数
├─ hooks/
│  ├─ use-user.ts               # 用户会话钩子
│  ├─ use-subscription.ts       # 订阅状态钩子
│  └─ use-toast.ts
├─ actions/                      # Server Actions
│  ├─ auth/
│  │  ├─ login.ts
│  │  ├─ signup.ts
│  │  └─ logout.ts
│  ├─ billing/
│  │  ├─ create-checkout.ts
│  │  └─ cancel-subscription.ts
│  └─ profile/
│     └─ update-profile.ts
├─ types/
│  ├─ database.ts               # 从Supabase生成
│  ├─ supabase.ts
│  └─ stripe.ts
├─ supabase/
│  ├─ migrations/               # 数据库迁移文件
│  │  └─ 20250101000000_initial.sql
│  ├─ functions/                # Edge Functions(可选)
│  └─ config.toml
├─ public/
│  ├─ images/
│  └─ icons/
├─ middleware.ts                 # Next.js 中间件(认证)
├─ next.config.js
├─ tailwind.config.js
├─ tsconfig.json
├─ package.json
└─ .env.local

Database Schema Patterns

数据库架构模式

Core Tables for SaaS

SaaS核心表结构

sql
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Users table (extended from Supabase auth.users)
CREATE TABLE profiles (
  id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  email TEXT UNIQUE NOT NULL,
  full_name TEXT,
  avatar_url TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Organizations/Teams (multi-tenant)
CREATE TABLE organizations (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  logo_url TEXT,
  subscription_tier TEXT DEFAULT 'free',
  subscription_status TEXT DEFAULT 'active',
  stripe_customer_id TEXT UNIQUE,
  stripe_subscription_id TEXT UNIQUE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Organization memberships
CREATE TABLE organization_members (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
  role TEXT NOT NULL DEFAULT 'member', -- 'owner', 'admin', 'member'
  invited_by UUID REFERENCES profiles(id),
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  UNIQUE(organization_id, user_id)
);

-- Subscription plans
CREATE TABLE plans (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  description TEXT,
  price_monthly DECIMAL(10,2),
  price_yearly DECIMAL(10,2),
  stripe_price_id_monthly TEXT,
  stripe_price_id_yearly TEXT,
  features JSONB,
  limits JSONB, -- { "users": 5, "projects": 10 }
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Subscription usage tracking
CREATE TABLE usage_records (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  metric TEXT NOT NULL, -- 'api_calls', 'storage_mb', 'users'
  value INTEGER NOT NULL,
  period_start DATE NOT NULL,
  period_end DATE NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Invitations
CREATE TABLE invitations (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  email TEXT NOT NULL,
  role TEXT NOT NULL DEFAULT 'member',
  token TEXT UNIQUE NOT NULL,
  invited_by UUID REFERENCES profiles(id),
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
  accepted_at TIMESTAMP WITH TIME ZONE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Audit log (optional but recommended)
CREATE TABLE audit_logs (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id),
  user_id UUID REFERENCES profiles(id),
  action TEXT NOT NULL,
  resource_type TEXT,
  resource_id UUID,
  metadata JSONB,
  ip_address INET,
  user_agent TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Indexes
CREATE INDEX idx_org_members_org ON organization_members(organization_id);
CREATE INDEX idx_org_members_user ON organization_members(user_id);
CREATE INDEX idx_usage_org_period ON usage_records(organization_id, period_start, period_end);
CREATE INDEX idx_invitations_token ON invitations(token);
CREATE INDEX idx_audit_logs_org ON audit_logs(organization_id);
sql
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Users table (extended from Supabase auth.users)
CREATE TABLE profiles (
  id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  email TEXT UNIQUE NOT NULL,
  full_name TEXT,
  avatar_url TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Organizations/Teams (multi-tenant)
CREATE TABLE organizations (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  logo_url TEXT,
  subscription_tier TEXT DEFAULT 'free',
  subscription_status TEXT DEFAULT 'active',
  stripe_customer_id TEXT UNIQUE,
  stripe_subscription_id TEXT UNIQUE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Organization memberships
CREATE TABLE organization_members (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
  role TEXT NOT NULL DEFAULT 'member', -- 'owner', 'admin', 'member'
  invited_by UUID REFERENCES profiles(id),
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  UNIQUE(organization_id, user_id)
);

-- Subscription plans
CREATE TABLE plans (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  description TEXT,
  price_monthly DECIMAL(10,2),
  price_yearly DECIMAL(10,2),
  stripe_price_id_monthly TEXT,
  stripe_price_id_yearly TEXT,
  features JSONB,
  limits JSONB, -- { "users": 5, "projects": 10 }
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Subscription usage tracking
CREATE TABLE usage_records (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  metric TEXT NOT NULL, -- 'api_calls', 'storage_mb', 'users'
  value INTEGER NOT NULL,
  period_start DATE NOT NULL,
  period_end DATE NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Invitations
CREATE TABLE invitations (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  email TEXT NOT NULL,
  role TEXT NOT NULL DEFAULT 'member',
  token TEXT UNIQUE NOT NULL,
  invited_by UUID REFERENCES profiles(id),
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
  accepted_at TIMESTAMP WITH TIME ZONE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Audit log (optional but recommended)
CREATE TABLE audit_logs (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id),
  user_id UUID REFERENCES profiles(id),
  action TEXT NOT NULL,
  resource_type TEXT,
  resource_id UUID,
  metadata JSONB,
  ip_address INET,
  user_agent TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Indexes
CREATE INDEX idx_org_members_org ON organization_members(organization_id);
CREATE INDEX idx_org_members_user ON organization_members(user_id);
CREATE INDEX idx_usage_org_period ON usage_records(organization_id, period_start, period_end);
CREATE INDEX idx_invitations_token ON invitations(token);
CREATE INDEX idx_audit_logs_org ON audit_logs(organization_id);

Row Level Security (RLS) Policies

行级安全(RLS)策略

sql
-- Enable RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;

-- Profiles: Users can read and update their own profile
CREATE POLICY "Users can view own profile"
  ON profiles FOR SELECT
  USING (auth.uid() = id);

CREATE POLICY "Users can update own profile"
  ON profiles FOR UPDATE
  USING (auth.uid() = id);

-- Organizations: Members can read their organizations
CREATE POLICY "Members can view their organizations"
  ON organizations FOR SELECT
  USING (
    id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid()
    )
  );

-- Only owners can update organizations
CREATE POLICY "Owners can update organizations"
  ON organizations FOR UPDATE
  USING (
    id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid() AND role = 'owner'
    )
  );

-- Organization members: Members can view other members
CREATE POLICY "Members can view team members"
  ON organization_members FOR SELECT
  USING (
    organization_id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid()
    )
  );

sql
-- Enable RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;

-- Profiles: Users can read and update their own profile
CREATE POLICY "Users can view own profile"
  ON profiles FOR SELECT
  USING (auth.uid() = id);

CREATE POLICY "Users can update own profile"
  ON profiles FOR UPDATE
  USING (auth.uid() = id);

-- Organizations: Members can read their organizations
CREATE POLICY "Members can view their organizations"
  ON organizations FOR SELECT
  USING (
    id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid()
    )
  );

-- Only owners can update organizations
CREATE POLICY "Owners can update organizations"
  ON organizations FOR UPDATE
  USING (
    id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid() AND role = 'owner'
    )
  );

-- Organization members: Members can view other members
CREATE POLICY "Members can view team members"
  ON organization_members FOR SELECT
  USING (
    organization_id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid()
    )
  );

Authentication Setup

认证系统搭建

Supabase Auth Configuration

Supabase Auth 配置

typescript
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}

// lib/supabase/server.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { cookies } from 'next/headers';

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          cookieStore.set({ name, value, ...options });
        },
        remove(name: string, options: CookieOptions) {
          cookieStore.set({ name, value: '', ...options });
        },
      },
    }
  );
}
typescript
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}

// lib/supabase/server.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { cookies } from 'next/headers';

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          cookieStore.set({ name, value, ...options });
        },
        remove(name: string, options: CookieOptions) {
          cookieStore.set({ name, value: '', ...options });
        },
      },
    }
  );
}

Auth Middleware

认证中间件

typescript
// middleware.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          response.cookies.set({
            name,
            value,
            ...options,
          });
        },
        remove(name: string, options: CookieOptions) {
          response.cookies.set({
            name,
            value: '',
            ...options,
          });
        },
      },
    }
  );

  const { data: { user } } = await supabase.auth.getUser();

  // Redirect to login if not authenticated
  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // Redirect to dashboard if already authenticated
  if (user && (
    request.nextUrl.pathname === '/login' ||
    request.nextUrl.pathname === '/signup'
  )) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }

  return response;
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

typescript
// middleware.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          response.cookies.set({
            name,
            value,
            ...options,
          });
        },
        remove(name: string, options: CookieOptions) {
          response.cookies.set({
            name,
            value: '',
            ...options,
          });
        },
      },
    }
  );

  const { data: { user } } = await supabase.auth.getUser();

  // 未认证用户访问dashboard时重定向到登录页
  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 已认证用户访问登录/注册页时重定向到dashboard
  if (user && (
    request.nextUrl.pathname === '/login' ||
    request.nextUrl.pathname === '/signup'
  )) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }

  return response;
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

Stripe Integration

Stripe 集成

Setup

配置

typescript
// lib/stripe/server.ts
import Stripe from 'stripe';

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia',
});

// Create checkout session
export async function createCheckoutSession(
  organizationId: string,
  priceId: string,
  userId: string
) {
  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    payment_method_types: ['card'],
    line_items: [
      {
        price: priceId,
        quantity: 1,
      },
    ],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard/billing?success=true`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
    client_reference_id: organizationId,
    metadata: {
      organizationId,
      userId,
    },
  });

  return session;
}
typescript
// lib/stripe/server.ts
import Stripe from 'stripe';

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia',
});

// 创建结账会话
export async function createCheckoutSession(
  organizationId: string,
  priceId: string,
  userId: string
) {
  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    payment_method_types: ['card'],
    line_items: [
      {
        price: priceId,
        quantity: 1,
      },
    ],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard/billing?success=true`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
    client_reference_id: organizationId,
    metadata: {
      organizationId,
      userId,
    },
  });

  return session;
}

Webhook Handler

Webhook 处理器

typescript
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { stripe } from '@/lib/stripe/server';
import { createClient } from '@/lib/supabase/server';

export async function POST(req: Request) {
  const body = await req.text();
  const signature = (await headers()).get('stripe-signature')!;

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }

  const supabase = await createClient();

  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object as Stripe.Checkout.Session;
      
      await supabase
        .from('organizations')
        .update({
          stripe_customer_id: session.customer as string,
          stripe_subscription_id: session.subscription as string,
          subscription_status: 'active',
        })
        .eq('id', session.metadata?.organizationId);
      break;

    case 'customer.subscription.updated':
      const subscription = event.data.object as Stripe.Subscription;
      
      await supabase
        .from('organizations')
        .update({
          subscription_status: subscription.status,
        })
        .eq('stripe_subscription_id', subscription.id);
      break;

    case 'customer.subscription.deleted':
      const deletedSub = event.data.object as Stripe.Subscription;
      
      await supabase
        .from('organizations')
        .update({
          subscription_status: 'canceled',
          subscription_tier: 'free',
        })
        .eq('stripe_subscription_id', deletedSub.id);
      break;
  }

  return NextResponse.json({ received: true });
}

typescript
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { stripe } from '@/lib/stripe/server';
import { createClient } from '@/lib/supabase/server';

export async function POST(req: Request) {
  const body = await req.text();
  const signature = (await headers()).get('stripe-signature')!;

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return NextResponse.json({ error: '无效签名' }, { status: 400 });
  }

  const supabase = await createClient();

  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object as Stripe.Checkout.Session;
      
      await supabase
        .from('organizations')
        .update({
          stripe_customer_id: session.customer as string,
          stripe_subscription_id: session.subscription as string,
          subscription_status: 'active',
        })
        .eq('id', session.metadata?.organizationId);
      break;

    case 'customer.subscription.updated':
      const subscription = event.data.object as Stripe.Subscription;
      
      await supabase
        .from('organizations')
        .update({
          subscription_status: subscription.status,
        })
        .eq('stripe_subscription_id', subscription.id);
      break;

    case 'customer.subscription.deleted':
      const deletedSub = event.data.object as Stripe.Subscription;
      
      await supabase
        .from('organizations')
        .update({
          subscription_status: 'canceled',
          subscription_tier: 'free',
        })
        .eq('stripe_subscription_id', deletedSub.id);
      break;
  }

  return NextResponse.json({ received: true });
}

Multi-Tenancy Patterns

多租户模式

Organization-Based (Recommended)

基于组织的模式(推荐)

Pattern: Each organization has its own data space.
Benefits:
  • Clear data isolation
  • Easy to implement
  • Scalable per organization
  • Simple billing per organization
Implementation:
typescript
// All queries include organization_id
const { data } = await supabase
  .from('projects')
  .select('*')
  .eq('organization_id', currentOrgId);
模式: 每个组织拥有独立的数据空间。
优势:
  • 清晰的数据隔离
  • 易于实现
  • 按组织扩展
  • 按组织计费简单
实现方式:
typescript
// 所有查询均包含organization_id
const { data } = await supabase
  .from('projects')
  .select('*')
  .eq('organization_id', currentOrgId);

Subdomain-Based (Advanced)

基于子域名的模式(进阶)

Pattern: Each organization has its own subdomain (e.g.,
acme.yourapp.com
).
Benefits:
  • Better branding
  • Clear isolation
  • Professional appearance
Challenges:
  • DNS management
  • SSL certificates
  • More complex routing
Implementation:
typescript
// middleware.ts
export function middleware(request: NextRequest) {
  const hostname = request.headers.get('host')!;
  const subdomain = hostname.split('.')[0];
  
  // Fetch organization by subdomain
  // Set in request context
}

模式: 每个组织拥有独立子域名(例如:
acme.yourapp.com
)。
优势:
  • 更好的品牌展示
  • 清晰的隔离性
  • 专业的外观
挑战:
  • DNS管理
  • SSL证书配置
  • 更复杂的路由
实现方式:
typescript
// middleware.ts
export function middleware(request: NextRequest) {
  const hostname = request.headers.get('host')!;
  const subdomain = hostname.split('.')[0];
  
  // 通过子域名查询组织
  // 将结果存入请求上下文
}

MVP Feature Prioritization

MVP功能优先级

Week 1-2: Foundation

第1-2周:基础搭建

  • Project setup (Next.js + Supabase)
  • Database schema creation
  • Authentication (email + OAuth)
  • Basic layouts and routing
  • Landing page
  • 项目初始化(Next.js + Supabase)
  • 数据库架构创建
  • 认证系统(邮箱 + OAuth)
  • 基础布局与路由
  • 落地页开发

Week 3-4: Core Features

第3-4周:核心功能

  • Dashboard layout
  • First core feature (main value prop)
  • User profile management
  • Organization/team creation
  • Team member invitations
  • 仪表盘布局
  • 首个核心功能(核心价值主张)
  • 用户资料管理
  • 组织/团队创建
  • 团队成员邀请

Week 5-6: Monetization

第5-6周:商业化

  • Pricing page
  • Stripe integration
  • Subscription management
  • Usage limits enforcement
  • Billing page
  • 定价页开发
  • Stripe集成
  • 订阅管理功能
  • 使用限制校验
  • 账单页开发

Week 7-8: Polish

第7-8周:优化完善

  • Email notifications
  • Error handling
  • Loading states
  • Responsive design
  • SEO optimization
  • 邮件通知
  • 错误处理
  • 加载状态
  • 响应式设计
  • SEO优化

Week 9: Launch Prep

第9周:上线准备

  • Testing (E2E, unit)
  • Security audit
  • Performance optimization
  • Documentation
  • Deployment scripts

  • 测试(端到端、单元测试)
  • 安全审计
  • 性能优化
  • 文档编写
  • 部署脚本

Environment Variables

环境变量

bash
undefined
bash
undefined

.env.local

.env.local

Supabase

Supabase

NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key SUPABASE_SERVICE_ROLE_KEY=your-service-key
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key SUPABASE_SERVICE_ROLE_KEY=your-service-key

Stripe

Stripe

STRIPE_SECRET_KEY=sk_test_xxxxx NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx STRIPE_WEBHOOK_SECRET=whsec_xxxxx
STRIPE_SECRET_KEY=sk_test_xxxxx NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx STRIPE_WEBHOOK_SECRET=whsec_xxxxx

App

App

NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000

Optional

可选

SENTRY_DSN=your-sentry-dsn POSTHOG_KEY=your-posthog-key

---
SENTRY_DSN=your-sentry-dsn POSTHOG_KEY=your-posthog-key

---

Deployment Checklist

部署检查清单

Pre-Launch

上线前

  • Environment variables configured
  • Database migrations run
  • RLS policies tested
  • Stripe webhooks configured
  • Domain configured
  • SSL certificates active
  • Analytics setup
  • Error tracking (Sentry)
  • Email service configured
  • Terms of Service + Privacy Policy pages
  • 环境变量配置完成
  • 数据库迁移执行完毕
  • RLS策略测试通过
  • Stripe Webhook配置完成
  • 域名配置完成
  • SSL证书生效
  • 分析工具搭建完成
  • 错误追踪(Sentry)配置完成
  • 邮件服务配置完成
  • 服务条款与隐私政策页面完成

Post-Launch

上线后

  • Monitor error rates
  • Check subscription flows
  • Test webhook delivery
  • Monitor database performance
  • Set up backups
  • Create status page
  • Implement feature flags

  • 监控错误率
  • 检查订阅流程
  • 测试Webhook交付
  • 监控数据库性能
  • 配置备份策略
  • 创建状态页
  • 实现功能开关

Best Practices

最佳实践

Performance

性能

  • Use Server Components by default
  • Implement proper caching strategies
  • Optimize images with
    next/image
  • Use React Server Actions for mutations
  • Lazy load heavy components
  • 默认使用Server Components
  • 实现合理的缓存策略
  • 使用
    next/image
    优化图片
  • 采用React Server Actions处理数据变更
  • 懒加载重型组件

Security

安全

  • Always use RLS policies
  • Validate inputs on server
  • Never expose service role key to client
  • Use prepared statements (Supabase handles this)
  • Implement rate limiting for sensitive endpoints
  • 始终启用RLS策略
  • 在服务端校验输入
  • 绝不在客户端暴露服务端密钥
  • 使用预编译语句(Supabase已自动处理)
  • 对敏感接口实现速率限制

Development

开发

  • Use TypeScript strictly
  • Write tests for critical paths
  • Use database migrations (never manual changes)
  • Version your API
  • Document complex logic
  • 严格使用TypeScript
  • 为关键路径编写测试
  • 使用数据库迁移(禁止手动修改)
  • 为API添加版本号
  • 为复杂逻辑编写文档

User Experience

用户体验

  • Add loading states everywhere
  • Implement optimistic updates
  • Show clear error messages
  • Make onboarding smooth
  • Add helpful empty states

  • 所有操作添加加载状态
  • 实现乐观更新
  • 显示清晰的错误提示
  • 优化用户引导流程
  • 添加友好的空状态提示

Common Patterns Reference

常见模式参考

For detailed implementation patterns, see reference documents:
  • AUTHENTICATION_PATTERNS.md
    - OAuth, magic links, MFA
  • BILLING_PATTERNS.md
    - Stripe integration, webhooks, metering
  • MULTI_TENANT_PATTERNS.md
    - Organization structures, data isolation
  • DEPLOYMENT_STRATEGIES.md
    - CI/CD, environments, monitoring

如需详细的实现模式,请参考以下文档:
  • AUTHENTICATION_PATTERNS.md
    - OAuth、魔法链接、多因素认证
  • BILLING_PATTERNS.md
    - Stripe集成、Webhook、用量统计
  • MULTI_TENANT_PATTERNS.md
    - 组织结构、数据隔离
  • DEPLOYMENT_STRATEGIES.md
    - CI/CD、环境管理、监控

Example Output Format

示例输出格式

When planning a SaaS, provide:
  1. Executive Summary
    • Product overview
    • Target users
    • Key features
  2. Technical Architecture
    • System diagram
    • Tech stack rationale
    • Database schema
  3. File Structure
    • Complete folder organization
    • Key file descriptions
  4. Development Roadmap
    • Week-by-week breakdown
    • Feature prioritization
    • Estimated effort
  5. Deployment Plan
    • Infrastructure setup
    • Environment configuration
    • Launch checklist
  6. Next Steps
    • Immediate actions
    • Setup commands
    • Documentation links

规划SaaS时,请提供以下内容:
  1. 执行摘要
    • 产品概述
    • 目标用户
    • 核心功能
  2. 技术架构
    • 系统架构图
    • 技术栈选型理由
    • 数据库架构
  3. 文件结构
    • 完整的文件夹组织
    • 关键文件说明
  4. 开发路线图
    • 按周拆解的任务
    • 功能优先级
    • 预估工作量
  5. 部署方案
    • 基础设施搭建
    • 环境配置
    • 上线检查清单
  6. 下一步行动
    • 立即执行的任务
    • 初始化命令
    • 文档链接

Success Criteria

成功标准

A well-planned SaaS should have:
  • ✅ Clear database schema with RLS
  • ✅ Secure authentication flow
  • ✅ Scalable architecture
  • ✅ Working billing integration
  • ✅ Organized file structure
  • ✅ Realistic timeline
  • ✅ Clear MVP scope
  • ✅ Deployment strategy
一个规划完善的SaaS应具备:
  • ✅ 带RLS的清晰数据库架构
  • ✅ 安全的认证流程
  • ✅ 可扩展的架构
  • ✅ 可用的账单集成
  • ✅ 规整的文件结构
  • ✅ 真实可行的时间线
  • ✅ 清晰的MVP范围
  • ✅ 明确的部署策略