Skip to main content
BlogWeb Dev

Variabel Environment dan Secrets di Next.js: Apa yang Salah dan Cara Memperbaikinya

Aturan untuk NEXT_PUBLIC_ vs variabel server-only, cara memvalidasi saat startup, dan pengaturan manajemen secrets yang saya gunakan di produksi.

Matthews Wong

Software Engineer

Juni 20259 menit baca
Web Dev

Kesalahan keamanan Next.js yang paling umum saya lihat dalam code review: pengembang memberi nama variabel NEXT_PUBLIC_DATABASE_URL dan bertanya-tanya mengapa kata sandi database mereka terlihat di tab Network browser. Prefiks NEXT_PUBLIC_ bukan hanya konvensi penamaan — ini mengontrol apakah variabel diinline ke dalam bundle JavaScript sisi klien pada waktu build dan disajikan kepada setiap pengguna yang mengunduh halaman.

Aturan NEXT_PUBLIC_

Next.js memiliki dua kategori variabel environment: server-only (tanpa prefiks) dan dapat diakses klien (prefiks NEXT_PUBLIC_). Variabel server-only tersedia di process.env di server — dalam API routes, server components, getServerSideProps, dan server actions. Mereka tidak pernah disertakan dalam bundle klien. Variabel NEXT_PUBLIC_ diinline ke dalam bundle JavaScript pada waktu build — mereka digantikan dengan nilai literal mereka dalam JavaScript yang dikompilasi. Jangan pernah memasukkan kredensial, kunci privat, atau secrets dalam variabel NEXT_PUBLIC_.

Apa yang Aman untuk Diekspos

ID analitik publik (GA4 measurement ID, Amplitude API key), URL CDN publik, feature flags yang aman untuk diekspos, environment aplikasi (development/staging/production), dan client ID OAuth publik aman sebagai variabel NEXT_PUBLIC_. String koneksi database, JWT secrets, kunci API privat, client secret OAuth, kunci enkripsi, dan secrets penandatanganan webhook harus menjadi variabel server-only.

Prioritas File Environment

Next.js memuat file environment dalam urutan prioritas ini (tertinggi dulu): 1) process.env (variabel environment yang sudah diatur dari OS atau platform deployment), 2) .env.local (selalu dimuat, tidak pernah di-commit ke git), 3) .env.{development|test|production} (spesifik lingkungan), 4) .env (file dasar, di-commit ke git untuk nilai non-secret). File .env.local harus selalu ada di .gitignore.

Next.js Environment Variable Security:
────────────────────────────────────────────────────────────
NEXT_PUBLIC_* variables          Server-only variables
────────────────────────         ──────────────────────────
Inlined into JS bundle at build  Only available in process.env
Visible to ALL users             Never sent to browser
✓ GA4 Measurement ID             ✓ DATABASE_URL
✓ Public CDN base URL            ✓ JWT_SECRET
✓ Feature flags (safe ones)      ✓ STRIPE_SECRET_KEY
✗ NEVER database credentials     ✓ SENDGRID_API_KEY
✗ NEVER private API keys         ✓ WEBHOOK_SIGNING_KEY

File priority (highest → lowest):
  process.env (OS/deployment platform)    [always wins]
  .env.local  (personal, never commit)    [local override]
  .env.production / .env.development      [environment-specific]
  .env        (committed, non-secrets)    [base defaults]

Example .env (safe to commit):
  NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
  NEXT_PUBLIC_APP_URL=https://matthewswong.com
  NODE_ENV=production

Example .env.local (NEVER commit — .gitignore):
  DATABASE_URL=postgresql://user:secret@localhost/mydb
  JWT_SECRET=my-local-dev-secret-change-in-prod

Dari pengalaman saya mengelola variabel environment di seluruh pengembangan, staging, dan produksi: validasi semua variabel environment yang diperlukan saat startup aplikasi, bukan saat runtime saat pertama kali digunakan. Saya menggunakan paket `t3-oss/env-nextjs` (atau skema Zod kustom) untuk memvalidasi dan mengetik variabel environment saat waktu build dan saat aplikasi dimulai. Jika variabel yang diperlukan hilang di produksi, aplikasi gagal segera dengan pesan error yang jelas.

