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
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:
https://your-project.supabase.co/auth/v1/callback as authorized redirect URI in GoogleTest 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:
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_SECRETin 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: www → cname.vercel-dns.comSSL certificate is automatic. Your SaaS is live.
What You Just Built
Next Steps
Common Issues
- If something breaks, check these first:
- Stripe webhook failing
- Supabase RLS blocking queries
- Vercel env vars undefined
- Vercel build failed

