Hvis det kun virker på din maskine, virker det ikke
Øjeblikket hvor alt knækker efter deploy
Jeg kan huske første gang jeg deployede et lille React-projekt til Vercel og bare blev mødt af en hvid skærm. Lokalt kørte alt fint. I produktion: ingenting. Ingen fejl i UI’et, bare tomhed.
Hvis du sidder med et projekt der virker lokalt men ikke i produktion, så er du ikke alene. Det sker for alle, og det sker mange gange. Forskellen er bare, om du famler rundt, eller om du har en fast måde at fejlsøge på.
Stop, kig, saml beviser før du roder i koden
Det mest naturlige er at begynde at ændre i koden, kommentere ting ud, pushe på ny, håbe. Det er også den langsomste måde at løse det på.
Jeg plejer at gøre tre ting, inden jeg rører en eneste linje kode:
1. Åbn devtools i produktion, ikke kun lokalt
Gå ind på den hostede version af siden og åbn browserens udviklerværktøjer (typisk F12). Kig især på to faner: Console og Network.
I Console leder du efter fejlmeddelelser. Røde linjer. Det kan være noget i stil med:
Uncaught TypeError: cannot read properties of undefined
Mixed Content: The page at 'https://...' was loaded over HTTPS, but requested an insecure resource 'http://...'
Access to fetch at 'https://api.example.com' from origin 'https://ditdomæne.dk' has been blocked by CORS policy
I Network kan du opdatere siden og se, hvilke requests der fejler. Kig efter røde 4xx og 5xx statuskoder, eller noget der står som (blocked).
Allerede her kan du ofte se, om det handler om CORS, mixed content, 404 på en fil, eller om JavaScript bundlen slet ikke loader.
2. Sammenlign build output lokalt og i CI
Hvis du bruger et bundler-setup (Vite, Create React App, Next, etc.), så kør et build lokalt:
npm run build
Eller hvad din build-kommando nu er. Tjek om den fejler lokalt. Mange gange opdager man, at builden faktisk allerede klager, men dev-serveren skjuler det ret godt.
Gå bagefter ind på dit deploy-dashboard (Vercel, Netlify, Render, hvad du nu bruger) og åbn build-logs for det sidste deploy. Kig efter fejl eller advarsler du ikke får lokalt. Det kan være alt fra forkert Node-version til manglende environment variables.
3. Bevis at fejlen kun findes i produktion
Nogle gange tror vi fejlen kun findes i produktion, fordi vi ikke har prøvet de samme trin lokalt. Prøv at genskabe præcis samme flow lokalt: samme URL, samme input, samme data.
Hvis du har en backend, så test gerne med samme database-type lokalt og i produktion. F.eks. SQLite lokalt men Postgres i produktion kan give sjove forskelle.
Når du har gjort de tre ting, har du typisk nok spor til at placere fejlen i én af de klassiske kategorier. Nu kan vi gå systematisk igennem dem.
Build, runtime og alt det der virker indtil du skifter miljø
En af de mest klassiske: koden kører fint med npm run dev, men builden til produktion opfører sig helt anderledes.
Node-version, build-kommando og output
Check for det første at du bruger samme Node-version lokalt og i din hosting. Mange platforme viser Node-versionen i logs. Hvis du ser noget som:
Node.js version: 14.x
mens du selv bruger Node 20 lokalt, kan det forklare mærkelige fejl. De fleste platforme har en måde at sætte Node-version via en config-fil eller via engines i package.json.
Næste ting er build-kommandoen. Er den sat rigtigt i din deploy-konfiguration? F.eks. på Vercel skal du sikre at den bruger det samme som du selv gør lokalt:
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
Hvis din platform kører npm run build, men du i praksis kun har en build:prod-kommando, så står du med en deploy som “virker” på papiret, men aldrig laver de rigtige filer.
Til sidst skal output dir matche. Hvis Vite bygger til dist, men din hosting forventer build, så serverer den bare en tom mappe. Det føles som magi, men det er bare konfig.
Environment variables der forsvinder på vej til Vercel og venner
Hvis du har noget der minder om process.env.MIT_API_URL eller import.meta.env.VITE_API_URL i din kode, og det kun fejler i produktion, så er environment variables en god mistænkt.
Jeg har skrevet en separat artikel om env vars på Coding Class (den her er værd at læse, hvis du er helt ny til det), men her er de vigtigste produktionsting.
Er variablen overhovedet sat i produktion?
På Vercel skal du manuelt tilføje alle env vars i projektets settings. En .env-fil på din maskine følger ikke automatisk med op.
Typisk mønster:
- Det virker lokalt, fordi du har en
.env. - I produktion er
process.env.MIT_API_URLundefined. - Koden crasher, eller din fetch kalder
undefined/users.
Tjek i dine build-logs: nogle gange står der en advarsel om, at en env var ikke findes. Ellers kan du midlertidigt logge den i build-step (aldrig secrets, kun ikke-følsomme ting) eller lave en lille testkomponent der viser værdien i UI’et, så du kan se, hvad der faktisk ender i bundlen.
Frontend-build vs. runtime env
I bundlere som Vite og CRA bliver environment variables “bagt ind” ved build. Det betyder, at hvis du ændrer en env var i din hosting, uden at lave et nyt build, så ser din frontend stadig den gamle værdi.
Det kan føles som cache-problem, men det er ofte bare et manglende rebuild. Løsning: trigger et nyt deploy efter at du ændrer env vars.
Har du en backend (Node/Express, Django, etc.), så læses env vars typisk ved opstart. Her skal du genstarte serveren, før ændringerne slår igennem.
API-URL, CORS og “det virker jo lokalt”
Hvis du har et frontend-projekt som kalder et API, er det ekstremt normalt at det virker lokalt, men fejler i produktion. To klassiske årsager er base URL og CORS.
Base URL der stadig peger på localhost
Forestil dig at du har noget i den stil her:
const API_URL = 'http://localhost:3000/api';
fetch(`${API_URL}/users`)
.then(res => res.json())
.then(data => console.log(data));
Lokalt er det fint. I produktion prøver din browser nu at kalde http://localhost:3000/api/users fra et domæne som https://dit-site.vercel.app. Det virker selvfølgelig ikke.
Løsningerne er typisk:
- Brug environment variables til at skifte base URL mellem dev og prod.
- Eller brug relative URLs, hvis frontend og backend hostes samme sted, f.eks.
/api/users.
CORS der kun slår til i produktion
CORS er browserens måde at beskytte brugeren mod at websider kan kalde hvad som helst på tværs af domæner. Lokalt ligger din frontend ofte på http://localhost:5173 og backend på http://localhost:3000, og du har måske slået CORS rimelig åbent til.
I produktion ændrer origin. Din frontend ligger pludselig på https://mitdomæne.dk, men din backend accepterer kun http://localhost:5173. Resultatet er typisk en fejl ala:
Access to fetch at 'https://api.example.com' from origin 'https://mitdomæne.dk' has been blocked by CORS policy
Her skal du ind i CORS-konfigurationen på din backend og tillade den rigtige origin. MDN har en god oversigt over CORS-fejl, som er til at læse uden at rive håret ud: se den her.
Og nej, Access-Control-Allow-Origin: * er ikke altid en god idé, selvom det “løser” fejlen. Tænk over, hvilke domæner der faktisk skal have adgang.
HTTPS, mixed content og den skjulte blokering
En anden forskel på lokal udvikling og produktion er HTTPS. Mange hostings kører automatisk HTTPS i produktion, mens du lokalt ofte kører HTTP.
Hvis din side loader over https://, men dine scripts, billeder eller API-kald bruger http://, så kan browseren blokere dem som “mixed content”. Du ser det typisk som advarsler eller fejl i konsollen.
Eksempel:
Mixed Content: The page at 'https://mitdomæne.dk' was loaded over HTTPS,
but requested an insecure resource 'http://api.mitdomæne.dk/data'.
This request has been blocked.
Løsningen er at få alt til at køre HTTPS, også dit API. Eller som minimum bruge https:// i dine URLs, hvis det allerede er sat op. Nogle gange virker det lokalt fordi browseren er mere afslappet med HTTP til HTTP.
SPA-routing, 404’er og siden der kun virker ved første load
Hvis du bygger en Single Page Application (SPA) med client side routing, kan du løbe ind i det her mønster:
- Skriver du
https://mitdomæne.dki browseren, virker det fint. - Trykker du ind på
/dashboardvia et link, virker det også fint. - Opdaterer du på
/dashboard, får du en 404 fra serveren.
Det er ikke fordi din app er gået i stykker. Det er fordi serveren prøver at tolke /dashboard som en rigtig fil eller route. I en klassisk SPA skal serveren egentlig bare give index.html tilbage på alle ruter, og så lader vi JavaScript håndtere resten.
På Netlify ordner man det ofte med en _redirects-fil der siger:
/* /index.html 200
På Vercel bruger man en config til at route alt til index.html, hvis det er et static SPA-setup. Hvis du bruger Next.js med file based routing, er mønstret lidt anderledes, men problemet føles ofte det samme: det virkede lokalt, hvorfor 404 nu?
Coding Class har også en god intro til forskellen på SPA og MPA, hvis du gerne vil have routing-delen lidt mere på plads: den ligger på forsiden blandt artiklerne.
Caching, gamle filer og “jeg har deployet, men ser stadig det gamle”
Det her er nok det mest irriterende, fordi det føles uforklarligt: du har lige deployet en rettelse, men i din browser ser du stadig den gamle fejl.
Der er flere lag, hvor ting kan cache:
- Din egen browser.
- CDN’en eller hostingen (Netlify, Vercel osv.).
- Service workers, hvis du har en PWA eller noget auto-genereret offline-support.
Start med det simple: hard refresh (Ctrl+Shift+R eller Cmd+Shift+R). Hvis det løser det, var det bare browser-cache.
Hvis du bruger et moderne bundler-setup, tilføjer den typisk fil-hash til dine bundler, f.eks. main.8f4a9c.js. Det gør cache meget nemmere at styre, fordi filnavnet ændrer sig når indholdet gør. Men HTML-filen kan stadig caches.
Nogle hosting-udbydere har en “clear cache” knap i dashboardet. Hvis du bliver ved med at se gamle versioner, så prøv den. Og hvis du roder med service workers, så vær opmærksom: en gammel service worker kan blive ved med at servere gamle assets, indtil du opdaterer eller sletter den.
Secrets, nøgler og rettigheder der først fejler i produktion
Når du bruger eksterne services (Firebase, Supabase, Stripe, Maps APIs osv.), er der tit forskel på dine udviklings-nøgler og dine produktions-nøgler.
Typisk scenarie:
- Du har oprettet en API key kun til localhost-domæner.
- Det virker perfekt lokalt.
- I produktion får du 401 eller 403 fra API’et.
Løsningen er at gå ind i dashboardet for den service du bruger og tjekke, hvilke domæner eller origins der er whitelisted. Tilføj dit rigtige domæne, og brug den rette key i dine production env vars.
Og bare lige gentaget: læg aldrig hemmelige nøgler direkte i frontend-koden. Hvis det er en hemmelighed, skal det via backend eller serverless functions. Ellers kan alle brugere læse den i devtools.
Det sidste trin: lær at stille gode fejlspørgsmål
En ting jeg ville ønske nogen havde fortalt mig tidligere: måden du beskriver en fejl på, er halvdelen af løsningen. Både når du spørger andre mennesker, og når du bare taler med dig selv i din egen notes-app.
Når noget “virker lokalt men ikke i produktion”, så prøv at formulere det sådan her:
- Hvad forventede du der skulle ske?
- Hvad sker der faktisk i produktion?
- Hvad sker der, hvis du prøver præcis det samme lokalt?
- Hvilke fejl ser du i console, network og server-logs?
- Hvad har du allerede testet?
Et godt fejlspørgsmål kunne være:
Jeg har en Vite/React app hostet på Vercel. Lokalt kan jeg kalde mit API på
http://localhost:3000/api/users uden problemer.
I produktion (https://mitdomæne.dk) får jeg denne fejl i console:
Access to fetch at 'https://api.mitdomæne.dk/api/users' from origin
'https://mitdomæne.dk' has been blocked by CORS policy.
Jeg har tjekket:
- API'et svarer fint, hvis jeg kalder det direkte i browseren.
- Origin i fejlbeskeden matcher mit domæne.
Hvor skal jeg sætte CORS op, og hvad skal jeg tillade for at det virker?
Sammenlign det med “hjælp, mit site virker ikke efter deploy”. Den første version inviterer til et brugbart svar. Den anden inviterer til 10 spørgsmål tilbage.
Hvis du vil træne den muskel lidt mere, kan du fx prøve at tage en fejl du har lige nu, skrive den ned som et Stack Overflow-spørgsmål, og så selv forsøge at svare på den. Det lyder lidt nørdet, men det gør dig markant bedre til at debugge over tid.
Min egen tommelfingerregel når produktion driller
Jeg er efterhånden nået dertil, hvor jeg bliver lidt mistænksom, hvis noget kun virker på min egen maskine. Det er nærmest et advarselssignal i sig selv.
Hvis du langsomt får bygget en lille mental tjekliste over “build, env vars, API-URL, CORS, HTTPS, routing, cache”, så vil det her problem gå fra at være ren panik til bare at være endnu et lille puslespil. Og puslespil er trods alt en af de sjovere dele af at bygge ting på nettet.







2 comments