ScaleRocket/Web

Deployment

The complete guide to deploying ScaleRocket to production — Vercel, Supabase, Stripe, Auth, Email, DNS, and a full checklist.

Deployment

ScaleRocket runs as three frontend apps on Vercel with a backend handling all server-side logic.

Architecture overview:

AppLocalProductionStack
apps/weblocalhost:3000yourdomain.comNext.js (marketing site)
apps/applocalhost:5173app.yourdomain.comVite + React (user dashboard)
apps/opslocalhost:5174admin.yourdomain.comVite + React (admin panel)

Backend: Supabase Edge Functions handle Stripe webhooks, checkout, portal, credit usage, and account deletion. Stripe webhooks point to Supabase, not to any Vercel-hosted app.

Backend: Convex functions handle Stripe webhooks, checkout, portal, credit usage, and account deletion. Stripe webhooks point to Convex HTTP endpoints, not to any Vercel-hosted app.


1. Deploy to Vercel (3 Projects)

You need three separate Vercel projects, all connected to the same Git repository. Each project has its own root directory and environment variables.

1a. Marketing Site (apps/web)

  1. Go to vercel.com/new
  2. Import your Git repository
  3. Set Root Directory to apps/web
  4. Set Framework Preset to Next.js
  5. Set Build Command to cd ../.. && pnpm turbo build --filter=web
  6. Set Install Command to cd ../.. && pnpm install
  7. Add environment variables (see table below)
  8. Click Deploy

Environment variables for apps/web:

VariableValueRequired
NEXT_PUBLIC_WEB_URLhttps://yourdomain.comYes
NEXT_PUBLIC_APP_URLhttps://app.yourdomain.comYes
NEXT_PUBLIC_PLAUSIBLE_DOMAINyourdomain.comOptional
NEXT_PUBLIC_GA_IDG-XXXXXXXXXXOptional
NEXT_PUBLIC_POSTHOG_KEYphc_...Optional

Note: The marketing site does NOT need Supabase or Stripe keys. It only needs to know the URLs so it can link to the dashboard.

1b. Dashboard (apps/app)

  1. Create another Vercel project from the same repository
  2. Set Root Directory to apps/app
  3. Set Framework Preset to Vite
  4. Set Build Command to cd ../.. && pnpm turbo build --filter=app
  5. Set Install Command to cd ../.. && pnpm install
  6. Add environment variables (see table below)
  7. Click Deploy

Environment variables for apps/app:

VariableValueRequired
VITE_WEB_URLhttps://yourdomain.comYes
VITE_APP_URLhttps://app.yourdomain.comYes
VITE_SUPABASE_URLhttps://YOUR_PROJECT_REF.supabase.coYes
VITE_SUPABASE_ANON_KEYeyJ... (from Supabase dashboard > Settings > API)Yes
VITE_STRIPE_PUBLISHABLE_KEYpk_live_... (from Stripe dashboard, LIVE mode)Yes
VariableValueRequired
VITE_WEB_URLhttps://yourdomain.comYes
VITE_APP_URLhttps://app.yourdomain.comYes
VITE_CONVEX_URLhttps://YOUR_DEPLOYMENT.convex.cloudYes
VITE_STRIPE_PUBLISHABLE_KEYpk_live_... (from Stripe dashboard, LIVE mode)Yes

1c. Admin Panel (apps/ops)

  1. Create a third Vercel project from the same repository
  2. Set Root Directory to apps/ops
  3. Set Framework Preset to Vite
  4. Set Build Command to cd ../.. && pnpm turbo build --filter=ops
  5. Set Install Command to cd ../.. && pnpm install
  6. Add environment variables (see table below)
  7. Click Deploy

Environment variables for apps/ops:

VariableValueRequired
VITE_SUPABASE_URLhttps://YOUR_PROJECT_REF.supabase.coYes
VITE_SUPABASE_ANON_KEYeyJ... (same anon key as apps/app)Yes
VariableValueRequired
VITE_CONVEX_URLhttps://YOUR_DEPLOYMENT.convex.cloudYes

Tip: All three projects share the same Git repo. Vercel detects changes per root directory and only rebuilds the affected app.


2. Deploy Backend

Push Database Migrations

Run this from your project root to apply all migrations to your production Supabase database:

npx supabase db push

If you haven't linked your project yet:

