npm run build Fails but npm run dev Works: Why and How to Fix It
Development mode is forgiving; production builds are strict. The common reasons a build fails when dev works — TypeScript and lint errors, case-sensitive imports, missing environment variables, server-only code in the browser, prerendering errors and memory — with the fix for each.
Your app works perfectly with npm run dev. Then npm run build — or your host's deploy — fails. This is extremely common, and it's not random: dev mode is designed to be forgiving, production builds are designed to be strict. (npm run dev vs build vs start)
Always read the first error in the build output; later errors are often consequences of it. (How to read an error message)
1. TypeScript errors
Dev servers often transpile TypeScript without type-checking it. next build and many CI setups do type-check, and fail on errors you never saw.
Run the checker yourself:
npx tsc --noEmit
Fix the errors rather than silencing them — they're often real bugs. (Type is not assignable to type, Property does not exist on type, Object is possibly undefined)
2. Lint errors
Some frameworks run ESLint during the build and fail on errors (not warnings). Run npm run lint locally to see them. (Linters and formatters)
3. Case-sensitive file names
Module not found: Can't resolve './components/Header'
…but the file is header.tsx. macOS and Windows ignore case; the Linux machine doing your build doesn't. Match the case exactly. If you renamed a file only by case, Git may not have noticed — use git mv header.tsx Header.tsx. (Cannot find module)
4. Missing environment variables
The build needs variables that exist in your local .env but not in your host's settings. Especially frontend variables (VITE_*, NEXT_PUBLIC_*), which are baked in at build time — they must be present during the build, not just at runtime. (Environment variable undefined?)
Add them in your hosting dashboard, then rebuild.
5. Prerendering errors (Next.js)
Error occurred prerendering page "/dashboard"
During the build, Next.js renders static pages ahead of time. If a page fetches data from an API or database that isn't reachable at build time, or uses browser-only things, the build fails.
- Browser APIs during render → move into
useEffect. (window is not defined) - Data that must be fetched live → make the page dynamic.
- A database the build server can't reach → check connection settings or don't prerender that page. (SSR vs CSR vs SSG)
6. Server-only code imported in client code
Importing a Node module (fs, crypto, a database client) into a component that runs in the browser fails in the build:
Module not found: Can't resolve 'fs'
Keep server code in server components, API routes or server actions. (React Server Components explained)
7. Out of memory
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
Big builds can exceed Node's default memory, especially on small build machines. (JavaScript heap out of memory)
8. Different Node or package versions
Your host builds with a different Node.js version, or installs slightly different package versions.
- Pin the Node version (
"engines"inpackage.json, or an.nvmrc). - Commit your lock file and use
npm ciin the build. (package-lock.json explained)
9. Dev-only dependencies
A package needed by the build is in devDependencies, and the host installs with --production. Either move it to dependencies or make sure the build step installs dev dependencies.
The habit that prevents most of this
Run the production build locally before pushing:
npm ci && npm run build
And put it in CI, so every push is checked. (Set up CI with GitHub Actions)
EasySpawn builds your app on the same Linux server it runs on, with the same Node version and environment, so a build that passes is a build that deploys. See how it works or join the waitlist.
Related: npm run dev vs build vs start · Why Does My App Work Locally but Not in Production? · React App Shows a Blank Page After Deploying · "Cannot Find Module"
Keep reading
"exec format error" in Docker: ARM vs x86 Images Explained
exec format error almost always means a container image built for one CPU architecture (ARM, like Apple Silicon Macs) is running on another (x86/amd64 servers), or vice versa. How to check, build for the right platform with --platform and buildx, and the other cause: scripts without a shebang or with Windows line endings.
Why Is My Environment Variable Undefined? (Vite, Next.js, Node)
Your .env file has the value but the code sees undefined. The reasons are almost always the same: the wrong prefix (VITE_, NEXT_PUBLIC_), the wrong file name or folder, not restarting the dev server, build-time vs runtime, or the variable never being set in production.