Skip to main content
友田 陽大
Vercel in production
Vercel
セキュリティ
Next.js
TypeScript
型安全
CI/CD
アーキテクチャ設計

Vercel environment-variable / secret management guide: 3 environments, the NEXT_PUBLIC_ trap, OIDC keyless, and a type-safe boundary

An environment-variable / secret management guide faithful to Vercel's official docs. With real code, it explains the three environments (production/preview/development) and branch-specific overrides, the trap of browser exposure via NEXT_PUBLIC_, the 64KB limit and the Edge 5KB limit, local sync with vercel env pull, system environment variables, external-cloud connection via OIDC keyless, and a type-safe env boundary with Zod.

Published
Reading time
6 min read
Author
友田 陽大
Share

Handling secrets is unassuming but an area where one mistake is instantly fatal. "An API key leaked to GitHub," "I attached NEXT_PUBLIC_ and a production key was showing in the browser" — all happen in minutes and have a long tail.

This article summarizes the use of the three environments, the NEXT_PUBLIC_ trap, OIDC keyless, and a type-safe env boundary, faithful to the official specs of Vercel environment variables. For the full picture, see the Vercel production-operations guide; for entrance security, Firewall, WAF, BotID.


Use the three environments

Vercel environment variables can have different values per environment. Values are encrypted at save time and are visible to members who can access the project.

EnvironmentWhen it appliesUse
ProductionPush to the production branch (usually main) / vercel --prodProduction keys and connection strings
PreviewPush to a non-production branch / vercelStaging DB, etc. Can be overridden per branch
Developmentvercel dev / localFor local development
# 環境ごとに値を登録
vercel env add DATABASE_URL production    # 本番DB
vercel env add DATABASE_URL preview       # ステージングDB
vercel env add DATABASE_URL development   # ローカルDB

# 特定ブランチだけ上書きしたいとき(preview)
# → ダッシュボードでブランチ指定。同名変数はブランチ別が優先される

An important pitfall: environment-variable changes apply only to new deployments. Existing production deployments keep their values. Most of "I changed it but it's not reflected" is this — redeploy.

Preview's branch-specific override

Preview variables can apply to "all non-production branches" or "a specific branch." A branch-specified variable overrides the same-named general Preview variable, so you only need to define the diff rather than duplicating everything.


The NEXT_PUBLIC_ trap (most important)

The most common and most dangerous incident is the NEXT_PUBLIC_ prefix.

  • A variable with NEXT_PUBLIC_ is embedded into the bundle at build time and exposed to the browser.
  • So never attach it to API keys, DB connection strings, secret keys, or service-role keys.
  • The only things you may attach it to are "public values that need to be read on the client" (a publishable measurement ID, the base URL of a public API, etc.).
// ❌ 絶対NG:秘密に NEXT_PUBLIC_ → ブラウザに丸見え
// NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_xxx

// ✅ サーバーでだけ読む(無印)
const stripeSecret = process.env.STRIPE_SECRET_KEY; // サーバー専用

// ✅ クライアントで読む公開値だけ NEXT_PUBLIC_
const ga = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID; // 公開してよい

Mixing up Supabase's anon key and service_role key, and slipping a secret into NEXT_PUBLIC_, frequently appear in real vulnerability assessments (env-leak prevention, anon/service_role exposure). The defense is to doubt one level whether it truly needs to be read on the client.


Size limits and runtime differences

  • A total of 64KB / deployment (the sum of all variables; a single variable is also under 64KB). Large values like JWTs and certificates fit.
  • Supported runtimes (64KB): Node.js / Python / Ruby / Go / PHP community runtimes.
  • The Edge runtime is up to 5KB per variable. Watch out when reading large values in Edge middleware.

Local development: vercel env pull

Locally, put development variables in .env.local (or the .env that vercel env pull generates).

# Development 環境の変数をローカルへ同期
vercel env pull .env.local

# vercel dev なら自動で Development 変数をメモリに読み込む(pull不要)
vercel dev

Don't commit .env* (always put it in .gitignore). Deploys via the Vercel CLI auto-ignore .env files, but deploys via Git rely on .gitignore.

