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 dev lets 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 build for 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 build script)
  • 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 via vercel 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 safety

2. 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.local

If 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 build

Watch 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.

After npm run build, start the production server to verify it actually runs:

npm run start

Open 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 push

Vercel 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 build exits with 0 and prints the route table
  • npm run start boots the server (you see Listening 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 sensitivityButton.tsx and button.tsx are 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.local has 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 pull if 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.json and next.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_URL is set.

”ESLint: X errors”

Lint errors that block the build (the default Next.js build does this).

  • Run npx eslint . --fix to 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: true to next.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:

  1. Case sensitivity — your macOS/Windows filesystem is case-insensitive; Vercel’s Linux isn’t.
  2. Different Node version — set the version in package.json engines field.
  3. 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 build locally (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 test

This 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


Sources