TidyURLs
Go to Tools
BLOG
The TidyURLsJournal

Practical guides, tutorials, and insights about URLs, SEO, web development, and online marketing. Learn, troubleshoot, and get the most of the web.

Back to the Journal
TidyURLs Journal

Why Your Next.js Website Works Locally but Fails After Deployment

Practical insights, clear explanations, and useful ideas for understanding the web.

October 25, 202613 min read7 topics
Why Your Next.js Website Works Locally but Fails After Deployment
Explore the details

Advertisement

#Next.js#React#Deployment#DNS#TLS#Caching#Debugging
October 25, 202613 min read

A Next.js app can feel perfect on your laptop and still break the moment it reaches production. The usual complaint is simple: Next.js works locally but not in production. The causes are rarely random. Production changes the build mode, environment variables, domain, TLS certificate, cache behavior, runtime, region, and logging surface.

This guide walks through the most common deployment failures Next.js developers face, with practical checks for teams shipping from India to global or India-focused audiences. You will learn how to diagnose environment variable issues, DNS and HTTPS problems, build failures, stale caches, API errors, and missing production logs without guessing.

Key takeaways

  • Always reproduce the issue with a production build locally before blaming the hosting platform.
  • Most deployment-only failures come from missing environment variables, build-time variable behavior, case-sensitive imports, server/client runtime differences, or cached data.
  • DNS and TLS issues are especially common after custom domain setup and may appear differently across Indian ISPs due to resolver caching.
  • Production logs are essential. Browser console errors alone rarely show server-side failures in Route Handlers, Server Components, middleware, or server actions.
  • A URL returning 200, 301, 404, 500, or a TLS error tells you where to debug next; use status checks before changing application code.

First, reproduce production locally

Development mode is intentionally forgiving. next dev prioritizes fast refresh, verbose errors, and developer convenience. Production uses optimized builds, static generation, server rendering, minification, caching, and stricter runtime behavior.

Before changing DNS records or rewriting code, run the production path locally:

npm ci
npm run build
npm run start

If you use pnpm or yarn, keep the package manager consistent with your lockfile:

pnpm install --frozen-lockfile
pnpm build
pnpm start

Check these differences:

Local developmentProduction deployment
Runs with next devRuns after next build
Often reads .env.localReads environment variables configured in the host
May show detailed overlay errorsMay show generic 500 pages to users
Less aggressive cachingStatic rendering, fetch caching, ISR, and CDN caching can apply
Runs on your machineRuns on the provider runtime, region, and OS

If the production build fails locally, you have an application issue. If it only fails on the host, compare environment variables, Node.js version, build command, output directory, install command, and runtime logs.

Advertisement

Environment variables: the most common reason Next.js works locally but not in production

Environment variable mistakes are the top source of deployment-only bugs. Locally, your app may read .env.local; in production, that file is not automatically available unless you explicitly configure equivalent variables in your hosting provider.

Check server-only versus browser-exposed variables

In Next.js, variables without the NEXT_PUBLIC_ prefix are intended for server-side use. Variables prefixed with NEXT_PUBLIC_ are bundled for browser access and should never contain secrets.

Common failure patterns:

  • process.env.DATABASE_URL works locally but is missing in the production project settings.
  • NEXT_PUBLIC_API_BASE_URL points to http://localhost:3000 after deployment.
  • A secret is accidentally exposed with NEXT_PUBLIC_.
  • A variable was added in the hosting dashboard but the app was not redeployed.
  • Preview, staging, and production environments have different variable values.

Remember build-time inlining

Public variables such as NEXT_PUBLIC_API_BASE_URL can be embedded into the client bundle at build time. If you change them after a build, the deployed JavaScript may still contain the old value until you rebuild and redeploy.

A safe pattern is to validate required server variables early:

const requiredEnv = ['DATABASE_URL', 'NEXTAUTH_SECRET'] as const;

for (const key of requiredEnv) {
  if (!process.env[key]) {
    throw new Error(`Missing required environment variable: ${key}`);
  }
}

export const env = {
  databaseUrl: process.env.DATABASE_URL!,
  nextAuthSecret: process.env.NEXTAUTH_SECRET!,
};

This example is useful for catching misconfiguration, but do not import server-only environment modules into Client Components.

India-specific API base URL mistake

Many teams test locally with an API running on localhost, then deploy the frontend to a global host while the backend is on a VPS, internal network, or India-only cloud region. In production, localhost means the hosting server or serverless function itself, not your laptop.

Use fully qualified production URLs for deployed services, for example:

NEXT_PUBLIC_API_BASE_URL=https://api.example.in

For server-side calls, keep secrets and private API URLs server-only when possible.

Build failures and runtime differences

A Next.js deployment can fail during install, build, or runtime. Each stage points to a different class of problem.

Install failures

Look for package manager and dependency issues:

  • The lockfile was generated with npm but the host uses yarn or pnpm.
  • A native dependency requires build tools unavailable in the hosting image.
  • Node.js version differs from your local machine.
  • Optional dependencies behave differently on Linux than on macOS or Windows.