The @vercel/speed-insights / @vercel/analytics trap: put these in package.json's dependencies. Installing them globally and referencing them causes build errors, especially in a monorepo (observability).


System environment variables

Vercel injects useful system variables at runtime.

VariableContent
VERCEL_ENVproduction / preview / development
VERCEL_URLThis deployment's generated URL
VERCEL_GIT_COMMIT_SHAThe commit the deployment came from
VERCEL_REGIONThe execution region
// 環境で分岐(例:プレビューだけ noindex、本番だけ外部送信)
const isProd = process.env.VERCEL_ENV === "production";
if (!isProd) headers.set("X-Robots-Tag", "noindex");

OIDC keyless: don't place long-lived keys for external clouds

When accessing external resources like AWS S3/SES, GCP, or databases, placing a long-lived access key in an environment variable is discouraged in 2026. Obtain temporary credentials via OIDC (OpenID Connect) integration.

  • An external cloud's IAM role trusts Vercel's OIDC token, obtaining temporary permissions without storing a key.
  • Leakage risk structurally drops, and rotation operations vanish.
  • Make CI/CD (GitHub Actions → Vercel/AWS) OIDC-keyless too (OIDC keyless CI/CD).

If you absolutely need a long-lived key, narrow it to least privilege, place it only in Production, and rotate it regularly.


Handling secrets in Fluid Compute

Fluid Compute processes multiple requests concurrently on one instance.

// ✅ 起動時に一度だけ読む(リクエスト非依存)
const apiKey = process.env.EXTERNAL_API_KEY!;

// ❌ リクエスト固有のトークンをモジュールスコープにキャッシュしない
// let userToken; // 別リクエストに漏れる

Confine per-request tokens and user secrets to function-scope locals.


A type-safe env boundary (Zod)

Environment variables are "external input." Validate them at startup with Zod to fail on unset or malformed values at build/startup. This prevents "a 500 from unset env in production" before it happens.

// lib/env.ts — env を型安全な単一の真実源に
import { z } from "zod";

const schema = z.object({
  DATABASE_URL: z.string().url(),
  STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
  NEXT_PUBLIC_GA_MEASUREMENT_ID: z.string().optional(),
});

// 起動時に検証(失敗したら即座に落ちる=本番で気づくより安全)
export const env = schema.parse(process.env);

By not scattering process.env.X directly and going through env.X, the type, requiredness, and format are guaranteed in one place (thorough type safety).


Production checklist (env, secrets)

  • Organize values across the three environments (production/preview/development)
  • No secrets attached to NEXT_PUBLIC_ (browser exposure)
  • Redeploy after changes (not reflected in existing deployments)
  • Not committing .env*
  • Variables read in Edge are within 5KB, total within 64KB
  • External clouds via OIDC keyless, long-lived keys at least privilege + rotation
  • Don't leave request-specific secrets in Fluid's global
  • Validate env at startup with Zod (a type-safe boundary)

Summary

Secret management is an area that's "not flashy, but a single miss is fatal."

  1. Use the three environments, and reflect changes by redeploying
  2. Don't attach secrets to NEXT_PUBLIC_ (most important)
  3. Locally use vercel env pull, and don't commit .env
  4. Make external clouds OIDC keyless to eliminate long-lived keys
  5. Make env a type-safe boundary with Zod

I take on, as a project, auditing secret management, going OIDC-keyless, and introducing a type-safe env boundary.

This article is based on the Vercel environment variables official documentation (as of June 2026). Limits and specs are updated, so confirm the latest values officially at production adoption.

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

I can take on the implementation from this article as an engagement

Vercel apps, from design to production and cost optimization

Function design assuming Fluid Compute (safe global state, waitUntil, Cron), four-layer caching (ISR/CDN/Runtime Cache/Cache Components), safe deploys (preview/Promote/Instant Rollback/Rolling Releases), entry-point defense (Firewall/WAF/BotID), storage selection (Blob/Edge Config/Marketplace), and Active-CPU-billing-aware cost optimization. With experience running Next.js products on Vercel in production, I deliver fast, cheap, and secure.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading