How to run the production build locally before pushing
Status: 🟩 COMPLETE Last updated: 2026-06-19 Plain-English tagline: The 60-second habit that catches the bugs
npm run devlets slide — type errors, missing env vars, case-sensitivity issues, broken imports — before Vercel does it for you in CI.
Goal
Catch the build-time errors that Vercel WILL hit before you push, by running npm run build (and ideally npm run start) locally. Reduce the number of failed deploys, broken preview URLs, and “Vercel says the build failed” messages by ~80%.
This is a non-negotiable habit in the playbook. From ~/.claude/CLAUDE.md:
Always run the production build locally before pushing (
npm run buildfor Next.js projects).
Why it matters
npm run dev (Next.js’s dev mode) is lenient. It:
- Compiles only the files you visit
- Hot-reloads on every change
- Tolerates many TypeScript and ESLint issues
- Uses different defaults for caching, error boundaries, image optimization
- Skips production-specific build steps (route manifest, sitemap generation, etc.)
npm run build (production build) is the opposite. It compiles EVERY file in the project. It hits TypeScript strictness. It catches issues that dev mode hides:
- TypeScript errors in files you didn’t open this session
- Missing environment variables required at build time
- Case-sensitivity mismatches (works on macOS/Windows; fails on Vercel’s Linux)
- Static-export issues (a server-only component imported from a static page)
- Image / metadata generation errors
- Bundle-size warnings for unexpectedly heavy code
Vercel runs npm run build for every deploy. If it fails on Vercel, your deploy fails. If you ran it locally first, you catch the failure in 60 seconds instead of waiting for the CI red-X email five minutes later.
Saving the failed-deploy cycle (deploy → email → check logs → fix → push → wait → deploy again) is the single highest-leverage build habit you can adopt.
Prerequisites
- A working Next.js project (or any framework with a
buildscript) - Node.js installed (LTS recommended)
- All your changes committed or stashed (so you know what you’re testing)
- All required env vars set in
.env.local(or viavercel env pull)
Steps
1. Save and commit (or stash) your work
You’re about to run a tool that compiles everything. If you have unsaved changes, save first. If you want to be sure you’re testing committed code, commit (or stash).
git status # Check what's staged / unstaged
git add . && git commit -m "wip" # Or commit current state for safety2. Make sure env vars are in place
The build will fail if NEXT_PUBLIC_* vars (or any other required env vars) are missing. Quick check:
# Check what's in your env file
cat .env.local
# If you have Vercel CLI installed, pull production env vars
vercel env pull .env.localIf you don’t have vercel env pull set up, just make sure your .env.local has all the same keys as .env.example.
3. Run the build
npm run buildWatch the output. The build proceeds through several phases:
▲ Next.js 16.0.0
- Environments: .env.local
Linting and checking validity of types ...
Creating an optimized production build ...
✓ Compiled successfully
Collecting page data ...
Generating static pages (12/12)
Finalizing page optimization ...
Route (app) Size First Load JS
┌ ○ / 142 B 87.5 kB
├ ○ /_not-found 871 B 88.2 kB
├ ƒ /api/posts 0 B 0 B
└ ƒ /posts/[id] 1.4 kB 88.7 kB
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand
A successful build ends with a route table. The whole process takes 30 seconds to 3 minutes on a typical Next.js project.
4. (Optional but recommended) Start the production server
After npm run build, start the production server to verify it actually runs:
npm run startOpen http://localhost:3000 in a private/incognito window (no dev-mode service workers, no cached state). Navigate to the pages you changed. Look at the browser console for errors.
Production mode behaves differently from dev:
- React error boundaries kick in
- Image optimization runs (different image URLs)
- ISR / cache behavior is real
- Service workers (if used) are active
- Source maps may not be available
This catches the second class of bugs — code that compiles fine but breaks at runtime in the production build.
5. Kill the server, push your changes
Once you’ve confirmed the build works and the production server boots correctly:
# Ctrl+C to stop npm run start
git pushVercel now runs npm run build on its side. You already know it succeeds — the push is calm, not anxious.
Verification
You’ll know it worked when:
npm run buildexits with0and prints the route tablenpm run startboots the server (you seeListening on http://localhost:3000)- The site loads at localhost:3000 in your browser
- No errors in the browser console for the pages you changed
If any of these fail, the deploy on Vercel will also fail — so fix locally first.
Common failures and fixes
”Type error: Property ‘X’ does not exist on type ‘Y’”
A TypeScript error somewhere in the project. The output names the file and line.
- Open the file. Read the error.
- Often: you renamed/refactored something and missed updating a usage.
- Fix the type or the usage to match.
- Re-run
npm run build.
”Module not found: Can’t resolve ’…’”
A file you imported doesn’t exist (or has the wrong path).
- Check the import path — typos are common.
- Check case sensitivity —
Button.tsxandbutton.tsxare different on Linux (Vercel) but the same on macOS/Windows. Match the file name’s exact case. - Make sure the file isn’t in
.gitignore(so it’s missing on the Vercel build server).
“process.env.X is undefined” / “Cannot read properties of undefined (reading ’…’)”
An env var the code uses at build time is missing.
- Check
.env.localhas the variable. - Check the variable’s spelling matches between code and env file.
- If the variable needs to be available client-side, it must start with
NEXT_PUBLIC_. - Run
vercel env pullif you’ve been adding vars in the Vercel dashboard.
”Module parse failed: Unexpected token”
Often a TypeScript syntax issue or a config issue (importing a .json or unsupported file type).
- Look at the file in question — does it use a feature that requires a config update?
- Check
tsconfig.jsonandnext.config.ts.
”Static generation failed for /some-page”
A page that should be statically generated tried to do something dynamic at build time (e.g. call a database that’s not reachable from the build environment).
- Either provide the data at build time, or make the page dynamic (
export const dynamic = "force-dynamic"). - For pages that need a database connection, ensure
DATABASE_URLis set.
”ESLint: X errors”
Lint errors that block the build (the default Next.js build does this).
- Run
npx eslint . --fixto auto-fix what’s fixable. - Read the remaining errors and fix manually.
- If you really need to ship with lint errors (rarely a good idea), add
eslint.ignoreDuringBuilds: truetonext.config.ts— but only as an emergency override, never a default.
”Out of memory” during build
The default Node heap is 4GB. Very large Next.js projects can blow past it.
- Increase the heap:
NODE_OPTIONS=--max-old-space-size=8192 npm run build - Or split the project into multiple builds / monorepo packages.
Build works locally, fails on Vercel
Three usual suspects:
- Case sensitivity — your macOS/Windows filesystem is case-insensitive; Vercel’s Linux isn’t.
- Different Node version — set the version in
package.jsonenginesfield. - Missing env var on Vercel — set it in Settings → Environment Variables and redeploy.
If you’ve ruled all three out, paste the Vercel build log into Claude Code and ask for help reading it.
What just happened, conceptually
You ran the EXACT same command Vercel runs in CI. If it succeeds locally, it almost certainly succeeds on Vercel. If it fails locally, you know what to fix BEFORE the push.
The wall-clock saving:
- Without local build: push → wait 90s for Vercel → see fail email → check logs → fix → push → wait 90s → maybe succeed. ~5 minutes per failed cycle.
- With local build:
npm run buildlocally (60s) → succeed locally → push → almost certainly succeed on Vercel. ~1 minute, no wasted CI cycles.
On a project where you push 10 times a day, this saves real time. More importantly, it makes pushing feel calm instead of anxious.
Variation: build + test in one shot
For belt-and-suspenders, run the full pipeline locally:
npm run lint && npm run build && npm testThis runs lint, build, and tests in sequence. The && operator stops on the first failure. If all three pass, you’re effectively running what Vercel + CI together run.
For projects with longer test suites, this might take 2-5 minutes. Worth it before a meaningful push.
Variation: using a single script
Add a script to package.json for the combined check:
{
"scripts": {
"predeploy": "npm run lint && npm run build && npm test",
"build": "next build",
"start": "next start"
}
}Then run npm run predeploy before every push. Building muscle memory.
Some teams add this as a Husky pre-push hook so it runs automatically on git push. Useful if you’re disciplined; annoying if you push small commits frequently. Pick what fits your rhythm.
See also
- Vercel 🟩 🟦
- CD 🟩 — what Vercel runs after you push
- Environment variables 🟩
- Type checking 🟩 —
tscruns as part ofnext build - Linting 🟩 — same
- Next.js 🟩 🟦
- Node.js 🟩 🟦 — the runtime running the build
- Deploy a Next.js app to Vercel 🟩 — the deploy step this guards
- Debug a Vercel build failure 🟥 — when this fails on Vercel anyway
Sources
- Next.js — Build — what
next buildactually does - Next.js —
next.config.js - Vercel — Build step — how Vercel runs the build