Fix Next.js Build Error Module Not Found After Deploy
Module not found errors only in production? Learn why Next.js builds fail after deploy and get 6 proven fixes that work on Vercel, AWS, and other platforms.
Fix Next.js Build Error Module Not Found After Deploy#
"Module not found" errors that only appear after deployment are among the most confusing Next.js issues. Your app works perfectly in development, builds successfully locally, but fails in production with cryptic module errors. This comprehensive guide covers every cause and provides battle-tested solutions.
This article is part of our comprehensive Deploying Next.js + Supabase to Production guide.
Why Module Errors Only Happen in Production#
Development and production environments differ in critical ways:
- Case Sensitivity - Production servers (Linux) are case-sensitive, Windows/Mac aren't
- Path Resolution - Different module resolution algorithms
- Build Optimization - Tree-shaking removes "unused" imports
- Environment Variables - Missing or incorrect env vars
- Dependencies - devDependencies not installed in production
Common Error Messages#
## Error 1: Cannot find module
Error: Cannot find module '@/components/Header'
## Error 2: Module not found (Webpack)
Module not found: Can't resolve './components/header'
## Error 3: Dynamic import failure
Error: Cannot find module './pages/dashboard'
## Error 4: Package not found
Module not found: Can't resolve 'some-package'
Solution 1: Fix Case Sensitivity Issues#
The #1 cause of production-only module errors:
Identify Case Mismatches#
// BAD: Import doesn't match actual filename
// File: components/Header.tsx
import { Header } from '@/components/header' // ❌ lowercase 'h'
// File: lib/utils.ts
import { formatDate } from './Utils' // ❌ Capital 'U'
// GOOD: Exact case match
// File: components/Header.tsx
import { Header } from '@/components/Header' // ✅ Capital 'H'
// File: lib/utils.ts
import { formatDate } from './utils' // ✅ lowercase 'u'
Find All Case Mismatches#
## Install case-sensitive-paths-webpack-plugin
npm install --save-dev case-sensitive-paths-webpack-plugin
## Add to next.config.mjs
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'
const nextConfig = {
webpack: (config, { isServer }) => {
config.plugins.push(new CaseSensitivePathsPlugin())
return config
},
}
export default nextConfig
Automated Fix Script#
// scripts/fix-case-sensitivity.js
const fs = require('fs')
const path = require('path')
const glob = require('glob')
// Find all TypeScript/JavaScript files
const files = glob.sync('**/*.{ts,tsx,js,jsx}', {
ignore: ['node_modules/**', '.next/**'],
})
files.forEach(file => {
let content = fs.readFileSync(file, 'utf8')
let modified = false
// Check each import statement
const importRegex = /from ['"](.+)['"]/g
let match
while ((match = importRegex.exec(content)) !== null) {
const importPath = match[1]
// Skip node_modules and absolute paths
if (!importPath.startsWith('.') && !importPath.startsWith('@/')) {
continue
}
// Resolve actual file path
const resolvedPath = path.resolve(path.dirname(file), importPath)
// Check if file exists with different case
if (fs.existsSync(resolvedPath)) {
const actualPath = fs.realpathSync(resolvedPath)
if (actualPath !== resolvedPath) {
console.log(`Case mismatch in ${file}:`)
console.log(` Import: ${importPath}`)
console.log(` Actual: ${actualPath}`)
modified = true
}
}
}
if (modified) {
console.log(`Fixed: ${file}`)
}
})
Solution 2: Fix Path Alias Configuration#
Incorrect tsconfig.json or jsconfig.json paths:
Verify Path Aliases#
// tsconfig.json or jsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"], // ✅ Correct
"@/components/*": ["./src/components/*"], // ✅ Specific paths
"@/lib/*": ["./src/lib/*"],
"~/*": ["./*"] // ✅ Root alias
}
}
}
Common Path Alias Mistakes#
// ❌ WRONG: Missing baseUrl
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"] // Won't work without baseUrl
}
}
}
// ❌ WRONG: Incorrect path format
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"] // Missing ./
}
}
}
// ✅ CORRECT
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Update Next.js Config#
// next.config.mjs
const nextConfig = {
// Ensure webpack resolves aliases correctly
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
'@': path.resolve(__dirname, 'src'),
}
return config
},
}
Solution 3: Fix Dynamic Imports#
Dynamic imports can fail in production:
Incorrect Dynamic Import#
// ❌ BAD: Variable path
const pageName = 'dashboard'
const Page = dynamic(() => import(`./pages/${pageName}`))
// ❌ BAD: Computed path
const getComponent = (name) => dynamic(() => import(`@/components/${name}`))
Correct Dynamic Import#
// ✅ GOOD: Static path with dynamic loading
const DashboardPage = dynamic(() => import('./pages/dashboard'))
// ✅ GOOD: Explicit imports with conditional rendering
const components = {
dashboard: dynamic(() => import('@/components/Dashboard')),
profile: dynamic(() => import('@/components/Profile')),
settings: dynamic(() => import('@/components/Settings')),
}
function MyComponent({ type }) {
const Component = components[type]
return <Component />
}
// ✅ GOOD: Use webpack magic comments
const DynamicComponent = dynamic(() =>
import(
/* webpackChunkName: "dashboard" */
/* webpackMode: "lazy" */
'./pages/dashboard'
)
)
Solution 4: Fix Missing Dependencies#
Dependencies in wrong section of package.json:
Check Dependency Location#
// package.json
// ❌ WRONG: Build-time dependency in devDependencies
{
"devDependencies": {
"@supabase/supabase-js": "^2.38.0", // Used in production!
"next": "15.0.0" // Should be in dependencies!
}
}
// ✅ CORRECT: Production deps in dependencies
{
"dependencies": {
"@supabase/supabase-js": "^2.38.0",
"next": "15.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"typescript": "^5.0.0"
}
}
Move Dependencies#
## Move package from devDependencies to dependencies
npm install --save some-package
npm uninstall --save-dev some-package
## Or manually edit package.json and run:
npm install
Solution 5: Fix Environment-Specific Imports#
Some imports only work in specific environments:
Server-Only Imports#
// ❌ BAD: Server-only import in client component
'use client'
import fs from 'fs' // ❌ fs doesn't exist in browser
export function MyComponent() {
// ...
}
// ✅ GOOD: Use server actions or API routes
'use client'
export function MyComponent() {
async function handleAction() {
const response = await fetch('/api/read-file')
const data = await response.json()
}
return <button onClick={handleAction}>Read File</button>
}
// app/api/read-file/route.ts
import fs from 'fs' // ✅ OK in API route
export async function GET() {
const data = fs.readFileSync('file.txt', 'utf8')
return Response.json({ data })
}
Solution 6: Fix Vercel-Specific Issues#
Vercel has specific requirements:
Output File Tracing#
// next.config.mjs
const nextConfig = {
// Ensure all dependencies are traced
experimental: {
outputFileTracingIncludes: {
'/api/**/*': ['./node_modules/**/*.wasm', './node_modules/**/*.node'],
},
},
// Exclude unnecessary files
experimental: {
outputFileTracingExcludes: {
'*': [
'node_modules/@swc/core-linux-x64-gnu',
'node_modules/@swc/core-linux-x64-musl',
'node_modules/@esbuild/linux-x64',
],
},
},
}
Vercel Build Command#
// package.json
{
"scripts": {
"build": "next build",
"vercel-build": "next build" // Vercel uses this if present
}
}
Solution 7: Debug Build Locally#
Test production build locally:
## Build for production
npm run build
## Start production server
npm run start
## Or use Vercel CLI
npm install -g vercel
vercel build
vercel dev --prod
Enable Build Debugging#
// next.config.mjs
const nextConfig = {
// Show detailed build info
productionBrowserSourceMaps: true,
// Log webpack build
webpack: (config, { dev, isServer }) => {
if (!dev) {
console.log('Production build for:', isServer ? 'server' : 'client')
}
return config
},
}
Common Mistakes#
-
Mistake #1: Inconsistent file naming - Use consistent casing across all files
-
Mistake #2: Wrong dependency section - Runtime deps must be in
dependencies -
Mistake #3: Dynamic imports with variables - Use static imports or explicit mapping
-
Mistake #4: Not testing production builds - Always test with
npm run build && npm run start -
Mistake #5: Ignoring TypeScript errors - Fix all TS errors before deploying
FAQ#
Why does it work locally but not in production?#
Local development (Windows/Mac) is case-insensitive, production (Linux) is case-sensitive. Also, dev mode doesn't optimize/tree-shake like production builds.
How do I find which module is missing?#
Check the full error stack trace in your deployment logs. The error shows the exact import path that failed.
Can I make production case-insensitive?#
No, but you can add case-sensitive-paths-webpack-plugin to catch issues during development.
Why do dynamic imports fail?#
Webpack needs to know possible import paths at build time. Variable paths prevent this. Use static imports or explicit mapping.
Should I use relative or absolute imports?#
Both work, but absolute imports (@/) are cleaner and easier to refactor. Just ensure tsconfig.json is configured correctly.
Advanced Debugging Strategies#
Using Build Analysis Tools#
## Analyze what's included in your build
npm install --save-dev @next/bundle-analyzer
## Add to next.config.mjs
import bundleAnalyzer from '@next/bundle-analyzer'
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
})
export default withBundleAnalyzer(nextConfig)
## Run analysis
ANALYZE=true npm run build
Debugging Import Resolution#
// Create a debug script to trace imports
// scripts/debug-imports.js
const fs = require('fs')
const path = require('path')
const glob = require('glob')
const files = glob.sync('**/*.{ts,tsx,js,jsx}', {
ignore: ['node_modules/**', '.next/**'],
})
files.forEach(file => {
const content = fs.readFileSync(file, 'utf8')
const importRegex = /from ['"](.+)['"]/g
let match
while ((match = importRegex.exec(content)) !== null) {
const importPath = match[1]
// Check if file exists
const resolvedPath = path.resolve(path.dirname(file), importPath)
if (!fs.existsSync(resolvedPath)) {
console.log(`❌ Missing: ${importPath} in ${file}`)
}
}
})
Production Build Simulation#
## Build and test locally before deploying
npm run build
## Start production server
npm run start
## Test critical routes
curl http://localhost:3000/
curl http://localhost:3000/api/health
## Check for errors in console
Environment-Specific Module Issues#
Handling Platform-Specific Modules#
// ❌ BAD: Platform-specific import in client code
import fs from 'fs' // Only works on server
// ✅ GOOD: Use dynamic imports with ssr: false
import dynamic from 'next/dynamic'
const FileReader = dynamic(
() => import('@/components/FileReader'),
{ ssr: false }
)
// ✅ GOOD: Use server actions
'use server'
export async function readFile(path: string) {
const fs = await import('fs')
return fs.readFileSync(path, 'utf8')
}
Conditional Imports Based on Environment#
// lib/platform-specific.ts
let platformModule: any
if (typeof window === 'undefined') {
// Server-side only
platformModule = require('fs')
} else {
// Client-side only
platformModule = require('browser-storage')
}
export default platformModule
Monorepo-Specific Issues#
Handling Monorepo Module Resolution#
// tsconfig.json in monorepo root
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@app/*": ["apps/app/src/*"],
"@api/*": ["apps/api/src/*"],
"@shared/*": ["packages/shared/src/*"],
"@ui/*": ["packages/ui/src/*"]
}
}
}
Monorepo Build Configuration#
// next.config.mjs in monorepo app
const nextConfig = {
webpack: (config, { isServer }) => {
// Ensure monorepo packages are resolved
config.resolve.alias = {
...config.resolve.alias,
'@shared': path.resolve(__dirname, '../../packages/shared/src'),
'@ui': path.resolve(__dirname, '../../packages/ui/src'),
}
return config
},
}
Performance Optimization for Large Projects#
Lazy Load Heavy Dependencies#
// ❌ BAD: Import everything upfront
import * as lodash from 'lodash'
// ✅ GOOD: Lazy load only what you need
import { debounce } from 'lodash'
// ✅ BETTER: Use dynamic imports
const { debounce } = await import('lodash')
Tree-Shaking Configuration#
// next.config.mjs
const nextConfig = {
// Ensure tree-shaking works
webpack: (config) => {
config.optimization.usedExports = true
config.optimization.sideEffects = true
return config
},
// Mark packages as side-effect free
modularizeImports: {
'lodash': {
transform: 'lodash/{{member}}',
},
'@mui/material': {
transform: '@mui/material/{{member}}',
},
},
}
Testing Module Resolution#
Create a Module Resolution Test#
// __tests__/module-resolution.test.ts
import { describe, it, expect } from 'vitest'
describe('Module Resolution', () => {
it('should resolve all path aliases', async () => {
const modules = [
'@/components/Button',
'@/lib/utils',
'@/types/index',
'@/hooks/useAuth',
]
for (const modulePath of modules) {
const module = await import(modulePath)
expect(module).toBeDefined()
}
})
it('should not have circular dependencies', async () => {
// Use madge to check
const madge = require('madge')
const result = await madge('src')
expect(result.circular()).toHaveLength(0)
})
})
Deployment Platform-Specific Fixes#
Vercel-Specific Configuration#
// next.config.mjs for Vercel
const nextConfig = {
// Ensure all files are traced
experimental: {
outputFileTracingIncludes: {
'/api/**/*': ['./node_modules/**/*.wasm'],
},
},
// Exclude unnecessary files
experimental: {
outputFileTracingExcludes: {
'*': [
'node_modules/@swc/core-linux-x64-gnu',
'node_modules/@swc/core-linux-x64-musl',
],
},
},
}
export default nextConfig
AWS Lambda/Netlify Configuration#
// next.config.mjs for serverless
const nextConfig = {
// Optimize for serverless
experimental: {
isrMemoryCacheSize: 0, // Disable ISR cache for serverless
},
// Ensure dependencies are bundled
webpack: (config) => {
config.externals = []
return config
},
}
export default nextConfig
Related Articles#
- Next.js Turbopack Stuck on Compiling How to Fix
- Resolve Next.js Hydration Mismatch Errors Complete Guide
- Deploy Next.js 15 to Vercel Without Environment Variable Errors
- Next.js Performance Optimization: 10 Essential Techniques
- Building SaaS with Next.js and Supabase
- Deploying Next.js + Supabase to Production
Conclusion#
Module not found errors in production are usually caused by case sensitivity mismatches, incorrect path aliases, or dependencies in the wrong package.json section. The fastest fix is ensuring all imports match exact file names (including case) and moving runtime dependencies to the dependencies section.
Always test production builds locally with npm run build && npm run start before deploying. Use case-sensitive-paths-webpack-plugin during development to catch issues early.
For Vercel deployments, check output file tracing configuration and ensure all necessary files are included in the build. Monitor deployment logs carefully to identify the exact module causing issues.
Frequently Asked Questions
Continue Reading
Deploy Next.js 15 to Vercel Without Environment Variable
Environment variables not working on Vercel? Learn the exact configuration needed for Next.js 15 deployment with zero errors.
Fix Supabase Auth Session Not Persisting After Refresh
Supabase auth sessions mysteriously disappearing after page refresh? Learn the exact cause and fix it in 5 minutes with this tested solution.
Resolve Next.js Hydration Mismatch Errors Complete Guide
Hydration mismatch errors breaking your Next.js app? Learn the root causes and 8 proven fixes to eliminate these errors permanently.