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 development | Production deployment |
|---|---|
Runs with next dev | Runs after next build |
Often reads .env.local | Reads environment variables configured in the host |
| May show detailed overlay errors | May show generic 500 pages to users |
| Less aggressive caching | Static rendering, fetch caching, ISR, and CDN caching can apply |
| Runs on your machine | Runs 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_URLworks locally but is missing in the production project settings.NEXT_PUBLIC_API_BASE_URLpoints tohttp://localhost:3000after 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
./Navbarwhen 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, orlocalStorage. - 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.
| Symptom | Likely area to inspect |
|---|---|
| DNS lookup fails | Domain records, nameservers, registrar setup |
| TLS or certificate error | HTTPS certificate, CAA records, proxy configuration |
| 404 | Routing, base path, dynamic route, deployment output |
| 500 | Server logs, environment variables, database/API failures |
| 502 or 504 | Upstream function, server timeout, backend availability |
| 301 or 308 loop | Redirect 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.inpointing to provider-specific A, ALIAS, ANAME, or flattened CNAME records. - A subdomain such as
www.example.inpointing 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
wwwvariant 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 anhttps://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.jsredirects- 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.
