Deployment Guide
Vercel for the Next.js app, Supabase for prod data, Stripe live mode, and HTTPS webhook URLs that match what you configured locally: same checklist, stricter keys.
Prerequisites
- Vercel account
- Supabase project (production)
- Stripe account (live mode)
- At least one AI provider key (OpenAI and/or Anthropic)
1. Environment Variables
Set these in your Vercel project → Settings → Environment Variables.
# Supabase (Production)
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Stripe (Live Mode)
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_PRO_PRICE_ID=price_... # Professional plan: $49/mo
STRIPE_TOPUP_PRICE_ID=price_... # Credit top-up: $20 one-time (1000 credits)
STRIPE_ENTERPRISE_PRICE_ID=price_... # Enterprise plan: $199/mo
# AI Providers
OPENAI_API_KEY=sk-proj-...
ANTHROPIC_API_KEY=sk-ant-...
# App URL (your production domain, no trailing slash)
NEXT_PUBLIC_APP_URL=https://your-domain.com
# Cron secret: protects /api/cron/reset-credits
CRON_SECRET=your_random_secret_here
Note:
SUPABASE_SERVICE_ROLE_KEYis server-only and never exposed to the browser. Never prefix it withNEXT_PUBLIC_.
2. Vercel Setup
Connect Repository
- Go to vercel.com → Add New Project
- Import your GitHub repository
- Framework: Next.js (auto-detected)
- Build command:
pnpm build - Install command:
pnpm install - Root directory:
.
Deploy
Add all environment variables then click Deploy.
The vercel.json at the root registers a daily cron job:
{
"crons": [
{
"path": "/api/cron/reset-credits",
"schedule": "0 0 * * *"
}
]
}
This runs at midnight UTC and resets credits for users whose billing cycle has renewed. It requires the Authorization: Bearer <CRON_SECRET> header, which Vercel Cron sends automatically using the CRON_SECRET env var.
3. Database Setup
Run the Schema
- Open SQL Editor in Supabase dashboard
- Paste the full contents of
src/db/schema.sql - Click Run
This creates all tables, RLS policies, triggers, indexes, and storage policies.
Migrations for Existing Databases
If you already have a running database and are updating from an older version, run these migration statements:
-- Add display name column (if not present)
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS name text;
-- Add max_credits column: tracks the credit ceiling set at subscription/top-up time
-- This keeps the denominator stable (e.g. 3595/3600 instead of 3595/3595)
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS max_credits int;
UPDATE profiles SET max_credits = credits WHERE max_credits IS NULL;
Create Storage Bucket
- Go to Storage in Supabase dashboard
- Create bucket named
uploads - Set to Private
- Storage policies are already in
schema.sql: no extra setup needed
Configure Auth Providers
- Go to Authentication → Providers
- Enable Google OAuth:
- Create OAuth app in Google Cloud Console
- Add callback URL:
https://xxxxx.supabase.co/auth/v1/callback - Paste client ID and secret
- Enable GitHub OAuth:
- Create OAuth app in GitHub Developer Settings
- Add callback URL:
https://xxxxx.supabase.co/auth/v1/callback - Paste client ID and secret
4. Stripe Setup
Create Products
In Stripe Dashboard → Products, create three products:
| Product | Billing | Price |
|---|---|---|
| Professional Plan | Recurring / Monthly | $49/mo |
| Enterprise Plan | Recurring / Monthly | $199/mo |
| Credit Top-up (1000 credits) | One-time | $20 |
For each product, copy the Price ID (price_xxx): not the Product ID: into your environment variables.
Configure Webhook
- Go to Developers → Webhooks
- Add endpoint:
https://your-domain.com/api/webhooks/stripe - Select these events:
checkout.session.completedinvoice.paidcustomer.subscription.deleted
- Copy the Webhook Signing Secret (
whsec_xxx) intoSTRIPE_WEBHOOK_SECRET
Product ID vs Price ID: Stripe shows both. You need the Price ID that starts with
price_, not the Product ID that starts withprod_.
5. Custom Domain
- Go to Vercel Project → Settings → Domains
- Add your domain
- Configure DNS as instructed by Vercel
- Update
NEXT_PUBLIC_APP_URLto your production domain
6. Post-Deployment Checklist
Database
- Schema SQL ran without errors
- Migration SQL ran (if upgrading existing database)
- Storage bucket
uploadscreated as Private - Auth providers configured
Stripe
- Three products created (Pro, Enterprise, Top-up)
- Price IDs added to environment variables (not Product IDs)
- Webhook endpoint registered with all three events
- Webhook signing secret in
STRIPE_WEBHOOK_SECRET
Security
-
SUPABASE_SERVICE_ROLE_KEYonly in server env (noNEXT_PUBLIC_prefix) -
STRIPE_SECRET_KEYonly in server env -
CRON_SECRETset to a random string (e.g.openssl rand -hex 32) - RLS policies verified in Supabase
Functionality
- Sign up / sign in works
- Chat sends and receives messages
- Credits deduct after each message
- Document upload and RAG retrieval works
- Stripe checkout completes and tier updates
- Subscription webhook grants correct credits
- Top-up webhook adds credits without resetting max
- Monthly cron resets credits at midnight UTC
7. Monitoring
| Service | Where to look |
|---|---|
| App errors | Vercel → Deployments → Functions logs |
| Auth | Supabase → Authentication → Logs |
| Database | Supabase → Database → Logs |
| Webhooks | Stripe → Developers → Webhooks → recent deliveries |
| Cron | Vercel → Cron Jobs tab (Pro plan required for cron) |
8. Scaling
Connection Pooling
Enable Supabase connection pooler (PgBouncer) for high-traffic deployments:
- Set the connection string to the pooler URL in your Supabase project settings
Rate Limiting
The chat API has no built-in rate limiting. For production add:
- Upstash Ratelimit (Redis-backed, Vercel Edge compatible)
- Vercel's built-in Edge Middleware rate limiting
Edge Caching
Marketing pages use force-static with hourly revalidation and are automatically cached by Vercel's CDN: no extra configuration needed.