npx supabase link --project-ref YOUR_PROJECT_REF

Deploy Edge Functions

Deploy all Edge Functions to production:

npx supabase functions deploy

This deploys every function in supabase/functions/ (stripe-webhook, stripe-checkout, stripe-portal, use-credits, delete-account, and any others).

To deploy a single function:

npx supabase functions deploy stripe-webhook

Set Production Secrets

Edge Functions need these secrets to operate. Set them all:

npx supabase secrets set STRIPE_SECRET_KEY=sk_live_...
npx supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...
npx supabase secrets set RESEND_API_KEY=re_...
npx supabase secrets set RESEND_FROM_EMAIL=hello@yourdomain.com
npx supabase secrets set APP_URL=https://app.yourdomain.com
npx supabase secrets set ADMIN_EMAILS=admin@yourdomain.com

Verify your secrets are set:

npx supabase secrets list

Important: APP_URL is used by Edge Functions for CORS and redirect logic. It must match your production dashboard URL exactly — https://app.yourdomain.com with no trailing slash.

Deploy Convex

Deploy your Convex backend to production:

npx convex deploy

This deploys the schema, all functions, and HTTP endpoints in a single command.

Set Production Environment Variables

Convex functions need these environment variables to operate. Set them all:

npx convex env set STRIPE_SECRET_KEY sk_live_...
npx convex env set STRIPE_WEBHOOK_SECRET whsec_...
npx convex env set RESEND_API_KEY re_...
npx convex env set RESEND_FROM_EMAIL hello@yourdomain.com
npx convex env set APP_URL https://app.yourdomain.com
npx convex env set ADMIN_EMAILS admin@yourdomain.com

Verify your environment variables are set:

npx convex env list

Important: APP_URL is used for redirect logic. It must match your production dashboard URL exactly — https://app.yourdomain.com with no trailing slash.


3. Configure Stripe for Production

Stripe test mode and live mode are completely separate. Test mode products, prices, and webhooks do not carry over to live mode.

Step-by-step:

  1. Switch to LIVE mode in the Stripe dashboard (toggle in the top-left corner)

  2. Create your products and prices in live mode. These must be created fresh — they cannot be copied from test mode.

  3. Copy the live Price IDs (e.g., price_1Abc...) and update them in:

    packages/config/src/pricing.ts

    Replace every test price ID (price_1Test...) with the corresponding live price ID.

  4. Create a production webhook endpoint:

    • Go to Developers > Webhooks in the Stripe dashboard (make sure you are in LIVE mode)
    • Click Add endpoint
    • Set the Endpoint URL to:
https://YOUR_PROJECT_REF.supabase.co/functions/v1/stripe-webhook
https://YOUR_DEPLOYMENT.convex.site/stripe-webhook
  • Select these events:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed
  • Click Add endpoint
  1. Copy the Signing secret (starts with whsec_) and set it:
npx supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...
npx convex env set STRIPE_WEBHOOK_SECRET whsec_...
  1. Copy the Live Publishable Key (pk_live_...) and make sure it is set as VITE_STRIPE_PUBLISHABLE_KEY in the apps/app Vercel project.

Critical: The webhook URL points to your backend function, not to a Vercel/Next.js API route. This is the most common mistake.


4. Configure Auth for Production

This step is critical and frequently forgotten. Without it, login and OAuth will break in production.

