Guide · 2026-03-04

Build and Deploy a SaaS with Cursor — Zero to Live in 2 Hours

Step-by-step tutorial: build a complete SaaS app with Cursor, Next.js, Supabase, Stripe, and Vercel. From empty folder to paying customers. Every command included.

What You'll Build

    A complete SaaS application with:
  • User authentication (email + Google OAuth)
  • Subscription billing (Stripe with monthly/annual plans)
  • Protected dashboard
  • Settings page with billing management
  • Production deployment on Vercel

Stack: Cursor + Next.js 14 + Supabase + Stripe + Vercel Time: ~2 hours (faster if you've done this before) Cost: $0 (everything has free tiers)

Prerequisites

  • Cursor installed
  • Node.js 18+ installed
  • A GitHub account
  • A Supabase account (free)
  • A Stripe account (free)
  • Step 1: Create Your Project (5 minutes)

    Open your terminal and create a new Next.js project:

    npx create-next-app@latest my-saas --typescript --tailwind --app --src-dir
    cd my-saas

    Install the dependencies you'll need:

    npm install @supabase/supabase-js @supabase/ssr stripe @stripe/stripe-js

    Open the project in Cursor:

    cursor .

    Step 2: Set Up Supabase (10 minutes)

    Go to supabase.com and create a new project. Wait for it to finish provisioning (takes about 1 minute).

      Go to Settings → API and copy your:
    • Project URL
    • Anon public key

    Create a .env.local file in your project root:

    NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
    NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

    Now open Cursor's chat and paste this prompt:

    Create a Supabase client utility at src/lib/supabase.ts that:
    1. Exports a browser client using createBrowserClient
    2. Exports a server client function using createServerClient for use in server components
    3. Uses the env vars NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY
    
    Use @supabase/ssr for both. Follow the latest Supabase docs for Next.js App Router.

    Cursor will generate the client files. Review them and accept.

    Step 3: Add Authentication (20 minutes)

    In Cursor chat, paste this prompt:

    @src/lib/supabase.ts
    
    Build a complete auth system:
    
    1. Create src/app/login/page.tsx:
       - Email + password login form
       - "Continue with Google" OAuth button
       - Link to signup page
       - Error handling for wrong password, user not found
       - Redirect to /dashboard on success
    
    2. Create src/app/signup/page.tsx:
       - Email + password signup form
       - Password confirmation field
       - Redirect to /login with "check your email" message
    
    3. Create src/middleware.ts:
       - Protect /dashboard/* routes
       - Redirect unauthenticated users to /login
       - Allow /login, /signup, /pricing, and / without auth
    
    4. Create src/app/auth/callback/route.ts:
       - Handle OAuth callback from Supabase
    
    Use Tailwind CSS for styling. Clean, minimal design.

    After Cursor generates the code, enable Google OAuth in Supabase:

  • Go to Supabase Dashboard → Authentication → Providers → Google
  • Toggle Google on
  • Add your Google OAuth credentials (from Google Cloud Console)
  • Add https://your-project.supabase.co/auth/v1/callback as authorized redirect URI in Google
  • Test it:

    npm run dev

    Open http://localhost:3000/login and try signing up with email.

    Step 4: Build the Dashboard (15 minutes)

    @src/lib/supabase.ts @src/middleware.ts
    
    Create a user dashboard at src/app/dashboard/page.tsx:
    
    1. Server component that checks auth
    2. Shows user's email and name
    3. Shows subscription status (free/pro — default to free for now)
    4. "Upgrade to Pro" button that links to /pricing
    5. Navigation: Dashboard, Settings, Logout
    6. Sidebar layout on desktop, bottom nav on mobile
    
    Also create src/app/dashboard/settings/page.tsx:
    1. User profile form (name, avatar)
    2. "Manage Billing" button (will link to Stripe portal later)
    3. "Sign Out" button that calls supabase.auth.signOut()
    
    Clean design with Tailwind. Match Apple's design aesthetic.

    Step 5: Set Up Stripe Billing (30 minutes)

      Go to Stripe Dashboard and:
    • Create a product called "Pro Plan"
    • Add two prices: $29/month and $290/year (save 17%)
    • Copy the price IDs (price_xxx...)
    • Get your API keys from Developers → API keys

    Add to .env.local:

    STRIPE_SECRET_KEY=sk_test_...
    NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
    STRIPE_WEBHOOK_SECRET=whsec_... (we'll get this in step 7)
    STRIPE_MONTHLY_PRICE_ID=price_...
    STRIPE_YEARLY_PRICE_ID=price_...

    Now prompt Cursor:

    @src/lib/supabase.ts
    
    Build complete Stripe integration:
    
    1. Create src/app/pricing/page.tsx:
       - Two pricing cards: Free and Pro ($29/mo or $290/yr)
       - Monthly/annual toggle
       - "Get Started Free" button for free tier (goes to /signup)
       - "Start Pro Trial" button for pro (creates Stripe checkout)
       - Feature comparison list
    
    2. Create src/app/api/checkout/route.ts:
       - POST endpoint that creates a Stripe Checkout session
       - Uses STRIPE_MONTHLY_PRICE_ID or STRIPE_YEARLY_PRICE_ID based on interval
       - success_url: /dashboard?upgraded=true
       - cancel_url: /pricing
    
    3. Create src/app/api/webhooks/stripe/route.ts:
       - Handles checkout.session.completed → save subscription to Supabase
       - Handles customer.subscription.deleted → remove subscription
       - Handles invoice.payment_failed → mark as past_due
       - Verify webhook signature with STRIPE_WEBHOOK_SECRET
       - IMPORTANT: Use req.text() not req.json() for signature verification
    
    4. Create a subscriptions table in Supabase:
       - id, user_id, stripe_customer_id, stripe_subscription_id, status, plan, current_period_end
       - RLS: users can only read their own subscription
    
    5. Create src/lib/subscription.ts:
       - getSubscription(userId) function
       - isProUser(userId) function
    
    Use Stripe SDK. Environment variables for all keys.

    Step 6: Create the Database Table

    Go to Supabase Dashboard → SQL Editor and run:

    sql
    CREATE TABLE subscriptions (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      user_id uuid REFERENCES auth.users(id) NOT NULL,
      stripe_customer_id text,
      stripe_subscription_id text,
      status text DEFAULT 'free',
      plan text DEFAULT 'free',
      current_period_end timestamptz,
      created_at timestamptz DEFAULT now()
    );
    
    ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY;
    
    CREATE POLICY "Users can read own subscription"
    ON subscriptions FOR SELECT
    USING (auth.uid() = user_id);

    Step 7: Test Payments Locally

    Install the Stripe CLI:

    brew install stripe/stripe-cli/stripe
    stripe login

    Forward webhooks to your local server:

    stripe listen --forward-to localhost:3000/api/webhooks/stripe

    Copy the webhook signing secret (whsec_...) and add it to .env.local as STRIPE_WEBHOOK_SECRET.

      Test a payment flow:
    • Go to http://localhost:3000/pricing
    • Click "Start Pro Trial"
    • Use test card: 4242 4242 4242 4242
    • Check your dashboard — it should show "Pro" status

    Step 8: Deploy to Vercel (10 minutes)

    Push to GitHub:

    git init
    git add -A
    git commit -m "initial SaaS"
    gh repo create my-saas --public --push
      Go to vercel.com:
    • Click "Import Project"
    • Select your GitHub repo
    • Add ALL environment variables from .env.local
    • Click Deploy
      After deployment:
    • Copy your Vercel URL (my-saas.vercel.app)
    • Go to Stripe Dashboard → Webhooks → Add endpoint
    • Enter: https://my-saas.vercel.app/api/webhooks/stripe
    • Select events: checkout.session.completed, customer.subscription.deleted, invoice.payment_failed
    • Copy the new webhook signing secret and update STRIPE_WEBHOOK_SECRET in Vercel

    Step 9: Connect Your Domain (5 minutes)

      In Vercel → Project Settings → Domains:
    • Add your custom domain
    • Update DNS records at your registrar:
    • - A record: @76.76.21.21 - CNAME: wwwcname.vercel-dns.com

    SSL certificate is automatic. Your SaaS is live.

    What You Just Built

  • User registration with email and Google OAuth
  • Protected dashboard that requires login
  • Stripe subscription billing with monthly/annual plans
  • Webhook handler that syncs payment status
  • Settings page with billing management
  • Deployed to production with custom domain
  • Total cost: $0/month until you have paying customers
  • Next Steps

  • Add your actual product features to the dashboard
  • Set up welcome emails with Resend
  • Add more OAuth providers (GitHub, Apple)
  • Set up error monitoring with Sentry
  • Read our security guide before going live
  • Common Issues

    🎬 Related videos

    External videos — opens YouTube in a new tab

    Recommended Stack

    Services we recommend for deploying your vibe coded app