'Cannot find module' after deploy — how to fix MODULE_NOT_FOUND
Quick answer: Error: Cannot find module (MODULE_NOT_FOUND) means Node couldn't resolve a require/import. After a deploy it's almost always one of: dependencies weren't installed (no node_modules), a devDependency is needed at runtime, a path's casing differs on case-sensitive Linux, or a build step didn't run. Check which module is missing, whether it's in package.json, and run npm ci.
What the error looks like
Node prints the module it tried and failed to resolve:
Error: Cannot find module 'express'
Require stack:
- /app/dist/server.js
at Function._resolveFilename (node:internal/modules/cjs/loader:1145:15)
code: 'MODULE_NOT_FOUND'
Two flavors: a package name ('express') means a dependency isn't installed; a relative path ('./utils/log') means a file is missing, mis-cased, or wasn't built. "Works locally, fails in prod" is the classic signature.
Why it appears only after deploy
Dependencies weren't installed
No node_modules on the server, or npm ci didn't run before start. Locally you already have them.
A devDependency is needed at runtime
Prod installs skip devDependencies. If your app requires one at runtime, it's missing in production.
Case-sensitive paths on Linux
./Utils resolves on macOS/Windows but not on Linux. The import casing must match the filename exactly.
The build didn't run
A TypeScript/bundler step was skipped, so dist/ is missing or stale and the imported file isn't there.
Diagnose it in three steps
Read which module is missing
Package name or relative path? That single distinction tells you which branch you're on.
# Is it a declared dependency?
node -e "console.log(require('./package.json').dependencies)"
cat package.json | grep -A20 '"dependencies"'Reinstall cleanly from the lockfile
# Reproducible install — matches the lockfile exactly
rm -rf node_modules
npm ci # not "npm install"
ls node_modules/express # confirm it's actually thereFor a relative path, check casing and the build
# Linux is case-sensitive — these are different files:
import { log } from './utils/Log'; // file is utils/log.ts ? -> fails on Linux
# And make sure the build actually produced the file:
npm run build && ls dist/Make the package the deploy ships be the package that runs
MODULE_NOT_FOUND in production is almost always a gap between what you built and what you shipped. Close it:
- Use
npm ciin the pipeline, notnpm install— it installs exactly the lockfile, every time. - Move runtime deps out of
devDependencies— if the running app needs it, it's adependency. - Build before you ship, and ship the build output — verify
dist/exists in the artifact, not just on your laptop. - Match import casing to filenames — and consider an
eslintrule so case drift fails CI on macOS too.
Verified packages — caught at the gate, not in production
Infraveil is a backend operations control plane that runs on your own servers. Nothing runs until it's verified: the agent checks the package it's handed before it starts the service, so a missing dependency or a broken build is caught at the deploy gate rather than crashing in production. Every release is approval-gated and recorded, with one-click rollback to the last package that actually started.
Frequently asked questions
What does 'Cannot find module' mean?
Node tried to resolve a require/import and couldn't find it. The MODULE_NOT_FOUND code accompanies it. The missing item is either an uninstalled package or a missing/mis-pathed file.
Why does it work locally but not in production?
Locally you have all dependencies and the right build output. Production may have skipped devDependencies, not run npm ci, not run the build, or be case-sensitive (Linux) where your machine isn't.
Should I use npm install or npm ci on deploy?
Use npm ci. It installs exactly what the lockfile specifies, fails if package.json and the lockfile disagree, and is reproducible — ideal for CI/CD and production.
Why does the same import fail only on Linux?
Linux filesystems are case-sensitive. ./Utils/Log and ./utils/log are different paths there, even though macOS and Windows treat them as the same. Match the import casing to the real filename.