How to Deploy a Next.js App to Vercel with Environment Variables
Deploying a Next.js app to Vercel is one of the fastest paths to production. But environment variables trip people up more than anything else — especially the first time. This guide walks through the complete deployment setup, including how Vercel environments work, which variables to expose to the browser, and how to avoid the most common mistakes.
Prerequisites
- A Next.js 16 project (App Router)
- A Vercel account (free tier works)
- Git repository on GitHub, GitLab, or Bitbucket
Step 1: Import Your Project
Log in to your Vercel dashboard and click Add New → Project. Connect your Git provider if you haven't already, then select your repository.
Vercel auto-detects Next.js and sets the correct build settings (bun run build or next build, depending on your setup). You don't need to change the framework preset.
Step 2: Understand Vercel's Three Environments
Before adding environment variables, understand that Vercel has three distinct environments:
| Environment | When it runs | Typical use |
|---|---|---|
| Production | Every push to your main branch | Live site (e.g., https://yourapp.io) |
| Preview | Every push to non-main branches and pull requests | Staging, feature previews |
| Development | vercel dev locally | Local dev with Vercel's edge runtime |
Each environment can have different values for the same variable. A common pattern:
DATABASE_URL→ points to production DB in Production, test DB in PreviewSTRIPE_SECRET_KEY→ live key in Production, test key everywhere else
Step 3: Add Environment Variables in the Vercel Dashboard
Go to your project in Vercel → Settings → Environment Variables.
For each variable:
- Enter the key (e.g.,
DATABASE_URL) - Enter the value
- Select which environments it applies to (Production, Preview, Development, or any combination)
- Click Save
The NEXT_PUBLIC_ prefix
Next.js has two classes of environment variables:
- Server-only (no prefix): Available only in server components, API routes, and server actions. Never exposed to the browser. Use these for secrets (API keys, database URLs, auth secrets).
NEXT_PUBLIC_(public): Baked into the client-side bundle at build time. Visible to anyone who views the page source. Only use these for non-sensitive config.
# .env.local (local development — never commit this)
DATABASE_URL=postgresql://localhost:5432/myapp
NEXTAUTH_SECRET=your-secret-here
STRIPE_SECRET_KEY=sk_test_...
# These will be inlined into the browser bundle:
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
Critical: NEXT_PUBLIC_ variables are replaced at build time, not at runtime. If you change a NEXT_PUBLIC_ variable in Vercel after a deploy, you must redeploy for the new value to take effect.
Step 4: Set Up Your .env Files Locally
Next.js loads environment files in this order (later files override earlier ones):
.env— defaults, committed to git (only non-sensitive values).env.local— local overrides, never committed (add to.gitignore).env.production/.env.development— environment-specific defaults.env.production.local/.env.development.local— local env-specific overrides
For a typical project, you only need two files:
# .env (committed — non-sensitive defaults only)
NEXT_PUBLIC_SITE_URL=https://yourapp.io
# .env.local (never committed — your secrets)
DATABASE_URL=postgresql://localhost:5432/myapp_dev
NEXTAUTH_SECRET=generate-with-openssl-rand-base64-32
AUTH_GITHUB_ID=your-github-oauth-app-id
AUTH_GITHUB_SECRET=your-github-oauth-app-secret
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
Generate a strong NEXTAUTH_SECRET:
openssl rand -base64 32
Step 5: Handle the NEXTAUTH_URL Variable
NextAuth v5 needs to know the canonical URL of your app to generate correct redirect URLs. In v5 this is AUTH_URL (v4 used NEXTAUTH_URL).
- Locally:
AUTH_URL=http://localhost:3000 - Production:
AUTH_URL=https://yourapp.io - Preview deploys: This is the tricky one — Vercel generates a unique URL per preview deploy (e.g.,
https://yourapp-git-feature-abc123.vercel.app)
For preview deploys, you can use Vercel's system environment variables. Set:
AUTH_URL=${VERCEL_URL}
Wait — VERCEL_URL does not include https://. You need to construct it:
AUTH_URL=https://${VERCEL_URL}
Vercel supports referencing other environment variables in values, but the syntax must be exactly right. Alternatively, use a custom domain for preview deploys and set AUTH_URL to match.
NextAuth v5 also works without AUTH_URL if you set AUTH_TRUST_HOST=1 in non-production environments, which tells it to derive the URL from the request host header:
AUTH_TRUST_HOST=true
Step 6: Configure OAuth Redirect URIs
If you're using OAuth providers (GitHub, Google, etc.), each provider's OAuth app needs to whitelist your redirect URIs.
For GitHub OAuth:
- Go to GitHub → Settings → Developer Settings → OAuth Apps
- Set Homepage URL to your production URL
- Set Authorization callback URL to
https://yourapp.io/api/auth/callback/github
For preview deploys, you'll need either:
- A wildcard callback URL in GitHub (not supported — GitHub requires exact URLs)
- A separate OAuth app for staging with the preview URL
- Or use
AUTH_TRUST_HOSTto skip strict URL validation in preview
Google supports wildcard patterns (https://*.vercel.app/api/auth/callback/google) which makes preview deployments much easier.
Step 7: Database Migrations on Deploy
If your project uses Drizzle ORM with PostgreSQL, you'll want migrations to run automatically on production deploy rather than manually.
Add a postbuild script to package.json:
{
"scripts": {
"build": "next build",
"postbuild": "drizzle-kit migrate"
}
}
Vercel runs postbuild automatically after build. This ensures your schema is always in sync after deployment. Make sure your DATABASE_URL in Vercel points to your production database.
Note: This runs on every deploy, including preview deploys. If you're pointing preview at a shared staging database, postbuild migrations will run there too. Use a separate database for preview or conditionally skip migrations based on VERCEL_ENV:
"postbuild": "[ \"$VERCEL_ENV\" = \"production\" ] && drizzle-kit migrate || true"
Step 8: Deploy
With environment variables set, click Deploy in the Vercel dashboard (or push a commit to trigger an automatic deploy). Vercel will:
- Clone your repository
- Install dependencies (
bun installornpm install) - Build the project (
next build) - Run
postbuildif defined - Deploy the output to its global edge network
Your site goes live at https://yourapp.vercel.app (or a custom domain if configured).
Checking Environment Variables at Runtime
Use vercel env pull to download environment variables from Vercel to a local .env.local file:
vercel env pull .env.local
This is the fastest way to sync your local environment with what's in production. It requires the Vercel CLI:
npm i -g vercel
vercel login
vercel link # links your local project to the Vercel project
vercel env pull .env.local
Common Mistakes
1. Committing .env.local to git. Add it to .gitignore immediately. If you accidentally commit a secret, rotate the key — don't just delete the file.
2. Using NEXT_PUBLIC_ for secrets. The NEXT_PUBLIC_ prefix puts the value into the browser bundle. API keys with NEXT_PUBLIC_ prefixes are fully visible in the page source.
3. Forgetting to redeploy after changing NEXT_PUBLIC_ variables. These are baked in at build time. Changing them in the Vercel dashboard doesn't update a live deploy.
4. Missing AUTH_URL in production. Auth redirects will fail or point to localhost if AUTH_URL isn't set correctly.
5. Using the same database for production and preview. Preview deploys should point to an isolated database or at minimum a separate schema, so test data doesn't pollute production.
Wrapping Up
Environment variables are simple once you understand the two-axis model: server-only vs. NEXT_PUBLIC_, and Production vs. Preview vs. Development. Get these right from the start and you'll rarely have deploy surprises.
If you're starting from Boltbase, all of this is pre-documented in the included CLAUDE.md and .env.example — including which variables each integration needs and which scope to assign them in Vercel.