Skip to main content
Reference

Environments

Development, preview, and production environment configuration. Vercel + Supabase branching model.

Environments

Purpose: How Framework manages development, preview, and production environments. Covers the three-environment model, secrets management via 1Password, deployment flow, and service-specific isolation strategies.

Environment strategy

Three-environment model

Environment Suffix Purpose URL Who
Local lcl Development localhost:3000 Developers
Preview pre Regression testing before production pr-123.vercel.app Developers, reviewers
Production prd Real users app.product.com Everyone

Promotion flow: Local (lcl) → Preview (pre) → Production (prd).

  • Develop locally — Push branch or merge to main
  • Vercel auto-deploys Preview — Regression test on the preview URL
  • Promote to Production — Via Vercel dashboard only

Pushing to main does NOT deploy to production.

Environment variables

Prefix Visibility Exposed to browser?
NEXT_PUBLIC_* Public, client-side Yes
No prefix Server-side only No

Never put secrets in NEXT_PUBLIC_ variables.

Backing services per environment

Each environment has its own isolated backing services. Local and Preview do NOT share databases.

Service Local (lcl) Preview (pre) Production (prd)
Supabase {project}-lcl {project}-pre {project}-prd
Clerk Dev instance (shared) Dev instance (shared) Production instance
Resend Shared key (guarded) Shared key (guarded) Live mode
PostHog Test project Test project Prod project
Stripe Test mode Test mode Live mode

Naming convention for Supabase projects: {project-name}-{env} (e.g., omni-access-lcl, omni-access-pre, omni-access-prd).

Service-specific environment strategies

Not every service supports three isolated environments. The strategies below document how each service maps to the three-environment model.

Clerk (Auth)

Clerk offers two tiers -- Development (pk_test_/sk_test_) and Production (pk_live_/sk_live_). There is no preview tier.

Environment Clerk Instance Keys
Local (lcl) Dev instance (shared) pk_test_ / sk_test_
Preview (pre) Dev instance (shared) Same as lcl
Production (prd) Production instance pk_live_ / sk_live_

Resend (Email)

Resend has no sandbox, test mode, or per-environment isolation. All environments share one Resend account. Isolation is handled at two levels: code-level recipient guarding and API key scoping.

Environment isolation (code-level guard):

