Preview Environments
Configure staging environments with Vercel preview deployments and separate Supabase projects.
This guide documents our cost-effective preview environment setup using:
- Local Docker Supabase - For development and initial testing
- Separate Free-Tier Supabase Project - For staging (
developmentbranch) - Production Supabase Project - For
mainbranch - Vercel Preview Deployments - Automatic deployments per branch
Architecture
┌─────────────────────────────────────────────────────────────────┐ │ WORKFLOW │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ LOCAL STAGING PRODUCTION │ │ ───── ─────── ────────── │ │ │ │ feature/xyz → development → main │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ Docker Supabase Free-Tier Project Pro Project │ │ (pnpm dev) (Vercel Preview) (Vercel Prod) │ │ │ │ Cost: $0 Cost: $0 Cost: $25/mo │ │ │ └─────────────────────────────────────────────────────────────────┘
Cost Summary
| Environment | Database | Cost |
|---|---|---|
| Local | Docker Supabase | $0 |
Staging (development) | Free-tier Supabase project | $0 |
Production (main) | Pro Supabase project | $25/month |
| Total | $25/month |
Prerequisites
- Supabase Pro Plan for production
- Vercel account with project connected
- GitHub repository
- Separate free-tier Supabase project for staging
Phase 1: Create Staging Supabase Project
1.1 Create New Free-Tier Project
- Go to Supabase Dashboard
- Click New Project
- Configure:
- Name:
eminentid-staging(or similar) - Database Password: Generate a strong password (save it!)
- Region: Same as production for consistency
- Plan: Free tier
- Name:
- Click Create new project
1.2 Get Staging Credentials
Once the project is created, go to Settings → API and note:
Project URL(e.g.,https://xxxxx.supabase.co)anon/publickeyservice_rolekey (keep secret!)
1.3 Apply Migrations to Staging
You need to apply your existing migrations to the staging database.
Important: Run all Supabase CLI commands from the apps/web directory where the supabase/ folder with migrations lives:
# Navigate to apps/web first cd apps/web # Link to the staging project npx supabase link --project-ref <staging-project-ref> # Push migrations npx supabase db push
Alternatively, you can run migrations via the Supabase Dashboard SQL editor.
Phase 2: Configure Vercel Environment Variables
2.1 Set Up Branch-Specific Environment Variables
Go to Vercel Dashboard → Your Project → Settings → Environment Variables
Add staging credentials for the
developmentbranch:Variable Value Environment NEXT_PUBLIC_SUPABASE_URLhttps://[staging-ref].supabase.coPreview NEXT_PUBLIC_SUPABASE_ANON_KEY[staging-anon-key]Preview SUPABASE_SERVICE_ROLE_KEY[staging-service-role-key]Preview Important: When adding each variable, click "Add to specific branches" and enter
developmentThis ensures only the
developmentbranch uses staging credentials. Other preview branches will use production (or you can set different values).
2.2 Verify Production Variables
Ensure your production variables are set for the Production environment:
| Variable | Value | Environment |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | https://[prod-ref].supabase.co | Production |
NEXT_PUBLIC_SUPABASE_ANON_KEY | [prod-anon-key] | Production |
SUPABASE_SERVICE_ROLE_KEY | [prod-service-role-key] | Production |
Phase 3: Set Up Git Branches
3.1 Create Development Branch
The development branch already exists. Verify it:
git fetch origin git checkout development
3.2 Verify Vercel Deployments
After pushing to development, Vercel should deploy to:
- Staging URL:
[project]-git-development-[team].vercel.app
Check that this deployment uses the staging Supabase credentials.
Phase 4: Enforce the Development Flow (GitHub Action)
Prevent anyone from merging feature branches directly to main. All code must flow through development first.
Note: GitHub Rulesets and Branch Protection rules require GitHub Team ($4/user/month) for private repos. We use a GitHub Action as a free alternative.
How It Works
A GitHub Action (.github/workflows/enforce-development-flow.yml) runs on every PR to main and fails if the source branch is not development.
name: Enforce Development Flow
on:
pull_request:
branches: [main]
jobs:
check-source-branch:
runs-on: ubuntu-latest
steps:
- name: Check if PR is from development branch
run: |
if [[ "${{ github.head_ref }}" != "development" ]]; then
echo "❌ PRs to main must come from the 'development' branch."
exit 1
fi
echo "✅ PR is from development branch. Allowed."
What This Enforces
| Action | Result |
|---|---|
feature/xyz → development | Allowed |
development → main | Allowed |
feature/xyz → main | Check fails (soft block) |
Limitations
This is a soft block - the check fails but anyone with merge access can still override and merge. For hard enforcement, upgrade to GitHub Team.
Upgrading to GitHub Team (Future)
When you upgrade, you can use proper branch protection:
- Go to Settings → Branches → Add branch protection rule
- Branch name pattern:
main - Enable Require status checks to pass before merging
- Add
check-source-branchas a required check - Enable Do not allow bypassing the above settings
Phase 5: The Workflow
Daily Development Flow
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ LOCAL │ │ STAGING │ │ PRODUCTION │
│ │ │ │ │ │
│ feature/xyz │ ──► │ development │ ──► │ main │
│ │ │ │ │ │
│ Docker DB │ │ Free-tier DB │ │ Pro DB │
│ localhost │ │ Vercel URL │ │ Prod URL │
└──────────────┘ └──────────────┘ └──────────────┘
TEST TEST DEPLOY
Step-by-Step
Create feature branch from development
git checkout development git pull origin development git checkout -b feature/my-feature
Develop locally
pnpm supabase:web:start # Start local Docker Supabase pnpm dev # Start Next.js dev server
- Make your code changes
- Test with local database
Schema changes (if needed)
pnpm --filter web supabase migrations new my_schema_change # Edit the generated SQL file in apps/web/supabase/migrations/
Push and create PR to development
git add . git commit -m "feat: my feature" git push origin feature/my-feature
- Open PR targeting
developmentbranch (notmain!)
- Open PR targeting
Test on staging
- Merge PR into
development - Apply migrations to staging DB (from
apps/webdirectory):cd apps/web npx supabase link --project-ref <staging-project-ref> npx supabase db push
- Test on staging URL:
[project]-git-development.vercel.app - Verify everything works with real (staging) data
- Merge PR into
Promote to production
- Create PR:
development→main - Apply migrations to production DB (from
apps/webdirectory):cd apps/web npx supabase link --project-ref <prod-project-ref> npx supabase db push
- Merge PR
- Vercel deploys to production
- Create PR:
Phase 6: Keeping Databases in Sync
Running Migrations
Since we're using separate Supabase projects (not branching), you need to manually apply migrations to each environment.
Local:
pnpm supabase:web:reset # Resets and applies all migrations
Staging:
cd apps/web npx supabase link --project-ref <staging-project-ref> npx supabase db push
Production:
cd apps/web npx supabase link --project-ref <prod-project-ref> npx supabase db push
Seed Data (Staging Only)
Create a seed file for staging test data at apps/web/supabase/seed.sql:
-- Seed data for staging environment
-- This file should be idempotent (safe to run multiple times)
-- Example: Insert test organization
INSERT INTO public.accounts (id, name, slug, is_personal_account)
VALUES
('00000000-0000-0000-0000-000000000001', 'Test Organization', 'test-org', false)
ON CONFLICT (id) DO NOTHING;
-- Add more seed data as needed...
Apply seed data:
npx supabase db reset --linked # WARNING: This wipes the database first # OR psql <staging-connection-string> -f apps/web/supabase/seed.sql
Configuration Checklist
Environment Variables
Ensure these are never hardcoded - always use process.env:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY
Security
- Row Level Security (RLS) policies are active on all tables
- RLS policies applied to both staging and production
- Staging credentials are NOT production credentials
Git Ignore
Ensure these are in .gitignore:
.env.local.env*.local- Any files containing secrets
Troubleshooting
Staging URL shows production data
- Check Vercel environment variables for
developmentbranch - Verify variables are scoped to "Preview" and specific to
developmentbranch - Redeploy the
developmentbranch
Migrations out of sync
- Check migration files in
apps/web/supabase/migrations/ - Run
npx supabase db pushon the target environment - If conflicts, check the
supabase_migrationstable in the database
Can't merge to main
- Verify you're merging from
developmentbranch - Check GitHub ruleset is configured correctly
- If emergency, use a
hotfix/*branch (if exception is configured)
Free tier project paused
Free-tier Supabase projects pause after 1 week of inactivity. To prevent:
- Set up a cron job or GitHub Action to ping the database weekly
- Or simply unpause manually when needed for testing
Quick Reference
URLs
| Environment | Vercel URL | Supabase Dashboard |
|---|---|---|
| Production | your-domain.com | Production Project |
| Staging | project-git-development.vercel.app | Staging Project |
| Local | localhost:3000 | localhost:54323 |
Commands
# Local development pnpm supabase:web:start # Start local Supabase pnpm dev # Start Next.js # Migrations (run from apps/web directory) cd apps/web pnpm --filter web supabase migrations new <name> # Create migration npx supabase db push # Apply to linked project # Switch between projects (run from apps/web directory) cd apps/web npx supabase link --project-ref <ref> # Link to a project