A Next.js application can work perfectly on your development machine and still fail after deployment.
You run:
npm run dev
Everything looks fine.
Pages load correctly. API requests work. Images appear. Authentication succeeds. Environment variables are available.
Then you deploy the same project to production and suddenly encounter problems such as:
500 Internal Server Error
Application Error
Module not found
ReferenceError: window is not defined
Or perhaps the application loads, but authentication, API calls, images, dynamic routes, or certain pages stop working.
The frustrating part is that developers often start changing random Next.js code even though the actual problem may be the production environment rather than the application logic.
This guide explains how to systematically troubleshoot a Next.js application that works locally but fails after deployment.
Why Does Next.js Work Locally but Fail in Production?
Your development and production environments are not identical.
Locally, you may have:
Development Machine
↓
npm run dev
↓
.env.local
↓
Local API
↓
Development Database
Production may instead use:
Production Server
↓
npm run build
↓
npm start
↓
Production Environment Variables
↓
Production API
↓
Production Database
Differences can include:
- Node.js version
- Environment variables
- API URLs
- Database credentials
- File-system behavior
- Operating system
- Case sensitivity
- Build configuration
- Server permissions
- HTTPS
- CDN or proxy configuration
- Production-only optimizations
That is why the first troubleshooting step should not be changing code.
First determine which part of the production lifecycle is failing.
1. Reproduce the Production Build Locally
Many developers test only with:
npm run dev
That is not enough.
Before deployment, test the actual production build:
npm run build
Then run:
npm start
Now test the application again.
This is important because:
npm run dev
and:
npm run build
npm start
do not behave identically.
If the application already fails during npm run build, you can debug the problem before involving your hosting environment.
If the production build works locally but fails after deployment, you have significantly narrowed the problem.
The issue is more likely related to:
- Server configuration
- Environment variables
- Deployment
- Node.js runtime
- Database/network access
- Reverse proxy
- Permissions
2. Read the Build Error Carefully
Suppose:
npm run build
returns:
Module not found: Can't resolve '@/components/Header'
Don’t immediately delete .next, reinstall Node.js, and change your hosting configuration.
The build has already told you what failed.
Look at the first meaningful error rather than the final:
Build failed
message.
For example:
Module not found
points toward imports or dependencies.
Type error
points toward TypeScript.
ReferenceError
points toward code execution.
Failed to collect page data
may indicate data fetching, environment, database, or build-time execution problems.
Fix the earliest meaningful error first and rebuild.
3. Check Node.js Version
Your local computer may use one Node.js version while production uses another.
Check locally:
node -v
Then check the production server:
node -v
For example:
Local
Node.js v22.x
Production
Node.js v18.x
Whether that exact combination is compatible depends on the Next.js version you’re using.
The important point is that your production Node version should satisfy your project’s actual requirements.
You can define the expected runtime in package.json where appropriate:
{
"engines": {
"node": ">=20"
}
}
Treat this as documentation and a deployment constraint—not a substitute for checking the requirements of your actual Next.js version and hosting platform.
4. Check Environment Variables
Environment variables are one of the most common reasons a Next.js application works locally but fails in production.
Locally, you may have:
.env.local
containing:
DATABASE_URL=...
API_URL=http://localhost:8000
NEXT_PUBLIC_API_URL=http://localhost:8000/api
AUTH_SECRET=...
Your production server does not automatically know those values.
You need to configure them in your deployment environment.
Check server-only variables
For example:
DATABASE_URL
AUTH_SECRET
PRIVATE_API_KEY
These should remain server-side.
Check browser-accessible variables
Next.js exposes appropriately prefixed environment variables to client-side code, commonly using:
NEXT_PUBLIC_
For example:
NEXT_PUBLIC_API_URL=https://api.example.com
Don’t solve a missing client-side value by prefixing sensitive secrets with NEXT_PUBLIC_.
Anything intentionally exposed to browser JavaScript should be considered public.
5. Check When Environment Variables Are Used
This is an important Next.js distinction.
Some environment values can be incorporated during the build process.
That means changing a production environment variable after building may not necessarily change values already embedded into client-side output.
A safe deployment workflow is:
Set production environment
↓
Build application
↓
Deploy generated application
↓
Start production server
If you build with:
NEXT_PUBLIC_API_URL=http://localhost:8000
and later change the server environment to:
NEXT_PUBLIC_API_URL=https://api.example.com
you should not assume that an already-generated client bundle will magically contain the new value.
Rebuild when your build-time configuration changes.
6. Check for localhost in Production
Search your project for:
localhost
and:
127.0.0.1
You may discover something like:
const API_URL = 'http://localhost:8000/api';
This worked locally because your backend was running on your computer.
In production, localhost means the machine/container on which that code is executing.
If the API is actually hosted at:
https://api.example.com
configure that value appropriately.
A better approach is:
const API_URL = process.env.NEXT_PUBLIC_API_URL;
with the correct production environment value.
7. Understand Server and Client Components
Modern Next.js applications can execute code in different environments.
That becomes particularly important when browser-only APIs are used.
Consider:
const token = localStorage.getItem('token');
or:
const width = window.innerWidth;
Browser APIs such as:
window
document
localStorage
sessionStorage
navigator
do not exist in the Node.js server environment.
This can produce errors such as:
ReferenceError: window is not defined
8. Fix “window is not defined”
If the code genuinely needs browser functionality, make sure it runs in the correct context.
For example, in a client component:
'use client';
import { useEffect, useState } from 'react';
export default function ScreenWidth() {
const [width, setWidth] = useState(null);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return <div>{width}</div>;
}
The key idea is not simply to add 'use client' everywhere.
Instead, determine:
Does this code belong on the server or in the browser?
Keeping server/client boundaries intentional leads to cleaner Next.js applications.
9. Check Client Component Boundaries
If a component uses client-side React features such as:
useState
useEffect
event handlers
browser APIs
it may need to be a Client Component.
For example:
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Don’t automatically convert your entire application to client components just to remove an error.
Use client components where interactivity is required and retain server-side rendering benefits elsewhere.
10. Check Case-Sensitive File Names
This is a classic production deployment problem.
Suppose the actual file is:
components/Header.jsx
but your import says:
import Header from '@/components/header';
This may appear to work on a case-insensitive development file system.
A Linux production environment is typically case-sensitive.
Therefore:
Header.jsx
and:
header.jsx
are different names.
Check:
- Component filenames
- Directory names
- Image names
- CSS imports
- Utility imports
Use consistent naming throughout the project.
11. Check Missing Dependencies
A package may exist on your development machine but not be correctly declared in your project.
Run:
npm install
and review package.json.
Make sure runtime dependencies are declared appropriately.
Do not rely on packages that happen to exist in an old local node_modules directory.
A useful test is to recreate dependencies from your lockfile in a clean environment, for example:
rm -rf node_modules .next
npm ci
npm run build
Use commands appropriate for your package manager and operating system.
If a clean installation fails locally, your production deployment may fail for the same reason.
12. Commit Your Lock File
If you’re using npm, keep:
package-lock.json
under version control.
Likewise, use the appropriate lock file for your selected package manager.
The lock file helps development, CI, and production install consistent dependency versions.
Without consistent dependency resolution, a deployment can unexpectedly install versions different from those you tested locally.
13. Don’t Mix Package Managers Unnecessarily
Avoid casually switching between:
npm
yarn
pnpm
inside the same project.
Multiple stale lock files can make deployment behavior harder to reason about.
Choose the project’s package manager and use it consistently across:
- Local development
- CI
- Production build
14. Check TypeScript Build Errors
During development, you may overlook TypeScript warnings or issues that become blocking during a production build.
Run:
npm run build
and fix genuine type problems rather than automatically disabling checks.
For example:
type User = {
id: number;
name: string;
};
const user: User = {
id: 1,
};
The missing name should be fixed according to your intended data model.
Avoid treating:
ignoreBuildErrors: true
as the default solution.
That hides problems rather than fixing them.
15. Check ESLint and Build Validation
Depending on your Next.js version and project configuration, linting may be part of your local/CI workflow rather than automatically blocking next build.
Either way, run your configured quality checks before deployment.
For example, if your project defines:
npm run lint
run it explicitly.
The goal is simple:
Don’t let production be the first environment where code quality and build problems are discovered.
16. Check Production API URLs
Your Next.js frontend may load correctly while API calls fail.
Open browser developer tools:
Developer Tools → Network
Look at failed requests.
You may discover:
http://localhost:8000/api/login
instead of:
https://api.example.com/api/login
Or perhaps the request reaches production but returns:
401
403
404
500
These status codes point toward very different problems.
Don’t label every failed API request as “Next.js deployment issue.”
17. Check CORS
If your frontend and API use different origins, CORS may matter for browser requests.
For example:
Frontend
https://www.example.com
API
https://api.example.com
Your backend needs to allow the intended frontend origin according to your architecture.
A CORS error usually appears clearly in the browser console.
Don’t use:
Access-Control-Allow-Origin: *
as a blind solution, especially for credentialed/private APIs.
Configure the required origins and credentials correctly.
18. Check HTTPS and Mixed Content
Your production Next.js site may run at:
https://example.com
while your API is configured as:
http://api.example.com
Browsers may block insecure HTTP resources requested from an HTTPS page.
The console may report a mixed-content error.
For production:
Frontend → HTTPS
API → HTTPS
Assets → HTTPS
is the preferred configuration.
Fix the SSL/API configuration instead of trying to bypass browser security.
19. Check Authentication Cookies
Authentication can work locally but fail after deployment because cookie behavior changes across HTTPS, domains, and subdomains.
Review settings such as:
Secure
HttpOnly
SameSite
Domain
Path
For example:
app.example.com
api.example.com
requires more deliberate cookie and CORS configuration than:
localhost:3000
localhost:8000
Inspect the browser’s cookie storage and network requests.
Ask:
- Was the authentication cookie created?
- Is it sent with the next request?
- Is the domain correct?
- Does HTTPS require
Secure? - Does the frontend request include credentials where required?
This is much more useful than repeatedly changing login UI code.
20. Check Production Database Connectivity
Some Next.js applications connect to a database from server-side code.
Your local database may be:
localhost
while production requires a remote database hostname.
Verify:
Database host
Database name
Username
Password
Port
SSL requirements
Network/firewall access
Also confirm the production server is permitted to connect to the database.
A database connection error may surface as a generic:
500 Internal Server Error
in the browser.
Check server logs for the actual exception.
21. Check Database Migrations
Deploying new application code without the corresponding database migration can break production.
For example, your new code expects:
users.profile_image
but the production database doesn’t have that column.
Locally everything works because your development database is current.
Production fails.
Include database migrations in your deployment process according to the ORM/database tooling you’re using.
And back up important production data before risky schema changes.
22. Check Dynamic Routes
Suppose you have:
app/products/[slug]/page.js
A URL such as:
/products/blue-shirt
works locally but returns 404 after deployment.
Check:
- Route directory naming
- Dynamic parameter handling
- Build/static-generation behavior
- Data source availability
- Rewrites
- Hosting configuration
If routes are generated statically, verify that your production build has access to the data needed to generate them.
23. Check Static Generation and Build-Time API Calls
This is a subtle but important production issue.
Suppose your build process fetches:
http://localhost:8000/api/products
to generate pages.
Your development computer has that API running.
Your production build server doesn’t.
The result may be:
Failed to fetch
or:
Failed to collect page data
Ask:
Does this data need to be available during build time, request time, or in the browser?
Then design the fetching strategy accordingly.
24. Check Image Configuration
Next.js image handling may require explicit configuration for remote sources.
If your images are hosted on another domain, configure approved remote sources using the approach supported by your Next.js version.
For example, modern configurations commonly use remotePatterns:
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
},
],
},
};
export default nextConfig;
If images work locally through one source but fail in production through another CDN/domain, check the final image host.
25. Check next.config.js
Production behavior can be affected by your Next.js configuration.
Review:
next.config.js
or the relevant config file used by your project.
Look for:
- Redirects
- Rewrites
- Image configuration
- Headers
- Base path
- Output mode
- Environment-related configuration
A configuration written for local development may not match your production deployment architecture.
26. Check Reverse Proxy Configuration
If you’re deploying Next.js on your own VPS, you may put Nginx or Apache in front of the Node.js application.
Conceptually:
Browser
↓
Nginx
↓
Next.js
↓
Node.js Port 3000
Nginx might proxy:
https://example.com
to:
http://127.0.0.1:3000
If the proxy configuration is wrong, the Next.js application may be running perfectly but remain inaccessible.
Check:
- Upstream port
- Host headers
- Proxy headers
- SSL
- Redirects
- WebSocket requirements where relevant
- Request-size/time limits where relevant
27. Check Whether the Next.js Process Is Running
On a self-managed server, don’t assume deployment means the Node process is still running.
If you manually execute:
npm start
and then close your SSH session, your process management setup determines what happens next.
Production servers commonly use a process supervisor such as systemd, a container platform, or another supported process-management approach.
The goal is:
Server reboot
↓
Application starts
Application crashes
↓
Supervisor handles restart/logging according to policy
Don’t depend on a terminal window staying open.
28. Check Production Logs
If the browser says:
500 Internal Server Error
the browser may not contain enough information.
Inspect the server/deployment logs.
Depending on your hosting setup, logs may reveal:
Environment variable missing
Database connection refused
Module not found
Permission denied
TypeError...
The frontend tells you that something failed.
The production logs often tell you why it failed.
29. Check File-System Permissions
Self-hosted deployments can fail because the Node process cannot access required files or directories.
Do not respond by blindly applying:
chmod -R 777
to your application.
That is an unsafe shortcut.
Instead, configure correct ownership and the minimum permissions required by your deployment user/process.
30. Don’t Depend on Writable Local Disk
Modern deployments may use containers or serverless infrastructure where local disk should not be treated as permanent application storage.
For example, this is usually a poor production upload strategy:
User uploads image
↓
Next.js server local folder
↓
Assume it stays forever
Depending on your hosting environment, that file may disappear when an instance is replaced or another instance handles the next request.
Use appropriate persistent storage such as object storage or a dedicated media service when the application requires durable uploads.
31. Check Linux Case Sensitivity for Static Assets
The same case-sensitivity problem can affect files in public.
Suppose the file is:
public/images/Product.jpg
but your code requests:
/images/product.jpg
This can work on one machine and fail on another.
Keep paths consistent.
32. Clear Stale Build Output Carefully
If deployment behavior doesn’t reflect recent changes, create a clean build.
Locally:
rm -rf .next
npm run build
In CI/deployment systems, ensure you’re not unintentionally serving stale artifacts.
Don’t make “delete random caches” your first troubleshooting strategy, but use clean builds when investigating stale output.
33. Check Your Hosting Platform Requirements
Next.js can be deployed in several ways:
- Managed Next.js hosting
- Node.js server
- Docker/container
- Serverless environment
- Static export for compatible applications
These aren’t interchangeable.
A project using server-side functionality cannot automatically be treated like a simple collection of static HTML files.
Before deploying, determine whether your application uses:
- Server Components
- Route handlers
- Server-side authentication
- Dynamic rendering
- Middleware
- Image optimization
- Server actions
- Runtime database access
Then choose a deployment model that supports the features your project actually uses.
34. Don’t Upload Only the .next Folder Blindly
On shared/VPS hosting, developers sometimes assume deployment means copying .next and nothing else.
Your production setup may also require:
package.json- Lock file
- Runtime dependencies
- Public assets
- Next.js configuration
- Environment configuration
- Server/process configuration
The exact deployment output depends on how your project is configured.
For certain self-hosted scenarios, Next.js supports standalone output, but that should be deliberately configured and deployed according to the framework’s documented structure.
35. Check Standalone Output for Self-Hosting
For suitable self-hosted deployments, you may configure:
const nextConfig = {
output: 'standalone',
};
export default nextConfig;
A production build can then generate a more self-contained server deployment output.
This can be particularly useful for containers and controlled Node.js deployments.
However, don’t enable standalone output merely because an application currently fails.
First identify whether the problem is actually related to your deployment packaging.
36. Check Memory and Server Resources
A production build can require significantly more memory than serving a basic website.
On a small server, you may see the build terminate unexpectedly.
Check server logs and system resources if:
npm run build
is killed or exits without a meaningful application error.
Potential constraints include:
- RAM
- Disk space
- Process limits
- Hosting build limits
If a build works locally but repeatedly dies on a low-resource server, investigate resource constraints rather than rewriting working pages.
37. Check API Rate Limits and Production Restrictions
Development traffic is usually tiny.
Production traffic is not.
An external API may enforce:
- Rate limits
- Allowed domains
- Allowed IPs
- Production API keys
- Quotas
- Separate test/live credentials
If an integration works locally but fails after launch, verify its production configuration and provider logs.
38. Check Third-Party OAuth Redirect URLs
OAuth integrations often use different callback URLs in development.
For example:
http://localhost:3000/api/auth/callback/provider
Production may require:
https://example.com/api/auth/callback/provider
If the production callback isn’t registered with the provider, authentication can fail even though your Next.js code hasn’t changed.
Check the provider dashboard and your application’s production base URL.
39. Check Build and Runtime Secrets Separately
A deployment pipeline may involve:
Git Repository
↓
Build Server
↓
Production Runtime
A secret available at runtime may not necessarily have been available during the build, and vice versa.
Determine when each configuration value is needed.
This is especially important for:
- Static generation
- Client-side environment variables
- Build-time API access
- Runtime database access
40. Use a Deployment Checklist Instead of Guessing
When production fails, debug in order.
Start with:
Does npm run build work locally?
Then:
Does npm start work locally?
Then:
Does the production build complete?
Then:
Does the server process start?
Then:
Does the homepage respond?
Then:
Do APIs work?
Then:
Does authentication work?
Then test individual production features.
This converts:
“Next.js doesn’t work in production”
into a specific failure you can investigate.
A Practical Example: Next.js API Works Locally but Not Production
Suppose you have:
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/products`
);
Locally:
NEXT_PUBLIC_API_URL=http://localhost:8000/api
Everything works.
After deployment, the browser shows:
GET http://localhost:8000/api/products
ERR_CONNECTION_REFUSED
Step 1 — Inspect the Network request
The production browser is still requesting localhost.
Step 2 — Check the production environment
The production API URL wasn’t configured correctly before the frontend build.
Step 3 — Configure it
For example:
NEXT_PUBLIC_API_URL=https://api.example.com/api
Step 4 — Rebuild
Create a fresh production build with the correct configuration.
The problem wasn’t React, fetch(), or the API endpoint.
The production bundle simply contained the wrong API URL.
A Practical Example: Next.js Works on Windows but Fails on Linux
Suppose your project contains:
components/UserCard.jsx
but the import is:
import UserCard from '@/components/userCard';
The project appears fine on a case-insensitive local filesystem.
Production Linux reports:
Module not found
Changing the import to match the actual filename exactly:
import UserCard from '@/components/UserCard';
solves the problem.
This is why production-like builds should be tested before deployment.
A Practical Example: Login Works Locally but Not Production
Imagine:
Local:
http://localhost:3000
↓
Login works
Production:
https://app.example.com
↓
Login succeeds
↓
Next request says Unauthorized
Instead of rewriting authentication, inspect the request.
You discover that the authentication cookie isn’t being sent.
Now investigate:
Secure
SameSite
Domain
Credentials
CORS
HTTPS
The login form wasn’t the problem.
The production cookie configuration was.
Next.js Production Troubleshooting Checklist
Before changing application code, verify:
npm run buildsucceeds locallynpm startworks locally- Node.js version is compatible
- Production environment variables exist
- Client-visible variables are configured correctly
- No production request points to localhost
- API uses HTTPS
- SSL certificates are valid
- Browser/server code boundaries are correct
- File and import case matches exactly
- Dependencies are declared
- Lock file is committed
- Package manager is consistent
- TypeScript checks pass
- Project lint/quality checks pass
- Production API URLs are correct
- CORS is configured
- Authentication cookies work
- Database is reachable
- Database migrations are current
- Dynamic routes work
- Build-time APIs are accessible
- Remote image sources are configured
- Next.js configuration matches production
- Reverse proxy is correct
- Node process is actually running
- Production logs are checked
- File permissions are correct
- Persistent files aren’t stored incorrectly
- Static asset case matches
- Deployment artifacts aren’t stale
- Hosting supports required Next.js features
- Server has sufficient resources
- OAuth production callbacks are registered
- Third-party production credentials are correct
Change one variable at a time.
Otherwise, you may fix the problem without understanding it—or create a second production issue.
How to Prevent Next.js Production Deployment Problems
Test Production Builds Before Every Important Deployment
Make this part of your normal workflow:
npm run build
npm start
Don’t let your production server be the first place where a production build is tested.
Keep Environment Configuration Explicit
Maintain a clear list of required environment variables.
For example:
DATABASE_URL
AUTH_SECRET
NEXT_PUBLIC_API_URL
Never depend on remembering which variables need to exist.
Keep Secrets Out of Client Code
Only expose configuration that is safe for visitors to see.
Database passwords, private API secrets and privileged credentials belong on the server.
Keep Development and Production Similar
Large environmental differences create deployment surprises.
Where practical, keep versions and deployment behavior consistent across:
Development
CI/Staging
Production
Use a Staging Environment
For important applications:
Development
↓
Staging
↓
Production
is safer than deploying every change directly from a developer laptop to production.
Monitor Production Errors
Production debugging shouldn’t depend entirely on a customer sending a screenshot.
Use appropriate application/server logging and error monitoring while ensuring that passwords, tokens and sensitive personal information are not logged.
Back Up Before Significant Changes
If your deployment includes database migrations or infrastructure changes, make sure you have a tested recovery plan.
Final Thoughts
When a Next.js app works locally but fails in production, don’t start by randomly rewriting components.
Trace the deployment lifecycle:
Source Code
↓
Dependencies
↓
Environment Variables
↓
npm run build
↓
Production Runtime
↓
Reverse Proxy / Hosting
↓
API + Database
↓
Browser
Find the first stage that fails.
If:
npm run build
fails, investigate the application/build configuration.
If the build succeeds locally but fails on the server, compare environments.
If the application loads but API requests fail, inspect the Network tab and backend logs.
If authentication alone fails, inspect cookies, callback URLs and production credentials.
A systematic approach is faster—and considerably safer—than making unrelated changes until the site starts working.
Need Web Application Help?
Need Help With a Next.js Production or Deployment Issue?
KDP Infusion can help troubleshoot Next.js applications, production deployments, API integrations, authentication problems, Node.js hosting, database connectivity and full-stack application issues.
- React and Next.js development
- Node.js and Express API development
- Frontend and backend troubleshooting
- REST API integration
- Performance and deployment optimization
Frequently Asked Questions
The production environment may have different Node.js versions, environment variables, API URLs, database access, file-system behavior, signing/authentication configuration or hosting capabilities. Start by testing npm run build and npm start locally.
Development mode and a production build follow different execution and optimization paths. The build may expose TypeScript problems, invalid imports, server/client execution issues, build-time data-fetching failures or missing configuration.
They may not be configured in the production environment, may be required during the build rather than runtime, or may be server-only when you're trying to access them from client code.
Check for localhost URLs, incorrect production environment variables, HTTP/HTTPS problems, CORS, authentication, firewall restrictions and production API availability.
Some code is executing in a server environment where browser globals such as window don't exist. Move genuinely browser-dependent behavior into the appropriate client-side execution context.
One common cause is filename/import case mismatch on a case-sensitive Linux filesystem. Missing dependencies or incorrect aliases can also cause this error.
Check production callback URLs, HTTPS, cookies, Secure, SameSite, domain settings, CORS, environment variables and OAuth provider configuration.
A clean .next rebuild can help with stale artifacts, but it should not be your first response to every production error. Read the build/server logs first and identify the actual failure.
It depends on the application and hosting environment. A fully static-compatible site can be deployed differently from a Next.js application requiring a persistent Node.js runtime, server-side features, route handlers or other dynamic capabilities. Verify that your hosting supports the features your project uses.
Standalone output can simplify some self-hosted/container deployments, but it isn't a universal fix. Choose it when it matches your deployment architecture rather than using it to hide an unidentified application error.