Manajemen Secrets Produksi

Menyimpan secrets sebagai variabel environment adalah pendekatan yang tepat untuk sebagian besar aplikasi, tetapi dari mana variabel environment itu berasal penting. Untuk deployment Vercel, gunakan variabel environment terenkripsi Vercel — tersimpan terenkripsi, tersedia untuk build dan serverless functions, dan dapat dibatasi lingkupnya per environment. Untuk deployment self-hosted, pertimbangkan Doppler, HashiCorp Vault, atau AWS Secrets Manager.

Server Actions dan Kebocoran Secret

Next.js Server Actions berjalan di server tetapi dipanggil dari klien melalui permintaan POST ke endpoint server. Tubuh fungsi tidak diekspos ke klien — tetapi berhati-hatilah untuk tidak mengembalikan nilai secret dari Server Action. Jika Server Action mengembalikan `process.env.INTERNAL_API_KEY`, klien menerima nilai itu dalam respons. Terapkan prinsip 'pengungkapan minimum' yang sama ke nilai kembalian Server Action seperti respons API route.

// env.ts — type-safe environment variable validation
import { createEnv } from "@t3-oss/env-nextjs"
import { z } from "zod"

export const env = createEnv({
  // Server-only variables (never exposed to browser)
  server: {
    DATABASE_URL: z.string().url(),
    JWT_SECRET: z.string().min(32, "JWT secret must be 32+ chars"),
    SENDGRID_API_KEY: z.string().startsWith("SG."),
    NODE_ENV: z.enum(["development", "test", "production"]),
    REVALIDATION_SECRET: z.string().min(16),
  },
  // Client-accessible variables (safe to expose)
  client: {
    NEXT_PUBLIC_APP_URL: z.string().url(),
    NEXT_PUBLIC_GA_ID: z.string().regex(/^G-[A-Z0-9]+$/).optional(),
  },
  // Map process.env keys to validation schema
  runtimeEnv: {
    DATABASE_URL: process.env.DATABASE_URL,
    JWT_SECRET: process.env.JWT_SECRET,
    SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,
    NODE_ENV: process.env.NODE_ENV,
    REVALIDATION_SECRET: process.env.REVALIDATION_SECRET,
    NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
    NEXT_PUBLIC_GA_ID: process.env.NEXT_PUBLIC_GA_ID,
  },
})

// Usage — TypeScript catches wrong variable access at compile time:
import { env } from "@/env"

// In a server component or API route:
const db = createPool({ connectionString: env.DATABASE_URL }) // ✓ type-safe

// In a client component: TypeScript error if you access server vars!
const url = env.NEXT_PUBLIC_APP_URL // ✓ allowed in client
// const secret = env.JWT_SECRET    // ✗ TypeScript ERROR in client code

// .env.example (commit this — documents required vars)
DATABASE_URL=postgresql://user:pass@localhost/mydb
JWT_SECRET=your-32-char-minimum-secret-here
SENDGRID_API_KEY=SG.your-key-here
NEXT_PUBLIC_APP_URL=http://localhost:3000

Variabel Environment yang Type-Safe

TypeScript mengetik semua variabel `process.env` sebagai `string | undefined`. Dalam kode produksi, Anda akan sering menulis `process.env.DATABASE_URL!` untuk menegaskan variabel didefinisikan — yang gagal secara diam-diam jika variabel benar-benar hilang. Gunakan skema Zod untuk memvalidasi dan mengubah variabel environment saat startup, lalu ekspor objek yang divalidasi sebagai `env`.

Jika Anda secara tidak sengaja meng-commit file .env yang berisi kredensial nyata, menghapusnya di commit berikutnya tidak memperbaiki masalah — secret masih ada di riwayat git. Siapa pun yang meng-clone repositori dapat menemukan file yang dihapus. Perbaikan: rotasi kredensial yang diekspos segera (batalkan yang lama, buat yang baru), kemudian gunakan BFG Repo Cleaner atau `git filter-repo` untuk menulis ulang riwayat.

