--- name: nextjs-supabase-saas-planner description: Comprehensive SaaS architecture planner for Next.js and Supabase applications. Converts ideas into production-ready technical plans with database schemas, file structures, feature breakdowns, and week-by-week development roadmaps. Use when planning subscription-based applications, multi-tenant SaaS products, or building from idea to launch with Next.js and Supabase stack. license: Apache-2.0 --- # Next.js + Supabase SaaS Planner ## 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 --- ## 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? ### 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** ### 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 --- ## Next.js 15 + Supabase Stack (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) ``` ### 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 --- ## 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 ``` --- ## Database Schema Patterns ### Core Tables for 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); ``` ### Row Level Security (RLS) Policies ```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 ```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)$).*)', ], }; ``` --- ## Stripe Integration ### 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; } ``` ### Webhook Handler ```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 }); } ``` --- ## 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); ``` ### 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 } ``` --- ## MVP Feature Prioritization ### Week 1-2: Foundation - [ ] Project setup (Next.js + Supabase) - [ ] Database schema creation - [ ] Authentication (email + OAuth) - [ ] Basic layouts and routing - [ ] Landing page ### Week 3-4: Core Features - [ ] Dashboard layout - [ ] First core feature (main value prop) - [ ] User profile management - [ ] Organization/team creation - [ ] Team member invitations ### Week 5-6: Monetization - [ ] Pricing page - [ ] Stripe integration - [ ] Subscription management - [ ] Usage limits enforcement - [ ] Billing page ### Week 7-8: Polish - [ ] Email notifications - [ ] Error handling - [ ] Loading states - [ ] Responsive design - [ ] SEO optimization ### Week 9: Launch Prep - [ ] Testing (E2E, unit) - [ ] Security audit - [ ] Performance optimization - [ ] Documentation - [ ] Deployment scripts --- ## Environment Variables ```bash # .env.local # Supabase 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_SECRET_KEY=sk_test_xxxxx NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx STRIPE_WEBHOOK_SECRET=whsec_xxxxx # App NEXT_PUBLIC_APP_URL=http://localhost:3000 # Optional 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 ### Post-Launch - [ ] Monitor error rates - [ ] Check subscription flows - [ ] Test webhook delivery - [ ] Monitor database performance - [ ] Set up backups - [ ] Create status page - [ ] Implement feature flags --- ## 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 ### 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 ### Development - Use TypeScript strictly - Write tests for critical paths - Use database migrations (never manual changes) - Version your API - Document complex logic ### 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 --- ## 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 --- ## 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