Vibe coding · Deploy basics · For beginners
App Works Locally but Not After Deploy? 5 Causes and Fixes
Your app didn’t break. It moved to a computer that knows nothing about yours.
Five usual suspects, four of them reproduced in a real test project — and the one fix that solves most of them.
You know the feeling. The app runs perfectly on your laptop. You click every button, it all works, and you finally hit deploy. Then you open the real link — and it’s a blank page, a value that says undefined, or a flat 404 the moment you refresh.
Nothing in your code changed. So what happened? Your code didn’t change, but the computer running it did. Your laptop has files, settings and running programs that the hosting service never sees. Every cause below is a version of that one idea.
The short version
An app that works locally but not after deploy usually depends on something only your computer has. Check five things: environment variables never added to the host, a redeploy you skipped after changing them, code still calling localhost, file names whose capital letters don’t match, and page routes the host doesn’t know about.
How I tested this. On Nov 12, 2026 I built a tiny Vite app (Vite 8.3.8, Node v22.23.4) in a Linux sandbox, committed it to git, then cloned a fresh copy and built it again — the same thing a host like Vercel or Netlify does. I served it two ways and loaded it in a headless Chromium browser.
What I did not do. I did not deploy to a real Vercel or Netlify account, and I didn’t test on a Mac or Windows machine. Where I rely on their docs instead of my own run, I say so.
If you see red text instead of a blank page, read how to read error messages when you can’t code alongside this — the build log on your host is just another error message.
Cause 1: your .env file never left your computer
This is the big one. Your API address, your keys, your database link — they usually live in a file called .env or .env.local. That file is supposed to stay private, so it is usually kept out of git. Vite’s docs list .env.local as “ignored by git,” and the starter project I generated already had *.local in its .gitignore.
That’s good for safety. It also means your host builds your app without those values. Here’s what my fresh copy printed:
# my machine (has .env.local)
VITE_API_URL = https://api.example.com
# fresh clone, like a host builds it (no .env.local)
VITE_API_URL = undefinedThe scary part: the build didn’t fail. It printed built in 47ms and moved on. A missing variable doesn’t always crash the build — sometimes it just quietly becomes “undefined.” You only find out when the live app tries to use it.
The fix: add each variable by hand in your host’s dashboard (on Vercel: Project Settings, then Environment Variables). Vercel’s own guide to this problem says variables must be defined the same way in your Preview and Production environments (Vercel KB). When I set the variable during the build, the fresh copy printed the right address.
The prefix trap (and the safety rule that comes with it)
Frontend tools only pass variables with a special prefix to the browser: VITE_ for Vite, NEXT_PUBLIC_ for Next.js. In my test, a variable called API_URL without the prefix came out undefined even on my own machine. Vite’s docs also say these values are “bundled into your source code at build time” — which means anyone can read them in the browser.
⚡ Don’t “fix” it by adding the prefix to a secret key
If the AI suggests renaming OPENAI_API_KEY to VITE_OPENAI_API_KEY so it “works,” stop. Vite’s docs warn that VITE_ variables should not contain API keys. A paid key in the browser is a key anyone can copy and spend. Keep secrets on a server or serverless function — more in the env file security guide.
Cause 2: you changed the variable but didn’t redeploy
You fixed cause 1, refreshed the live site — and nothing changed. That’s normal. Vercel’s docs put it plainly: changes to environment variables “are not applied to previous deployments, they only apply to new deployments” (Vercel docs).
For frontend apps this makes extra sense: the value is baked into the files at build time, so the old files still carry the old value. The fix: trigger a new deploy after every variable change. I didn’t test this one on a live account — it comes straight from Vercel’s troubleshooting table.
Cause 3: the code is still calling localhost
AI tools love to write fetch("http://localhost:3000/api") while you build. On your laptop that works, because your backend is running on port 3000. But localhost always means “this same computer.” On a visitor’s phone, there’s no backend there.
I tested exactly that. With my little backend running, the page showed hello from my backend. With it stopped — which is what every visitor’s computer looks like — the browser console showed this:
Failed to load resource: net::ERR_CONNECTION_REFUSED
TypeError: Failed to fetchThe fix: put the real API address in a variable (cause 1) and make sure your backend is actually deployed somewhere too. Search your project for “localhost” before every deploy. It takes ten seconds and catches this every time.
Cause 4: capital letters in file names
This one feels unfair. Your file is greeting.js, but somewhere the code imports ./Greeting.js. Netlify’s support guide explains that its build system is case sensitive while a typical Windows or Mac setup is not, so the mismatch slips through locally (Netlify support guide). Apple’s Disk Utility lists “case-sensitive” as a separate APFS option you have to choose (Apple).
On my Linux sandbox — the same kind of system most hosts build on — the build stopped cold:
✗ Build failed in 44ms
[UNRESOLVED_IMPORT] Could not resolve './Greeting.js' in src/main.js
1 │ import { greet } from './Greeting.js'
│ ╰── Module not found.The fix: make the import match the file name letter for letter. One more trap from the same Netlify guide: git ignores case changes by default, so renaming Greeting.js to greeting.js may not register. Their workaround is to remove the file in one commit and add it back with the right name in the next. If a rename goes wrong, here’s how to go back with git.
Cause 5: the home page works, but refreshing any other page shows 404
Many AI-built apps are “single page apps”: there’s really only one file, index.html, and JavaScript draws the other pages. Click around and it’s fine. Refresh on /about, and the host goes looking for a real /about file that doesn’t exist.
Here’s what I actually measured with the exact same build:
| Served by | Home page / | Refresh on /about |
|---|---|---|
vite preview (local) | 200 ✓ | 200 ✓ |
| Plain static file server | 200 ✓ | 404 ✗ |
That’s why it “worked locally”: in my test, Vite’s preview server quietly sent every unknown address to index.html. A basic file server didn’t. Many hosts won’t either, unless you tell them to.
The fix on Netlify: add a file named _redirects to your published folder (for Vite, put it in public/) with this one line, from Netlify’s 404 guide:
/* /index.html 200The fix on Vercel: add a vercel.json at the project root with a rewrite, as shown in Vercel’s Vite guide:
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}I copied both rules from the official pages but couldn’t run them on a live Netlify or Vercel account. If your project uses Next.js or another framework with its own server, you likely don’t need either — the framework handles routing.
How to actually test before you deploy
Most of this can be caught on your own laptop in about five minutes. Pretend to be the host: build from a fresh copy that has never seen your private files. Here’s the checklist I’d run:
- Search for “localhost” across your project. Every hit that isn’t in a comment needs a real address.
- List every variable in your
.envfiles, then confirm each one exists in your host’s dashboard — without pasting secret values into a chat. - Build a fresh copy. Clone your repo into a new folder, run
npm installandnpm run build. If something readsundefinedthere, it will readundefinedlive. - Preview the build with
npm run previewand click through every page — then remember preview hides cause 5, so add the rewrite rule anyway. - After deploy, read the host’s build log, even when it says “success.” Then redeploy whenever you change a variable.
Want the AI’s help? Give it one clear sentence: “It works on my machine but on the deployed site [what you see]. Check my code for localhost URLs, missing environment variables and file-name case mismatches before changing anything.” That’s my suggested prompt, not something I’ve benchmarked. Planning this upfront also helps — see the vibe coding planning guide.
💡 Going live? Do the security pass too
Once the app is public, anyone can poke at it. Before you share the link, run through this free security audit for vibe-coded apps.
The honest limitations
I didn’t deploy to a real account. My “host” was a fresh git clone plus a plain static file server in a sandbox. That reproduces the logic, but real hosts add their own features and defaults. Their dashboards also change; follow the official docs linked above if a menu looks different.
I only tested a Vite frontend. Next.js, Python backends, databases and serverless functions have more failure points — for example, Vercel’s guide also lists native packages and writing to files as reasons functions break after deploy. I didn’t test those.
Cause 2 and the Mac side of cause 4 come from docs, not my runs. I’m repeating what Vercel, Netlify and Apple say.
Five causes is not every cause. These are the ones that line up with “nothing changed, but it broke.” If none fit, read the host’s build log line by line.
Who should actually care
Worth it if: you built an app with AI, it runs on your laptop, and you’re about to put it online for the first time — or you just did and it broke.
Maybe not if: you build only inside an all-in-one tool that hosts the app for you; some of these steps happen behind the scenes there.
Skip if: you deploy for a living. You’ve already been bitten by all five.
Quick answers
Why does my app work locally but not when deployed?
Because it depends on something only your computer has: a private .env file, a backend running on localhost, a file system that ignores capital letters, or a dev server that handles page routes for you. The host has none of these unless you set them up.
Why are my environment variables undefined after deploy?
Usually because your .env file isn’t uploaded, so the variables were never added to the host. For browser code, the name may also be missing its VITE_ or NEXT_PUBLIC_ prefix. After adding them, redeploy.
Why do I get a 404 when I refresh a page on my deployed app?
Your app is likely a single page app, and the host doesn’t know to send unknown addresses to index.html. Add a _redirects rule on Netlify or a rewrite in vercel.json on Vercel.
Can I just put my API key in a VITE_ variable to make it work?
It will “work,” and anyone can then read the key in their browser. Vite’s docs say not to. Keep secret keys on a server or serverless function.
The bottom line
“It works on my machine” is true — that’s the problem. Your machine has private files, running programs and forgiving settings the host never gets. Find which of those your app leans on, give the host its own copy, and the gap closes.
Do this today: clone your project into a brand-new folder, run npm install and npm run build, and look for the word undefined.
Written Nov 12, 2026. Updates will be logged here: none yet.