Set the Node.js version explicitly in your project or hosting settings where supported. Avoid relying on whatever version happens to be installed locally.

Build failures

Production builds often reveal problems hidden in development:

  • Case-sensitive import mismatches, such as importing ./Navbar when the file is ./NavBar.tsx. This may work on some local file systems but fail on Linux.
  • Server Components importing browser-only APIs such as window, document, or localStorage.
  • Client Components missing the appropriate client boundary.
  • TypeScript errors that were ignored locally.
  • API calls during static generation failing because production credentials are missing.

If your page fetches data at build time, a temporary API outage can break deployment. Decide whether the page should be static, dynamically rendered, or generated with revalidation.

Runtime failures

A successful build does not guarantee a working site. Runtime errors appear when users request pages, call API routes, submit forms, or trigger server actions.

Examples include:

  • Database connection refused from the provider region.
  • Missing runtime-only environment variables.
  • Edge runtime code importing Node.js-only modules.
  • Middleware redirect loops.
  • Image optimization failing because remote image domains are not configured correctly.

If you configure a route for Edge runtime, verify that every dependency is compatible with the Edge environment. If you need Node.js APIs, use the Node.js runtime instead:

export const runtime = 'nodejs';

Use HTTP status codes to narrow the problem

When a deployed page fails, start by identifying the response. A status code gives you a faster path than guessing from screenshots.

SymptomLikely area to inspect
DNS lookup failsDomain records, nameservers, registrar setup
TLS or certificate errorHTTPS certificate, CAA records, proxy configuration
404Routing, base path, dynamic route, deployment output
500Server logs, environment variables, database/API failures
502 or 504Upstream function, server timeout, backend availability
301 or 308 loopRedirect rules, middleware, canonical host logic

You can use an HTTP status checker to confirm whether a deployed URL returns a success, redirect, client error, or server error before digging through code.

For command-line checks, use:

curl -I https://example.in
curl -I https://www.example.in

Compare the apex domain and www subdomain. Many issues affect only one of them.

DNS and TLS problems after deployment

Custom domain setup is outside your React code, but it can make a healthy app look broken.

DNS records

Follow your hosting provider documentation exactly. Common patterns include:

  • An apex domain such as example.in pointing to provider-specific A, ALIAS, ANAME, or flattened CNAME records.
  • A subdomain such as www.example.in pointing to a CNAME target.
  • Nameservers delegated from your registrar to your DNS provider.

Avoid mixing old and new records unless you understand the provider requirements. Duplicate A records, stale CNAMEs, and incorrect nameservers can produce inconsistent results.

Useful checks:

dig example.in
dig www.example.in
dig +trace example.in

If some users in India see the old site while others see the new one, resolver caching is a likely explanation. DNS changes take time to propagate through recursive resolvers, and different ISPs may refresh at different times depending on TTL and cache state.

TLS certificates

A production site should serve HTTPS correctly for every public hostname. TLS issues often happen when:

  • DNS is not pointing to the host yet, so the provider cannot validate the domain.
  • The domain was added without the www variant or vice versa.
  • CAA records restrict which certificate authorities may issue certificates.
  • A CDN or proxy is configured with an incompatible SSL mode.
  • The frontend calls an http:// API from an https:// page, causing mixed content errors in the browser.

When debugging TLS, test the exact hostname users visit. https://example.in and https://www.example.in are different hosts and may have different certificates, redirects, and DNS records.

Advertisement

Caching differences between local and production

Caching can make a fixed bug appear unfixed. Next.js production deployments may involve several layers:

  • Browser cache
  • Next.js data cache
  • Static rendering output
  • Incremental Static Regeneration
  • Hosting CDN cache
  • External API or database cache

Fetch caching in App Router

In the App Router, data fetching behavior depends on how a route is rendered and how fetch is configured. If a page should update periodically, use revalidation intentionally:

export const revalidate = 60;

export default async function ProductsPage() {
  const res = await fetch(`${process.env.API_BASE_URL}/products`, {
    next: { revalidate },
  });

  if (!res.ok) {
    throw new Error(`Products API failed with ${res.status}`);
  }

  const products = await res.json();
  return <pre>{JSON.stringify(products, null, 2)}</pre>;
}

If the data must always be fresh for each request, use no-store carefully:

await fetch(`${process.env.API_BASE_URL}/account`, { cache: 'no-store' });

Fresh data costs more in latency and backend load. For Indian users, region placement matters too. A Mumbai user calling a distant backend on every request may see slower responses than a statically rendered or cached page.

Redirect and middleware caching

Redirect logic can be cached by browsers and CDNs. If you accidentally deploy a redirect from /login to /login, users may experience a loop even after a quick fix until caches clear.

Check:

  • next.config.js redirects
  • Middleware redirects
  • Hosting dashboard redirect rules
  • CDN cache rules
  • Trailing slash and locale handling

Production logs: where the real error usually is