Strategi Multi-Environment

Untuk proyek dengan pengembangan lokal, staging, dan environment produksi, saya menggunakan: .env (di-commit, default non-secret — URL dasar, feature flags, ID publik), .env.local (tidak di-commit, kredensial pribadi pengembang untuk pengembangan lokal), variabel environment Vercel (terenkripsi, per-environment: development, preview, production).

Mengaudit Variabel Environment Anda

Jalankan audit berkala dari variabel environment Anda untuk menangkap: variabel NEXT_PUBLIC_ yang berisi data sensitif, variabel yang tidak digunakan yang mengacaukan konfigurasi, variabel yang hilang yang dapat menyebabkan kegagalan runtime, dan variabel duplikat di seluruh environment yang seharusnya berbeda. Saya menjalankan `grep -r 'NEXT_PUBLIC_' src/ --include='*.ts' --include='*.tsx'` untuk menemukan semua penggunaan variabel publik dan memverifikasi setiap satu secara manual.

Sumber & Bacaan Lebih Lanjut

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa perbedaan antara variabel NEXT_PUBLIC_ dan variabel server-only di Next.js?

Variabel server-only (tanpa prefiks) hanya tersedia di process.env pada server — dalam API routes, server components, getServerSideProps, dan server actions — dan tidak pernah disertakan dalam bundle klien. Variabel NEXT_PUBLIC_ diinline ke dalam bundle JavaScript pada waktu build dan bisa dibaca langsung dari sumber JS oleh siapa pun yang mengunduh halaman. Jangan pernah memasukkan kredensial, kunci privat, atau secrets dalam variabel NEXT_PUBLIC_.

Bagaimana cara mencegah variabel environment yang hilang menyebabkan kegagalan diam-diam di produksi?

Validasi semua variabel environment yang diperlukan saat startup aplikasi, bukan menunggu sampai code path yang membutuhkannya pertama kali dipanggil. Post ini merekomendasikan penggunaan paket t3-oss/env-nextjs atau skema Zod kustom untuk memvalidasi dan mengetik variabel saat waktu build dan saat aplikasi dimulai. Jika variabel yang diperlukan hilang, aplikasi langsung gagal dengan pesan error yang jelas, bukan gagal diam-diam beberapa jam kemudian.

Jika saya tidak sengaja meng-commit file .env dengan kredensial nyata, apakah menghapusnya di commit berikutnya sudah cukup?

Tidak — menghapus file di commit berikutnya tidak menghapusnya dari riwayat git. Siapa pun yang meng-clone repositori masih bisa memulihkan file tersebut menggunakan git log dan git show. Perbaikan yang benar adalah merotasi kredensial yang terekspos segera (batalkan yang lama dan buat yang baru), lalu gunakan BFG Repo Cleaner atau git filter-repo untuk menulis ulang riwayat dan menghapus file dari semua commit.

Apakah Next.js Server Action bisa bocorkan secret ke klien?

Ya. Server Actions berjalan di server tetapi dipanggil dari klien melalui permintaan POST, dan apa pun yang dikembalikan fungsi tersebut dikirim kembali dalam respons. Jika Server Action mengembalikan nilai seperti process.env.INTERNAL_API_KEY, klien menerima secret itu dalam payload respons. Prinsip pengungkapan minimum yang berlaku untuk respons API route harus diterapkan juga pada nilai kembalian Server Action: hanya kembalikan apa yang benar-benar dibutuhkan klien.

Apa urutan prioritas file environment Next.js yang direkomendasikan, dan mana yang tidak boleh di-commit ke git?

Next.js memuat file environment dalam urutan prioritas dari tertinggi ke terendah: process.env (variabel yang diatur OS atau platform), .env.local, file spesifik environment (.env.development, .env.production, dll.), dan .env. File .env.local harus selalu ada di .gitignore dan tidak pernah di-commit — file ini menyimpan kredensial lokal pengembang yang menggantikan default .env yang di-commit.