Du ødelægger dine miljøvariabler allerede i dev
Byg kører fint, du klikker “Deploy”, og fem minutter senere stirrer du på en hvid skærm og en fejl i konsollen: Cannot read properties of undefined (reading 'API_URL'). Velkommen til miljøvariabler.
Jeg har mistet mere tid på forsvundne process.env værdier end på nogen anden type bug i små projekter. Det gode er, at når du først har en mental model, falder 80% af fejlene i samme få kategorier.
Miljøvariabler er bare konfiguration, ikke magi
Start med at aflaste hjernen: en miljøvariabel er bare tekst, der kommer udefra. Ikke en særlig datatype. Ikke en “hemmeligheds-boks”. Bare tekst.
I Node ligger de typisk på process.env. I Vite-projekter ender de som import.meta.env.VITE_... i din frontend. Fælles for dem er, at de ikke defineres i koden, men i det miljø koden kører i.
Det lyder simpelt, men for at bruge dem rigtigt skal du skille tre ting ad:
- hvor værdien er defineret (OS, hosting panel, .env-fil)
- hvornår værdien læses (under build eller når koden rent faktisk kører)
- om koden kører i browseren eller på serveren
Hvis de tre ikke matcher det, du tror, får du klassikeren: “men det virker jo lokalt”. Jeg har skrevet om det generelle deploy-kaos i en anden artikel på Coding Class: Hvis det kun virker på din maskine, virker det ikke. Her holder vi fokus på miljøvariabler.
Miljøvariabel vs. secret vs. almindelig config
Der går tit rod i begreberne, så lad mig skære det ud.
Miljøvariabel: En nøgle-værdi, som miljøet stiller til rådighed. F.eks. PORT=3000 eller NODE_ENV=production. Teknik, ikke sikkerhed i sig selv.
Secret: En følsom værdi, som ikke må ende i frontend eller logs. F.eks. database password, API-nøgler til Stripe, JWT-secrets. De lever ofte som miljøvariabler, men det gør dem ikke automatisk sikre.
Almindelig config: Ting der godt må være offentlige: feature flags, API-base-url til din egen backend, app-navn osv. De kan også ligge som miljøvariabler, men det er mere for fleksibilitet end for sikkerhed.
Pointen: miljøvariabler er bare en måde at give konfiguration til din app. Sikkerhed handler om hvor værdierne ender, ikke hvordan de startede.
Byggetid vs køretid – den forskel der dræber dine Vite-projekter
Den hyppigste fejl jeg ser: man forventer at kunne ændre en env-var i hosting panelet og så ændrer frontend-adfærden sig magisk uden et nyt build. Det gør den ofte ikke.
Forestil dig to tidspunkter i dit projekt:
Byggetid (build time): Når du kører npm run build. Koden pakkes, bundtes, minificeres. For Vite betyder det, at alle import.meta.env.VITE_... læses og deres værdier bliver hardcodet ind i JavaScript-filerne.
Køretid (runtime): Når brugeren åbner siden, eller din Node-server processer et request. Her kan koden læse det, der ligger i miljøet lige nu.
Frontend-konfig: fastfrosset ved build
Hvis du har noget som:
// src/config.ts
export const API_URL = import.meta.env.VITE_API_URL
så sker der det her under build:
- Vite læser
VITE_API_URLfra dit miljø/.env-filer - indsætter værdien direkte i den bundtede fil, f.eks.
"https://api.example.com" - den færdige
.js-fil indeholder ikke længere nogen reference tilimport.meta.env
Derfor: ændrer du env-vars i Vercel eller Netlify efter build, ændrer du ikke den allerede deployede frontend. Du skal bygge igen.
Backend-konfig: læses ved startup
I en simpel Node-backend:
// server.ts
import http from "http"
const PORT = process.env.PORT || 3000
http.createServer((req, res) => {
res.end("Hello")
}).listen(PORT)
her læses process.env.PORT, når processen starter. Hvis hosting-platformen ændrer miljøvariablen og genstarter processen (eller du selv gør), får du den nye værdi uden nyt build.
Så din mentale model kan være:
Frontend (Vite, React osv.): miljøvariabler er i praksis build-konstant. Backend (Node, Express osv.): miljøvariabler er runtime-konfiguration (læses ved opstart).
Det er derfor “undefined i production” på frontend ofte handler om manglende VITE_-prefix eller en env-var, der ikke var sat på det tidspunkt, hvor buildet kørte.
Frontend må aldrig kende dine secrets
Nu til den ubehagelige sandhed: alt, hvad du sender til browseren, er offentligt. Uanset om det kom fra en .env-fil, en fancy “secret manager” eller blev hardcodet i koden.
Det gælder også miljøvariabler via Vite. Der er én simpel regel:
Hvis værdien lander i bundlet JavaScript, er den ikke hemmelig.
Det kan godt være, du ikke viser den direkte i UI, men brugeren kan altid åbne devtools, søge i kildekoden eller kigge på network requests.
Hvad må så gerne i frontend-env?
Jeg plejer at bruge denne tommelfingerregel:
- URL’er til din egen backend (f.eks.
VITE_API_BASE_URL) - feature flags der styrer UI (f.eks.
VITE_ENABLE_EXPERIMENTAL_UI) - offentlige nøgler, der er designet til at være offentlige (f.eks. Stripe public key)
Alt andet, der har noget med penge, adgang eller signering at gøre, bør bo i backend. Frontenden kalder din backend, og backenden snakker med 3. parts API’er med rigtige secrets.
Hvis du er i tvivl, så antag at det ikke må ligge i frontend. Din fremtidige jeg vil være taknemmelig.
.env-filer lokalt – det stille rod der bryder prod
Lokalt vil du næsten altid ende med en eller flere .env-filer. De er praktiske, men de er også en skjult kilde til “det virker hos mig”-bugs.
Et simpelt setup til et Vite + Node-projekt kunne være:
.env.localtil ting, der kun gælder dig (lokale passwords, porte osv.).enveller.env.developmenttil delt udviklingskonfiguration.env.examplesom “skabelon” uden rigtige secrets
.env og venner skal næsten altid i din .gitignore. Det gælder især filer med rigtige secrets. Det du til gengæld gerne vil commite, er .env.example:
# .env.example
VITE_API_BASE_URL=
DATABASE_URL=
JWT_SECRET=
Så kan en ny udvikler på projektet bare:
cp .env.example .env.local
# og så udfylde værdierne
Typisk fejl jeg selv har lavet mere end én gang: jeg opdaterer en env-var lokalt, men glemmer at opdatere .env.example. Næste person på projektet får gamle keys, og vi fejlsøger et problem, der kun eksisterer på deres maskine.
Hvis du vil se et lidt større eksempel på config-struktur, har jeg en artikel om monorepos hvor env-vars også spiller en rolle: Byg et lille monorepo uden at drukne i tooling.
NODE_ENV – hvad betyder den egentlig?
NODE_ENV er en af de mest misforståede env-vars. Mange pakker kigger på den for at slå debug-funktioner til eller fra.
Typisk bruger man:
NODE_ENV=developmentnår du kører lokalt med hot reloadNODE_ENV=productionnår du kører den rigtige deploy
I Node bruges den ofte til at styre logging, cache osv. I bundlere bruges den også til at fjerne dødkode:
if (process.env.NODE_ENV === "development") {
console.log("Extra debug")
}
Ved build med NODE_ENV=production kan bundleren ofte se, at betingelsen aldrig er sand og fjerne hele blokken.
Pointen: du skal ikke overbelaste NODE_ENV. Brug den til “er vi i prod eller ej”, og lav dine egne env-vars til alt andet.
Vite og VITE_-prefixet der redder dig fra at lække secrets
Hvis du har prøvet at skrive import.meta.env.API_URL og få undefined, så har du allerede mærket Vites sikkerhedsdesign.
Vite eksponerer kun miljøvariabler til frontend, hvis de starter med VITE_. Resten er tilgængelige i build-processen, men lander ikke i browser-koden.
Sådan læser du env-vars i Vite
Et simpelt eksempel:
// .env
VITE_API_BASE_URL="http://localhost:3000"
SECRET_API_KEY="abc123" # kommer IKKE ud i frontend
// src/api.ts
const baseUrl = import.meta.env.VITE_API_BASE_URL
export async function fetchUsers() {
const res = await fetch(`${baseUrl}/users`)
return res.json()
}
Her får du kun VITE_API_BASE_URL i frontend. SECRET_API_KEY vil ikke være tilgængelig som import.meta.env.SECRET_API_KEY. Den er stadig til stede i build-processens miljø, men Vite lader den ikke slippe ud.
Typisk begynderfejl: du har sat API_URL i Vercel, men i koden skriver du import.meta.env.VITE_API_URL. Resultat: undefined. Løsning: giv variablen et VITE_-prefix både lokalt og i hosting-panelet.
Modes og .env-filer i Vite
Vite har “modes” som styrer hvilke env-filer der bruges. Groft sagt:
.envbruges altid.env.developmentbruges når du kørervite/npm run dev.env.productionbruges når du kørervite build
Vercel og Netlify sætter typisk NODE_ENV=production under build, så dine .env.production-værdier er relevante, hvis du bruger dem. Mange vælger dog at lade hosting-panelet styre alt og kun bruge env-filer lokalt.
Du kan finde flere detaljer i Vites egen dokumentation, som faktisk er ret læsbar: Vite env og modes.
Node-backend: brug process.env tidligt og højt
I backend-land bliver tingene både simplere og mere farlige. Simpler, fordi du kun har én kontekst: Node. Farligere, fordi du typisk håndterer rigtige secrets.
Mit bedste råd: læs og valider alle env-vars ved startup. Ikke nede i tilfældige services.
// config.ts
type Config = {
port: number
databaseUrl: string
jwtSecret: string
}
export function loadConfig(): Config {
const { PORT, DATABASE_URL, JWT_SECRET } = process.env
if (!DATABASE_URL) throw new Error("DATABASE_URL is required")
if (!JWT_SECRET) throw new Error("JWT_SECRET is required")
return {
port: PORT ? Number(PORT) : 3000,
databaseUrl: DATABASE_URL,
jwtSecret: JWT_SECRET,
}
}
// server.ts
import { loadConfig } from "./config"
const config = loadConfig()
console.log("Starting server on", config.port)
Fordelen er, at hvis du glemmer at sætte en env-var i produktion, fejler appen ved opstart i stedet for halvvejs inde i et request. Det er nemmere at opdage og logs er mere tydelige.
Der findes også biblioteker til det her (f.eks. zod kombineret med process.env), men mønstret er det samme: valider én gang, tidligt.
Vercel og Netlify – hvorfor er min env undefined i production?
Hvis du hoster på Vercel eller Netlify, er miljøvariabler en UI-ting i deres dashboard. Det gør det nemt at ændre, men det skjuler også, hvad der egentlig sker.
Vercel: build-env vs runtime-env
På Vercel har du tre miljøer: Development, Preview og Production. Hvert miljø kan have sine egne env-vars.
For et Vite-projekt gør Vercel i grove træk:
- starter en build-job med de env-vars du har sat for det miljø
- kører
npm run build(eller hvad du har angivet) - hoster den genererede frontend som statiske filer
Hvis du ændrer en env-var i Vercel og ikke trigger et nyt build, ser din bundtede frontend de gamle værdier.
Typiske fejlkilder:
- env-variablen er kun sat i “Development”, men dit deploy kører i “Production”
- du har glemt
VITE_-prefixet - du har sat env-var efter sidste build
Til Node backends på Vercel (Serverless Functions) læses process.env ved køretid for hver function execution, men værdierne kommer fra samme sted i dashboardet.
Dokumentationen er værd at skimpe: Vercel environment variables.
Netlify: samme idé, lidt anden UX
Netlify har også environment variables per site. De bruges både til build og til eventuelle serverless functions.
Flowet ligner Vercel meget:
- du sætter env-vars i Site settings
- de er tilgængelige når Netlify bygger din frontend
- de er også tilgængelige som runtime-vars i functions
Hvis din Vite-app ser undefined, er opskriften den samme: tjek navn, prefix og at du faktisk har lavet et nyt deploy efter at have ændret værdierne.
Netlifys dokumentation om emnet: Netlify environment variables.
Fejlsøgning når alt bare hedder undefined
Et simpelt mønster du kan følge, når din env-var forsvinder på vej til prod:
- Log udvalgte env-vars i build-loggen (uden secrets). F.eks.
console.log('VITE_API_BASE_URL', import.meta.env.VITE_API_BASE_URL)i en lille build-only fil. - Tjek i dashboardet at variablen er sat i det rigtige miljø (Production vs Preview).
- Søg i den deployede bundle (via devtools “Search in all files”) efter værdien for at se, om den overhovedet er kommet med.
Det føles lidt gammeldags at søge i bundlet JS, men det er ofte den hurtigste måde at bekræfte, om problemet er build eller runtime.
En lille env-tjekliste der redder dig fra de værste fejl
Hvis jeg skulle koge det ned til få vaner, ville det være de her.
1. Skeln frontend og backend skarpt
Beslut eksplicit hvad der er frontend-config, og hvad der er backend-config. Alt privat og følsomt ryger i backend. Frontenden får kun de værdier, den skal bruge, og alle med VITE_-prefix.
2. Valider backend-env ved startup
Lav en lille config.ts som tjekker process.env og fejler højt og tydeligt, hvis noget mangler. Ingen stille undefined, der først eksploderer senere.
3. Brug .env.example som kontrakt
Hold .env.example opdateret. Hver gang du tilføjer en ny env-var, tilføjer du den der. Tænk på filen som dokumentation for, hvad appen forventer af sit miljø.
4. Husk build vs runtime i dine deploys
Når du ændrer env-vars på Vercel/Netlify, skal du tænke: skal der et nyt build til? Hvis det handler om frontend, er svaret næsten altid ja. Hvis det “kun” er backend (functions), kan en genstart være nok.
5. Log klogt i prod uden at lække noget
Du behøver ikke logge hele process.env i produktion (gør det ikke), men du kan logge f.eks. hvilken base-url der er valgt, eller om et feature flag er slået til. Hellere en bekræftelse i loggen end gætteri.
Hvis du vil bygge videre på de her vaner, er 12-factor app-princippet om config et fint, overskueligt sted at kigge: 12factor.net/config. Det er ikke raketvidenskab, bare lidt disciplin.
Jeg er ret sikker på, miljøvariabler bliver ved med at overraske nye udviklere de næste mange år. Spørgsmålet er mest, hvor hurtigt vi lærer at bruge dem som et bevidst værktøj i stedet for en magisk fejl-generator.







Send kommentar
Du skal være logget ind for at skrive en kommentar.