nextjs-supabase-saas-planner
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNext.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:
-
Product Overview
- What problem does your SaaS solve?
- Who is your target user?
- What's your unique value proposition?
-
Scale Expectations
- How many users in first year? (0-1K, 1K-10K, 10K-100K, 100K+)
- Expected growth rate?
- Geographic distribution?
-
Team and Timeline
- Team size and skills?
- Launch timeline? (MVP date)
- Budget constraints?
-
Business Model
- Pricing tiers?
- Free trial or freemium?
- What features differentiate paid tiers?
-
Core Features
- Must-have features for MVP?
- Nice-to-have features for v1.1?
- Future roadmap ideas?
首先询问以下问题:
-
产品概述
- 你的SaaS解决什么问题?
- 目标用户是谁?
- 核心价值主张是什么?
-
规模预期
- 第一年预计用户量?(0-1K、1K-10K、10K-100K、100K+)
- 预期增长率?
- 用户地域分布?
-
团队与时间线
- 团队规模与技能配置?
- 上线时间线?(MVP发布日期)
- 预算限制?
-
商业模式
- 定价层级?
- 是否提供免费试用或免费增值模式?
- 付费层级的差异化功能有哪些?
-
核心功能
- MVP必备功能?
- v1.1版本的可选功能?
- 未来路线图创意?
Phase 2: Technical Architecture Planning
第二阶段:技术架构规划
Based on requirements, create:
- System Architecture Diagram
- Database Schema
- File/Folder Structure
- Authentication Flow
- Multi-Tenancy Strategy (if applicable)
- Billing Integration Plan
- Deployment Architecture
基于需求,创建以下内容:
- 系统架构图
- 数据库架构
- 文件/文件夹结构
- 认证流程
- 多租户策略(如适用)
- 账单集成方案
- 部署架构
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.localmy-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.localDatabase 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.comBenefits:
- 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
undefinedbash
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:
- - OAuth, magic links, MFA
AUTHENTICATION_PATTERNS.md - - Stripe integration, webhooks, metering
BILLING_PATTERNS.md - - Organization structures, data isolation
MULTI_TENANT_PATTERNS.md - - CI/CD, environments, monitoring
DEPLOYMENT_STRATEGIES.md
如需详细的实现模式,请参考以下文档:
- - OAuth、魔法链接、多因素认证
AUTHENTICATION_PATTERNS.md - - Stripe集成、Webhook、用量统计
BILLING_PATTERNS.md - - 组织结构、数据隔离
MULTI_TENANT_PATTERNS.md - - CI/CD、环境管理、监控
DEPLOYMENT_STRATEGIES.md
Example Output Format
示例输出格式
When planning a SaaS, provide:
-
Executive Summary
- Product overview
- Target users
- Key features
-
Technical Architecture
- System diagram
- Tech stack rationale
- Database schema
-
File Structure
- Complete folder organization
- Key file descriptions
-
Development Roadmap
- Week-by-week breakdown
- Feature prioritization
- Estimated effort
-
Deployment Plan
- Infrastructure setup
- Environment configuration
- Launch checklist
-
Next Steps
- Immediate actions
- Setup commands
- Documentation links
规划SaaS时,请提供以下内容:
-
执行摘要
- 产品概述
- 目标用户
- 核心功能
-
技术架构
- 系统架构图
- 技术栈选型理由
- 数据库架构
-
文件结构
- 完整的文件夹组织
- 关键文件说明
-
开发路线图
- 按周拆解的任务
- 功能优先级
- 预估工作量
-
部署方案
- 基础设施搭建
- 环境配置
- 上线检查清单
-
下一步行动
- 立即执行的任务
- 初始化命令
- 文档链接
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范围
- ✅ 明确的部署策略