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:
| App | Local | Production | Stack |
|---|---|---|---|
apps/web | localhost:3000 | yourdomain.com | Next.js (marketing site) |
apps/app | localhost:5173 | app.yourdomain.com | Vite + React (user dashboard) |
apps/ops | localhost:5174 | admin.yourdomain.com | Vite + 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)
- Go to vercel.com/new
- Import your Git repository
- Set Root Directory to
apps/web - Set Framework Preset to Next.js
- Set Build Command to
cd ../.. && pnpm turbo build --filter=web - Set Install Command to
cd ../.. && pnpm install - Add environment variables (see table below)
- Click Deploy
Environment variables for apps/web:
| Variable | Value | Required |
|---|---|---|
NEXT_PUBLIC_WEB_URL | https://yourdomain.com | Yes |
NEXT_PUBLIC_APP_URL | https://app.yourdomain.com | Yes |
NEXT_PUBLIC_PLAUSIBLE_DOMAIN | yourdomain.com | Optional |
NEXT_PUBLIC_GA_ID | G-XXXXXXXXXX | Optional |
NEXT_PUBLIC_POSTHOG_KEY | phc_... | 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)
- Create another Vercel project from the same repository
- Set Root Directory to
apps/app - Set Framework Preset to Vite
- Set Build Command to
cd ../.. && pnpm turbo build --filter=app - Set Install Command to
cd ../.. && pnpm install - Add environment variables (see table below)
- Click Deploy
Environment variables for apps/app:
| Variable | Value | Required |
|---|---|---|
VITE_WEB_URL | https://yourdomain.com | Yes |
VITE_APP_URL | https://app.yourdomain.com | Yes |
VITE_SUPABASE_URL | https://YOUR_PROJECT_REF.supabase.co | Yes |
VITE_SUPABASE_ANON_KEY | eyJ... (from Supabase dashboard > Settings > API) | Yes |
VITE_STRIPE_PUBLISHABLE_KEY | pk_live_... (from Stripe dashboard, LIVE mode) | Yes |
| Variable | Value | Required |
|---|---|---|
VITE_WEB_URL | https://yourdomain.com | Yes |
VITE_APP_URL | https://app.yourdomain.com | Yes |
VITE_CONVEX_URL | https://YOUR_DEPLOYMENT.convex.cloud | Yes |
VITE_STRIPE_PUBLISHABLE_KEY | pk_live_... (from Stripe dashboard, LIVE mode) | Yes |
1c. Admin Panel (apps/ops)
- Create a third Vercel project from the same repository
- Set Root Directory to
apps/ops - Set Framework Preset to Vite
- Set Build Command to
cd ../.. && pnpm turbo build --filter=ops - Set Install Command to
cd ../.. && pnpm install - Add environment variables (see table below)
- Click Deploy
Environment variables for apps/ops:
| Variable | Value | Required |
|---|---|---|
VITE_SUPABASE_URL | https://YOUR_PROJECT_REF.supabase.co | Yes |
VITE_SUPABASE_ANON_KEY | eyJ... (same anon key as apps/app) | Yes |
| Variable | Value | Required |
|---|---|---|
VITE_CONVEX_URL | https://YOUR_DEPLOYMENT.convex.cloud | Yes |
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 pushIf you haven't linked your project yet:
npx supabase link --project-ref YOUR_PROJECT_REFDeploy Edge Functions
Deploy all Edge Functions to production:
npx supabase functions deployThis 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-webhookSet 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.comVerify your secrets are set:
npx supabase secrets listImportant:
APP_URLis used by Edge Functions for CORS and redirect logic. It must match your production dashboard URL exactly —https://app.yourdomain.comwith no trailing slash.
Deploy Convex
Deploy your Convex backend to production:
npx convex deployThis 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.comVerify your environment variables are set:
npx convex env listImportant:
APP_URLis used for redirect logic. It must match your production dashboard URL exactly —https://app.yourdomain.comwith 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:
-
Switch to LIVE mode in the Stripe dashboard (toggle in the top-left corner)
-
Create your products and prices in live mode. These must be created fresh — they cannot be copied from test mode.
-
Copy the live Price IDs (e.g.,
price_1Abc...) and update them in:packages/config/src/pricing.tsReplace every test price ID (
price_1Test...) with the corresponding live price ID. -
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-webhookhttps://YOUR_DEPLOYMENT.convex.site/stripe-webhook- Select these events:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failed
- Click Add endpoint
- 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_...- Copy the Live Publishable Key (
pk_live_...) and make sure it is set asVITE_STRIPE_PUBLISHABLE_KEYin 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
-
Go to your Supabase dashboard > Authentication > URL Configuration
-
Set Site URL to:
https://app.yourdomain.com -
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.).
-
Set the
SITE_URLenvironment variable in Convex:npx convex env set SITE_URL https://app.yourdomain.com -
Update
convex/auth.config.tswith 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:
- Go to Google Cloud Console > APIs & Services > Credentials
- Edit your OAuth 2.0 Client
- Under Authorized redirect URIs, add:
https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback - Remove any
localhostredirect URIs (or keep them for local dev)
GitHub OAuth:
- Go to GitHub Developer Settings > OAuth Apps
- Edit your OAuth App
- 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:
- Go to Google Cloud Console > APIs & Services > Credentials
- Edit your OAuth 2.0 Client
- Under Authorized redirect URIs, add:
https://YOUR_DEPLOYMENT.convex.site/api/auth/callback/google
GitHub OAuth:
- Go to GitHub Developer Settings > OAuth Apps
- Edit your OAuth App
- 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.
-
Verify your domain in the Resend dashboard:
- Add
yourdomain.com - Add the DNS records Resend provides (SPF, DKIM, DMARC)
- Wait for verification to complete
- Add
-
Update the from email in your backend secrets:
npx supabase secrets set RESEND_FROM_EMAIL=hello@yourdomain.comnpx convex env set RESEND_FROM_EMAIL hello@yourdomain.comThe from address must use your verified domain. Emails from unverified domains will be rejected.
- 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.comIf CORS errors appear in the browser console after deploying, double-check:
- The
APP_URLsecret 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:
- Go to Settings > Domains in the Vercel dashboard
- Add your domain:
| Vercel Project | Domain(s) |
|---|---|
| apps/web | yourdomain.com and www.yourdomain.com |
| apps/app | app.yourdomain.com |
| apps/ops | admin.yourdomain.com |
- 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:
| Type | Name | Value | Project |
|---|---|---|---|
| A | @ | 76.76.21.21 | apps/web |
| CNAME | www | cname.vercel-dns.com | apps/web |
| CNAME | app | cname.vercel-dns.com | apps/app |
| CNAME | admin | cname.vercel-dns.com | apps/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_URLandNEXT_PUBLIC_APP_URLset - apps/app on Vercel has
VITE_WEB_URL,VITE_APP_URL,VITE_SUPABASE_URL,VITE_SUPABASE_ANON_KEY, andVITE_STRIPE_PUBLISHABLE_KEYset - apps/ops on Vercel has
VITE_SUPABASE_URLandVITE_SUPABASE_ANON_KEYset - 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_URLandNEXT_PUBLIC_APP_URLset - apps/app on Vercel has
VITE_WEB_URL,VITE_APP_URL,VITE_CONVEX_URL, andVITE_STRIPE_PUBLISHABLE_KEYset - apps/ops on Vercel has
VITE_CONVEX_URLset - 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.tsare 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 asSTRIPE_WEBHOOK_SECRETin Supabase secrets - Stripe publishable key in apps/app is the LIVE key (
pk_live_..., notpk_test_...)
- Products and prices created in LIVE mode (not test mode)
- Price IDs in
packages/config/src/pricing.tsare updated to live price IDs - Webhook endpoint URL is
https://YOUR_DEPLOYMENT.convex.site/stripe-webhook - Webhook signing secret (
whsec_...) is set asSTRIPE_WEBHOOK_SECRETin Convex env vars - Stripe publishable key in apps/app is the LIVE key (
pk_live_..., notpk_test_...)
Auth
- Site URL is set to
https://app.yourdomain.com - Redirect URLs include
https://app.yourdomain.com/**andhttps://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_URLenv var is set tohttps://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
- Domain verified in Resend (SPF, DKIM, DMARC records added)
-
RESEND_FROM_EMAILuses 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_EMAILSsecret contains the correct admin email addresses - CORS is restricted to production
APP_URLonly -
apps/ops(admin panel) is not publicly accessible, or has proper admin email verification
DNS
-
yourdomain.compoints to apps/web on Vercel -
app.yourdomain.compoints to apps/app on Vercel -
admin.yourdomain.compoints 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_SECRETSupabase 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_SECRETConvex 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_URLSupabase 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.tsconfiguration.
Emails not sending
- Confirm your domain is verified in Resend.
- Confirm
RESEND_FROM_EMAILuses 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.