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 build succeeds locally
  • npm start works 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
Discuss Your Project →