A browser may only show a generic error, but the server log contains the stack trace. Inspect logs at the correct level:

  • Build logs: dependency install, compilation, static generation, type errors.
  • Runtime logs: server rendering, Route Handlers, API routes, server actions, middleware.
  • Edge logs: middleware or Edge runtime errors, if your host separates them.
  • External service logs: database, payment gateway, CMS, search service, or custom backend.

When reading logs, align timestamps carefully. Hosting dashboards often display UTC, while Indian teams commonly discuss incidents in IST. Note the exact request time, URL, status code, deployment ID, and region if available.

Add structured logging for important server paths, but never log secrets, tokens, cookies, OTPs, payment details, or personal data.

Example minimal server-side log around an upstream request:

export async function GET() {
  const startedAt = Date.now();
  const res = await fetch(`${process.env.API_BASE_URL}/health`, {
    cache: 'no-store',
  });

  console.info({
    route: '/api/health-check',
    upstreamStatus: res.status,
    durationMs: Date.now() - startedAt,
  });

  return Response.json({ ok: res.ok }, { status: res.ok ? 200 : 502 });
}

This is intentionally small. In real applications, use your platform logging and monitoring tools, redact sensitive values, and avoid logging large payloads.

Step-by-step troubleshooting workflow

Use this workflow when Next.js works locally but not in production.

1. Confirm the deployed response

Check the exact production URL, including protocol and hostname:

curl -I https://example.in
curl -I https://www.example.in

Record the status code, redirect target, and any TLS error.

2. Check the latest deployment

Confirm that the URL points to the deployment you expect. It is easy to test an old deployment, wrong branch, preview URL, or a domain connected to another project.

3. Read build logs first

If the deployment failed, runtime debugging is irrelevant. Fix install or build errors first.

Look for:

  • Missing variables during static generation
  • Node.js version mismatch
  • Case-sensitive imports
  • Failed API calls during build
  • TypeScript or bundling errors

4. Compare environment variables

Create a checklist of required variables and compare local, preview, and production values. Do not print secret values in logs; log only whether required keys are present.

5. Check runtime logs for 500 errors

For 500, 502, or 504 responses, inspect server-side logs around the request time. Check database allowlists, firewall rules, API credentials, and provider region connectivity.

6. Verify DNS and HTTPS

Check apex and www separately. Confirm that DNS records match the host documentation and that the TLS certificate covers the hostname being visited.

7. Invalidate or bypass caches

If code and configuration look correct but users still see old behavior, test with:

curl -I -H 'Cache-Control: no-cache' https://example.in

Also try a private browser window, a different network, and the host or CDN cache purge mechanism if available.

8. Reproduce with production settings locally

Create a local .env.production.local only for safe local testing and run a production build. Do not commit this file.

npm run build
npm run start

If the issue appears locally with production values, you can debug faster without repeated deployments.

Common mistakes and pitfalls

Using localhost in production

localhost in deployed code does not point to your development machine. Use production service URLs or relative URLs where appropriate.

Assuming .env.local is deployed

Local env files are not a substitute for hosting environment configuration. Add variables in the hosting provider and redeploy.

Exposing secrets with NEXT_PUBLIC

Anything prefixed with NEXT_PUBLIC_ can be visible to browser users. Never use it for database URLs, API secrets, JWT signing secrets, payment keys that must remain private, or admin tokens.

Ignoring case sensitivity

A component import may work on a local machine but fail on Linux. Keep file names and imports exactly consistent.

Treating a URL parser as a security control

Parsing a URL is useful for understanding components, but it is not a complete security boundary. For server-side URL fetching, you still need authorization, allowlists where appropriate, DNS and redirect handling, network egress controls, and SSRF-aware design.

Forgetting that static pages can fail at build time

If a statically generated page depends on a CMS or API, that dependency must be reachable during build. Otherwise, use dynamic rendering, fallback behavior, or revalidation based on the user experience you need.

Checking only one hostname

Always test both apex and www if both are public. One may redirect correctly while the other fails TLS or points to an old deployment.

Defensive best practices for reliable deployments

  • Keep a small deployment checklist covering build command, install command, Node.js version, environment variables, domain records, and log locations.
  • Use separate development, preview, and production environment variables.
  • Validate required server variables during startup or first server-side use.
  • Avoid build-time API dependencies unless static generation is intentional.
  • Monitor status codes and error rates after every deployment.
  • Keep secrets server-side and rotate any secret accidentally exposed to the browser.
  • Document DNS records for example.in, www.example.in, and any API subdomains.
  • Use staging or preview deployments for major changes before pointing a production domain.
  • Add meaningful error handling for upstream API calls instead of assuming every response is 200.

Conclusion

When Next.js works locally but not in production, the fix usually starts with separating build problems, runtime problems, DNS or TLS problems, and caching problems. Run a production build locally, compare environment variables, inspect status codes, read production logs, and test the exact domain users visit.

The fastest teams do not guess. They collect the response code, deployment ID, build log, runtime log, environment checklist, and DNS result, then change one thing at a time. That approach works whether you host on Vercel or another platform and whether your users are in India, overseas, or both.

References