In non-production, the email client redirects all recipients to delivered@resend.dev (Resend's safe test address). Real API calls are made so you can validate sending works, but no real inboxes are hit. Only production (VERCEL_ENV=production) delivers to actual recipients.

// resend-client.ts
function safeRecipient(to: string): string {
  if (process.env.NODE_ENV === "production" && process.env.VERCEL_ENV === "production") {
    return to
  }
  return "delivered@resend.dev"
}

API key strategy:

Resend supports two permission levels per API key, with optional domain restriction:

Key type Permissions Domain scope Use for
Full Access Send, manage resources All domains Local dev, admin
Sending Access Send only Restricted to one domain Production, per-tenant

For multi-tenant apps where tenants have their own sending domains, create a domain-scoped Sending Access key per tenant and store it in the database alongside EMAIL_FROM (tenant config, not env vars).

Viewing and previewing emails:

Method What you see When to use
pnpm email:dev React Email preview server (localhost:3001) Designing/iterating on templates
Resend Dashboard > Emails Rendered preview, HTML, plain text, delivery logs After sending (including test sends to delivered@resend.dev)
Resend Dashboard > Share Public link to a sent email (valid 48h) Sharing with teammates for review

Domain reputation: Never send test emails to fake addresses from your production domain. The delivered@resend.dev guard prevents this -- test sends go through Resend's test infrastructure and don't affect your domain reputation.

Secrets management: 1Password

All secrets stored in 1Password -- one item per environment within a single project vault. 1Password is the single source of truth.

Vault and item structure

  • One vault per project — Contains all environment items
  • One item per environment — Stores the full .env content in notesPlain (a single text block, not individual fields)
Vault Item Maps to When populated
{project} {project}-lcl .env.local (local dev) M0: Manual Setup
{project} {project}-pre Vercel Preview env When preview environment is created
{project} {project}-prd Vercel Production env Pre-launch

Layer 1: Local development -- copy from 1Password

Copy the notesPlain content from the 1Password item into .env.local:

  1. Open 1Password — Navigate to {project} vault > {project}-lcl item
  2. Copy the notes content — Contains all key=value pairs
  3. Paste into .env.local — Gitignored, never committed

The .env.op file (committed) documents which vault/item maps to which environment. It is a reference file, not used by op run.

Layer 2: CI -- GitHub Actions

  • CI secrets — Configured as GitHub repository secrets or injected from Vercel environment variables
  • Source of truth — The 1Password item defines which values to set

Layer 3: Vercel

Env vars are synced from 1Password to Vercel using pnpm sync:vercel-env:

pnpm sync:vercel-env wix-access-pre preview
pnpm sync:vercel-env wix-access-prd production

The script reads notesPlain from the 1Password item, extracts KEY=VALUE lines, and sets each one in Vercel using printf (not echo) to avoid trailing newlines.

[!WARNING] Never use echo "value" | vercel env add manually -- echo appends a trailing newline that becomes part of the value and breaks length-validated vars. Always use printf '%s' "value" or the sync script.

Layer 4: Typed validation -- lib/env.ts

  • Zod schema — Validates every env var on first import
  • Fails hard — Clear error listing missing vars

Summary: secret flow per environment

Environment 1Password Item Mechanism
Local {project}-lcl Copy notesPlain into .env.local
CI {project}-lcl GitHub secrets / Vercel env
Vercel Preview {project}-pre pnpm sync:vercel-env {project}-pre preview
Vercel Production {project}-prd pnpm sync:vercel-env {project}-prd production

Do: Store all secrets in 1Password notesPlain, one item per environment, copy into .env.local for local dev, validate with Zod.

Don't: Commit .env.local, reuse local keys in preview/production, share database between environments.

Deployment

Branch strategy

Git Branch Vercel Environment Triggers
main Preview Every push to main
Feature branches Preview Every push
production Production Only when promoted via Vercel dashboard

Vercel production branch is set to production, not main.

  • Pushes to main — Create Preview deployments (backed by -pre services), not Production deployments
  • Production is always explicit — Never an automatic deploy
  • production branch — Exists in the repo but is never pushed to directly; serves only as Vercel's production branch target
  • To deploy to production — Promote a known-good Preview deployment in the Vercel dashboard

Deploy flow

Feature branch pushed -> Vercel Preview deployment (backed by -pre services)
Merge to main -> Vercel Preview deployment (backed by -pre services)
Test on Preview -> Promote to Production in Vercel dashboard

Rollback procedure

If production breaks:

  1. Open Vercel Dashboard — Navigate to Project > Deployments
  2. Find last known good deployment — Review the deployment list
  3. Select "..." menu — Click "Promote to Production"
  4. Time — Under 60 seconds

Rule: Rollback first, debug second.

Database migrations (expand/contract)

Two-phase migrations prevent breaking running code during deploy:

  1. Expand (backward compatible) — Add new columns as nullable/with defaults, deploy code handling both schemas
  2. Contract (cleanup) — Remove old columns after confirming new schema works
npx prisma migrate dev --name descriptive_name  # Local
# Preview/Production: npx prisma migrate deploy (via Vercel build)
  • Apply to all three Supabase projects — Migrations must reach lcl, pre, and prd
  • Never run prisma migrate dev against preview or production

Feature flags (PostHog)

  • Use for — Large features, critical path changes, or features you might need to kill quickly
  • Skip for — Bug fixes, small improvements, and non-user-facing changes

Code quality gates

Every PR must pass before merge:

Check Tool
Lint ESLint
Format Prettier
Type check TypeScript
Tests Vitest
Build Next.js
pnpm type-check && pnpm lint && pnpm test:run && pnpm build

Checklist before merge

  • Preview environment tested — Regression passes against -pre database
  • No console.log statements
  • Database migration is backward compatible — If applicable
  • Feature flag in place — If risky

Related standards

  • standards/core/sdlc.md -- Development lifecycle
  • standards/reference/tech-stack.md -- Technology choices

Last updated: March 2026

Search Framework Explorer

Search agents, skills, and standards