Update URL Configuration

  1. Go to your Supabase dashboard > Authentication > URL Configuration

  2. Set Site URL to:

    https://app.yourdomain.com
  3. Add these Redirect URLs:

    https://app.yourdomain.com/**
    https://admin.yourdomain.com/**

    The /** wildcard ensures all sub-paths are allowed (e.g., /auth/callback, /dashboard, etc.).

  1. Set the SITE_URL environment variable in Convex:

    npx convex env set SITE_URL https://app.yourdomain.com
  2. Update convex/auth.config.ts with your production redirect URLs.

Update OAuth Provider Callbacks

OAuth providers (Google, GitHub, etc.) redirect to your auth backend first, then to your app.

Google OAuth:

  1. Go to Google Cloud Console > APIs & Services > Credentials
  2. Edit your OAuth 2.0 Client
  3. Under Authorized redirect URIs, add:
    https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback
  4. Remove any localhost redirect URIs (or keep them for local dev)

GitHub OAuth:

  1. Go to GitHub Developer Settings > OAuth Apps
  2. Edit your OAuth App
  3. Set Authorization callback URL to:
    https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback

How it works: User clicks "Sign in with Google" -> redirected to Google -> Google redirects to https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback -> Supabase creates the session -> Supabase redirects to your Site URL (https://app.yourdomain.com).

Google OAuth:

  1. Go to Google Cloud Console > APIs & Services > Credentials
  2. Edit your OAuth 2.0 Client
  3. Under Authorized redirect URIs, add:
    https://YOUR_DEPLOYMENT.convex.site/api/auth/callback/google

GitHub OAuth:

  1. Go to GitHub Developer Settings > OAuth Apps
  2. Edit your OAuth App
  3. Set Authorization callback URL to:
    https://YOUR_DEPLOYMENT.convex.site/api/auth/callback/github

How it works: User clicks "Sign in with Google" -> redirected to Google -> Google redirects to your Convex HTTP endpoint -> Convex creates the session -> redirects to your app.


5. Configure Email (Resend)

Server-side functions use Resend to send transactional emails.

  1. Verify your domain in the Resend dashboard:

    • Add yourdomain.com
    • Add the DNS records Resend provides (SPF, DKIM, DMARC)
    • Wait for verification to complete
  2. Update the from email in your backend secrets:

npx supabase secrets set RESEND_FROM_EMAIL=hello@yourdomain.com
npx convex env set RESEND_FROM_EMAIL hello@yourdomain.com

The from address must use your verified domain. Emails from unverified domains will be rejected.

  1. Test sending by triggering a flow in production (e.g., sign up a test user, make a test purchase) and confirming the email arrives.

6. Configure CORS

The Edge Functions use the APP_URL secret to restrict CORS (Cross-Origin Resource Sharing). Only requests from your production dashboard will be accepted.

Make sure you have set:

npx supabase secrets set APP_URL=https://app.yourdomain.com

If CORS errors appear in the browser console after deploying, double-check:

  • The APP_URL secret is set correctly (no trailing slash)
  • The Edge Functions have been redeployed after setting the secret
  • The request is coming from https://app.yourdomain.com, not a Vercel preview URL

Convex handles CORS automatically for all function calls via the Convex client. No manual CORS configuration is needed.

For HTTP endpoints (webhooks), CORS is handled in convex/http.ts.


7. Custom Domains

Vercel Domains

For each of the three Vercel projects:

  1. Go to Settings > Domains in the Vercel dashboard
  2. Add your domain:
Vercel ProjectDomain(s)
apps/webyourdomain.com and www.yourdomain.com
apps/appapp.yourdomain.com
apps/opsadmin.yourdomain.com
  1. Vercel will provide DNS records to add. The exact records depend on your DNS provider.

DNS Configuration

Add these records at your domain registrar or DNS provider:

TypeNameValueProject
A@76.76.21.21apps/web
CNAMEwwwcname.vercel-dns.comapps/web
CNAMEappcname.vercel-dns.comapps/app
CNAMEadmincname.vercel-dns.comapps/ops

Note: The exact A record IP may vary. Always use the values Vercel shows you in the dashboard. SSL certificates are provisioned automatically by Vercel once DNS propagates.


8. Production Checklist

Go through every item before announcing your launch.

Environment Variables

  • apps/web on Vercel has NEXT_PUBLIC_WEB_URL and NEXT_PUBLIC_APP_URL set
  • apps/app on Vercel has VITE_WEB_URL, VITE_APP_URL, VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY, and VITE_STRIPE_PUBLISHABLE_KEY set
  • apps/ops on Vercel has VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY set
  • Supabase secrets are set: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, RESEND_API_KEY, RESEND_FROM_EMAIL, APP_URL, ADMIN_EMAILS
  • apps/web on Vercel has NEXT_PUBLIC_WEB_URL and NEXT_PUBLIC_APP_URL set
  • apps/app on Vercel has VITE_WEB_URL, VITE_APP_URL, VITE_CONVEX_URL, and VITE_STRIPE_PUBLISHABLE_KEY set
  • apps/ops on Vercel has VITE_CONVEX_URL set
  • Convex env vars are set: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, RESEND_API_KEY, RESEND_FROM_EMAIL, APP_URL, ADMIN_EMAILS

Stripe

  • Products and prices created in LIVE mode (not test mode)
  • Price IDs in packages/config/src/pricing.ts are updated to live price IDs
  • Webhook endpoint URL is https://YOUR_PROJECT_REF.supabase.co/functions/v1/stripe-webhook
  • Webhook signing secret (whsec_...) is set as STRIPE_WEBHOOK_SECRET in Supabase secrets
  • Stripe publishable key in apps/app is the LIVE key (pk_live_..., not pk_test_...)
  • Products and prices created in LIVE mode (not test mode)
  • Price IDs in packages/config/src/pricing.ts are updated to live price IDs
  • Webhook endpoint URL is https://YOUR_DEPLOYMENT.convex.site/stripe-webhook
  • Webhook signing secret (whsec_...) is set as STRIPE_WEBHOOK_SECRET in Convex env vars
  • Stripe publishable key in apps/app is the LIVE key (pk_live_..., not pk_test_...)

Auth

  • Site URL is set to https://app.yourdomain.com
  • Redirect URLs include https://app.yourdomain.com/** and https://admin.yourdomain.com/**
  • Google OAuth Authorized Redirect URI is https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback
  • GitHub OAuth Authorization callback URL is https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback
  • SITE_URL env var is set to https://app.yourdomain.com
  • Google OAuth Authorized Redirect URI is https://YOUR_DEPLOYMENT.convex.site/api/auth/callback/google
  • GitHub OAuth Authorization callback URL is https://YOUR_DEPLOYMENT.convex.site/api/auth/callback/github

Email

  • Domain verified in Resend (SPF, DKIM, DMARC records added)
  • RESEND_FROM_EMAIL uses your verified domain (e.g., hello@yourdomain.com)
  • Test email sent and received successfully in production

Database

  • Migrations pushed to production (npx supabase db push)
  • RLS (Row Level Security) enabled on all tables
  • Edge Functions deployed (npx supabase functions deploy)
  • Convex deployed to production (npx convex deploy)
  • Schema and functions are up to date

Security

  • ADMIN_EMAILS secret contains the correct admin email addresses
  • CORS is restricted to production APP_URL only
  • apps/ops (admin panel) is not publicly accessible, or has proper admin email verification

DNS

  • yourdomain.com points to apps/web on Vercel
  • app.yourdomain.com points to apps/app on Vercel
  • admin.yourdomain.com points to apps/ops on Vercel
  • SSL certificates are active on all domains (automatic with Vercel once DNS propagates)

Troubleshooting

"Invalid redirect" after OAuth login

Your auth Site URL or Redirect URLs are wrong. Verify they match your production domains exactly.

Stripe webhook returns 401 or 400

  • Verify the webhook URL points to Supabase (https://YOUR_PROJECT_REF.supabase.co/functions/v1/stripe-webhook), not to a Vercel URL.
  • Verify the STRIPE_WEBHOOK_SECRET Supabase secret matches the signing secret shown in the Stripe dashboard for your production webhook endpoint.
  • Make sure you created the webhook in LIVE mode, not test mode.
  • Verify the webhook URL points to Convex (https://YOUR_DEPLOYMENT.convex.site/stripe-webhook), not to a Vercel URL.
  • Verify the STRIPE_WEBHOOK_SECRET Convex env var matches the signing secret shown in the Stripe dashboard for your production webhook endpoint.
  • Make sure you created the webhook in LIVE mode, not test mode.

CORS errors in the browser console

  • Check that the APP_URL Supabase secret matches your production dashboard URL exactly (https://app.yourdomain.com, no trailing slash).
  • Redeploy Edge Functions after changing secrets: npx supabase functions deploy.
  • Convex handles CORS automatically for client function calls. If you see CORS errors on HTTP endpoints, check your convex/http.ts configuration.

Emails not sending

  • Confirm your domain is verified in Resend.
  • Confirm RESEND_FROM_EMAIL uses the verified domain.
  • Check the Resend dashboard for delivery logs and errors.

Payments succeed but subscription not created

  • The Stripe webhook is not reaching your backend function. Check the webhook logs in the Stripe dashboard under Developers > Webhooks > [your endpoint] > Attempts.
  • Make sure all six required events are selected on the webhook endpoint.

Done reading? Mark this page as complete.

On this page