Miljövariabler i Next.js
Hur Next.js laddar .env-filer, NEXT_PUBLIC_-prefixet, validering med Zod och hur du hanterar secrets i Vercel och CI/CD.
Miljövariabler i Next.js är enkla att komma igång med men har ett par fallgropar som biter hårt i produktionsmiljö: variabler som läcker ut till klienten när de inte borde, saknade värden som tyst returnerar undefined istället för att krascha tidigt, och hemligheter som av misstag hamnar i git-historiken. Den här guiden täcker laddningsordning, säkerhetsgränsen mellan server och klient, validering vid uppstart, och hur du hanterar secrets i Vercel och GitHub Actions.
.env-filer och laddningsordning
Next.js stöder fyra .env-filer med olika räckvidd. Förstå skillnaden mellan dem så undviker du konstiga beteenden där ett värde lokalt skiljer sig från produktionsmiljön.
| Fil | Laddas när | Ska committas? |
|---|---|---|
.env | Alltid, alla miljöer | Ja (bara icke-känsliga värden) |
.env.local | Alltid, men ej under tester | Nej, alltid gitignorerad |
.env.development | Enbart next dev | Ja om lämpligt |
.env.production | Enbart next build / next start | Ja om lämpligt |
Prioritetsordningen, från högst till lägst, är: .env.local tar precedens, sedan den miljöspecifika filen (.env.development eller .env.production), och sist faller allt tillbaka på .env.
Ett praktiskt upplägg ser ut så här:
# .env (committas till repot)
NEXT_PUBLIC_APP_NAME="Min App"
API_URL="https://api.produktion.se"
# .env.local (gitignorerad - lokala overrides)
API_URL="http://localhost:4000"
Lokalt pekar API_URL mot din lokala server. I produktion används värdet från .env utan att du behöver göra något. Inget manuellt byte, inga villkor i koden.
En sak att hålla koll på: .env.local laddas inte under next test. Behöver du testrelaterade overrides skapar du en separat .env.test-fil.
NEXTPUBLIC-prefixet
Det här prefixet styr var en variabel är tillgänglig: i webbläsaren eller enbart på servern. Regeln är okomplicerad men bryter man mot den händer det obehagliga saker.
Variabler med NEXT_PUBLIC_-prefix bunteras in i det klientsidiga JavaScript-paketet och blir synliga i webbläsaren. Variabler utan prefixet är serverexklusiva: de finns i Server Components, API-rutter och Server Actions, men är undefined i Client Components.
// Server Component - båda variablerna är tillgängliga
const apiKey = process.env.STRIPE_SECRET_KEY; // OK
const appName = process.env.NEXT_PUBLIC_APP_NAME; // OK
// Client Component - bara NEXT_PUBLIC_ är tillgänglig
const apiKey = process.env.STRIPE_SECRET_KEY; // undefined - läcker INTE ut men fungerar inte
const appName = process.env.NEXT_PUBLIC_APP_NAME; // OK
Lägg märke till kommentaren: STRIPE_SECRET_KEY läcker inte ut till klienten bara för att du försöker läsa den i en Client Component. Next.js skyddar dig där. Men variabeln är undefined och koden kraschar tyst eller ger oväntade fel.
Tumregeln: allt som aldrig får lämna servern, API-nycklar, databasanslutningssträngar, signeringshemligheter, ska sakna NEXT_PUBLIC_-prefix. Lägg bara saker i NEXT_PUBLIC_ som du vore bekväm med att se inbäddade i HTML-källkoden för sidan. Ingen känslig information alltså.
Validering med Zod
Det vanligaste felet med miljövariabler är inte att de läcker, det är att de saknas i produktion och att applikationen misslyckas tyst en timme senare på en svårspårad plats. Lösningen är att validera alla nödvändiga variabler vid uppstart, innan applikationen börjar hantera trafik.
Installera Zod om det inte redan finns:
npm install zod
Skapa sedan en dedikerad modul för miljökonfiguration:
// lib/env.js
import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url("DATABASE_URL måste vara en giltig URL"),
STRIPE_SECRET_KEY: z.string().min(1, "STRIPE_SECRET_KEY saknas"),
NEXT_PUBLIC_APP_URL: z
.string()
.url("NEXT_PUBLIC_APP_URL måste vara en giltig URL"),
});
export const env = envSchema.parse(process.env);
Importera env från den här modulen istället för att läsa process.env direkt i resten av koden:
import { env } from "@/lib/env";
// Istället för process.env.DATABASE_URL
const db = createConnection(env.DATABASE_URL);
Om en variabel saknas eller har fel format kastar parse() ett tydligt fel direkt vid uppstart med en beskrivning av exakt vad som är fel. Inte fem requests senare med ett kryptiskt meddelande.
Vill du att applikationen ska starta ändå och logga felen utan att krascha, använd safeParse():
const resultat = envSchema.safeParse(process.env);
if (!resultat.success) {
console.error("Miljövariabler saknas eller är ogiltiga:");
console.error(resultat.error.flatten().fieldErrors);
}
safeParse() kastar aldrig, den returnerar ett objekt med success: true och data, eller success: false och error. Praktiskt i testmiljöer där du inte vill att en saknad produktionsvariabel ska avbryta hela testsviten.
Vercel environment UI
Vercel delar in miljövariabler i tre scopes som matchar dina deployment-miljöer:
- Production: gäller för deploys från din huvudgren
- Preview: gäller för alla branch-preview-deploys, perfekt för staging
- Development: synkas ner lokalt via Vercel CLI
Varje variabel kan ha olika värden per scope. DATABASE_URL kan peka på produktionsdatabasen i Production-scope och en staging-databas i Preview-scope, utan att du behöver hantera det manuellt i koden.
Att synka Development-variabler till din lokala maskin görs med ett kommando:
vercel env pull .env.local
Det skriver Vercels Development-variabler till din .env.local, utan manuell copy-paste. Användbart när du onboardar nya teammedlemmar eller när någon lagt till en ny variabel i Vercel och alla behöver uppdatera lokalt.
Kom ihåg att .env.local är gitignorerad, så varje utvecklare behöver köra kommandot på sin maskin.
Staging-miljöer
Det finns två vanliga upplägg för staging i Next.js-projekt med Vercel.
Vercel Preview som staging: Varje branch-push skapar automatiskt en unik preview-URL. Preview-scope i Vercel kan konfigureras med staging-specifika variabler, exempelvis en staging-databas. Det kräver noll extra konfiguration och kostar inget utöver det du redan betalar. Fungerar bra för mindre team eller projekt där preview-URL:er är tillräckliga.
Separat Vercel-projekt: Skapa ett andra Vercel-projekt som pekar på samma repository men med en staging-gren som produktionsgren. Det ger helt isolerade miljövariabler och en tydlig, namngiven stagingmiljö som speglar produktion. Bättre för team som vill ha en stabil, alltid tillgänglig stagingmiljö som inte beror på att en specifik branch är pushad.
Vilket upplägg som passar beror på teamets storlek och hur strikt ni separerar testning från produktion. För de flesta projekt räcker Preview-upplägget långt.
Secrets i CI/CD
När GitHub Actions kör byggen eller tester som behöver miljövariabler, lägger du till dem som Repository Secrets: Settings → Secrets and variables → Actions → New repository secret.
Referera till dem i workflow-filen:
# .github/workflows/deploy.yml
- name: Build
run: npm run build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
GitHub krypterar secrets och maskerar dem i loggar. De syns aldrig i klartext i workflow-output, även om du av misstag loggar dem.
En regel som inte förhandlas: .env.local ska alltid ligga i .gitignore. Committa aldrig någon fil som innehåller riktiga secrets till repot. Om du råkar göra det, rotera hemligheten omedelbart, git-historiken är permanent och publik även efter att du tar bort filen. En force-push raderar inte historiken från forks eller caches som redan klonat repot.
Lägg till .env*.local i .gitignore om det inte redan finns:
# .gitignore
.env*.local
Next.js genererar det här automatiskt i nya projekt, men i äldre projektupplägg kan det saknas.
