Adam BlansettSenior Full-Stack & AI Engineer
Software Architecture
13 min read
Adam Blansett

How to Find Out Why Your App Works Locally but Fails in Production

A systematic engineering guide to diagnosing environment drift, missing build configurations, CORS, connection strings, and production runtime failures.

Production ReadinessDevOpsNext.jsEnvironment ConfigurationDebuggingFull-Stack

The phrase 'it works on my machine' has plagued software engineering for decades, but in the era of AI-generated code and serverless cloud hosting, the problem has taken on a sharper edge. An application builds cleanly in your local development environment or in an AI builder's browser preview. You deploy the repository to production hosting, open the custom domain, and immediately encounter blank screens, failing API requests, or authentication loops.

When software behaves differently in production than it does locally, developers often resort to desperate, unstructured troubleshooting: tweaking configuration variables at random, modifying build scripts directly in production dashboards, or repeatedly prompting an AI assistant to 'fix the deployment.' This approach rarely succeeds and frequently introduces secondary security vulnerabilities or corrupted configuration state.

Production failures are not random mysteries. They occur because specific runtime boundaries—networking, security contexts, environment variables, and build optimizations—operate differently in production than they do on localhost. This guide provides a systematic engineering framework to diagnose why your application fails in production and establish repeatable deployment reliability.

Diagnostic principle: Never guess at deployment bugs by changing multiple production settings simultaneously. Isolate one variable at a time: verify environment variables, inspect network requests, examine build output, and review runtime logs in a disciplined sequence.

1. Environment Variables and Configuration Drift

The single most frequent root cause of production deployment failures is configuration drift: the discrepancy between the environment variables defined in your local .env or .env.local file and the variables configured in your cloud hosting provider.

Build-Time vs. Runtime Variable Inlining

In modern JavaScript and TypeScript frameworks such as Next.js, environment variables operate under strict compilation boundaries:

  • Public Client Variables (e.g., NEXT_PUBLIC_API_URL): These are evaluated and statically replaced at compile/build time. If this variable is missing or incorrectly set during the 'npm run build' step on your CI/CD server, the bundle bakes in 'undefined' as a hardcoded string. Changing the variable later in your cloud hosting settings will have zero effect until you trigger a complete rebuild.
  • Private Server Variables (e.g., DATABASE_URL, STRIPE_SECRET_KEY): These are read dynamically at runtime by serverless functions or container runtimes. If they are absent, server routes crash upon first invocation.

A common mistake in AI-generated repositories is relying on a local .env file that is rightfully ignored by git (.gitignore). When the code is pushed to GitHub or deployed, the production environment lacks these values entirely, causing immediate runtime crashes.

2. Network Boundaries: Localhost Leaks and CORS

On your local machine, your frontend (running on port 3000) and your backend (running on port 8000) share the same machine network context. In production, your frontend and backend run on separate cloud hosts or different subdomains. This creates two distinct points of failure:

The Localhost Leak

AI code generators frequently write API clients with hardcoded base paths: const API = 'http://localhost:8000/api'. While this works during local development, when deployed to production, any customer opening your site has their browser attempt to connect to port 8000 on their own personal laptop. The browser console immediately logs 'net::ERR_CONNECTION_REFUSED'. Always verify that client requests use relative URLs (e.g., '/api/...') or an environment-injected public base URL.

Cross-Origin Resource Sharing (CORS) and Preflight Rejections

If your frontend is hosted on app.yourdomain.com and your API resides on api.yourdomain.com, the browser enforces strict CORS boundaries. For any non-simple HTTP request (including any request with an Authorization header or JSON Content-Type), the browser automatically dispatches an HTTP OPTIONS preflight request before sending the actual GET or POST.

illustrative-cors-preflight.httphttp
// Browser automatic preflight request
OPTIONS /api/v1/projects HTTP/1.1
Host: api.yourdomain.com
Origin: https://app.yourdomain.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

// Required successful server response
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.yourdomain.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true

If your backend server does not explicitly handle OPTIONS requests or omits the Access-Control-Allow-Origin header, the browser blocks the response completely. The request will fail in production despite working locally where CORS may have been disabled.

3. Missing Dependencies and Production Build Optimizations

Local development servers (like 'npm run dev') run in development mode, while production builds execute 'npm run build' followed by aggressive code bundling, minification, tree-shaking, and dead-code elimination. This reveals several failure modes that never appear locally:

  • devDependencies in Production: An essential library was accidentally installed under devDependencies in package.json. In production cloud pipelines that run 'npm install --production', that dependency is omitted, causing module not found errors at runtime.
  • Dynamic Imports and Minification: Code that relies on dynamic string evaluation (e.g. require(variableName)) breaks when variable names and module paths are mangled by Webpack or esbuild during production minification.
  • Case Sensitivity in File Systems: macOS and Windows file systems are case-insensitive by default. If you import './components/Button' but the file is named 'button.tsx', it compiles locally on a Mac. In Linux production build environments (Docker, GitHub Actions, Vercel), the build crashes immediately with 'Module not found'.

4. Database Connection Pooling Constraints

Locally, you typically run a single Node process communicating with a local PostgreSQL instance. In production serverless architectures (Next.js API routes on Vercel, AWS Lambda, or auto-scaling Cloud Run containers), each incoming HTTP request can spin up an independent compute instance.

If each serverless instance opens its own direct PostgreSQL database connection pool, a burst of 50 concurrent requests will attempt to open 500 database connections. The database's max_connections limit is rapidly exceeded, throwing 'FATAL: remaining connection slots are reserved for non-replication superuser connections'. To survive production traffic, serverless backends must connect through a connection pooler (such as Supavisor, PgBouncer, or AWS RDS Proxy).

5. Authentication, Cookies, and HTTPS Boundaries

Local development typically runs over unencrypted HTTP on localhost. Production mandates HTTPS with strict transport security. This difference alters how authentication cookies and session headers operate:

  • The Secure Cookie Flag: If a session cookie is marked Secure=true, modern browsers will reject it if transmitted over an insecure connection or through an misconfigured reverse proxy that strips SSL headers.
  • SameSite Policy: A cookie configured with SameSite=Strict will not be sent across cross-site navigations (such as returning from a third-party OAuth provider like Google or GitHub). This creates an infinite login loop in production that never occurred on localhost.
  • OAuth Redirect URI Mismatches: In your OAuth provider console, you registered 'http://localhost:3000/auth/callback'. In production, your application redirects to 'https://app.yourdomain.com/auth/callback'. The OAuth provider rejects the request with an invalid redirect URI error.

6. A Repeatable Troubleshooting Isolation Workflow

When an app fails in production, resist the urge to push hurried commits. Follow this step-by-step diagnostic workflow:

  • 1. Check the Deployment Build Logs: Log into your cloud hosting dashboard (Firebase, Cloud Run, Vercel, AWS) and review the build log. Confirm that 'npm run build' exited with code 0 and all expected routes were generated.
  • 2. Check Server Runtime Logs: Open the real-time runtime log stream. Trigger the error in your browser and inspect the exact error stack trace logged by the server. Identify the exact file and line number.
  • 3. Inspect Browser Network and Console: In DevTools, examine the failing request's exact Request URL, Response Headers, and Response Payload. Look for CORS blocks or HTTP 500 error messages.
  • 4. Validate Environment Variables: Compare every key in your local .env file against your cloud provider's environment variables. Verify that production URLs use https:// and contain no trailing slashes or typos.
  • 5. Test Production Build Locally: Run 'npm run build && npm run start' on your local development machine. This simulates production minification, bundling, and SSR behavior while keeping the environment under your direct control.

7. Production Deployment Readiness Checklist

Before pointing domain DNS to a new production deployment, ensure every item on this checklist is verified:

  • Zero Hardcoded Localhost: Repository is scanned for references to 'localhost' or '127.0.0.1' in client code.
  • Explicit Environment Schemas: Environment variables are validated on startup using an enforcement library (such as @t3-oss/env-nextjs or Zod) to crash early if a required key is missing.
  • CORS Whitelist Configured: API servers strictly whitelist production frontend domains with allowed headers and credentials support.
  • Connection Pooler Active: Relational databases use PgBouncer or a managed connection pooler to prevent serverless connection exhaustion.
  • Production OAuth URIs Registered: All third-party providers (Google, GitHub, Stripe) have verified production callback URLs.
  • Automated Health Check Route: An unauthenticated '/api/health' endpoint exists to verify database connectivity for cloud load balancers.

Conclusion and Next Steps

The difference between an application that works on localhost and one that thrives in production is architectural discipline. When production breaks, treat the failure as a diagnostic opportunity to identify missing configuration, network boundaries, or resource constraints.

To diagnose specific persistence and database write failures, read Why Your AI-Built App Fails to Save Data. For network protocol debugging, consult Why Frontend and Backend Integrations Fail. For comprehensive tool evaluations and deployment platforms, visit the AI Development Tools Resource Hub. If you are facing an urgent deployment failure, explore my Software Architecture and Full-Stack Engineering services, or book a consultation to resolve the roadblock.

Applied Architecture

Production Case Studies & Capabilities

Explore how these engineering patterns are deployed in production systems and available through client engagements.

Related Service

Software Architecture & System Design

Fast-moving teams frequently accrue hidden architectural liabilities: tangled domain logic, unmaintainable monoliths, or over-engineered microservices that paralyze development.

Explore Service Scope
Related Service

Full-Stack Engineering

Companies often struggle with fragile web applications, slow delivery cycles, and disjointed client-server boundaries. I build robust, production-grade applications that scale seamlessly from day one without architectural debt.

Explore Service Scope
Related Service

Technical Consulting & Advisory

Making the wrong technology choices, hiring the wrong vendor, or misjudging project scope can cost months of runway and hundreds of thousands of dollars.

Explore Service Scope

Written by Adam Blansett

Senior Full-Stack & AI Engineer designing production software across web, mobile, and cloud architectures.

Discuss This Topic

Related Technical Articles

Software Rescue
8 min read

My App Is Broken: How to Rescue a Web or Mobile App and Get It Production-Ready

What actually happens when a freelance engineer takes over an app that crashes, fails after launch, or only works on one laptop: how problems are found, what usually causes them, and when to repair instead of rewrite.

App RescueTroubleshootingFirebaseFull-StackAI IntegrationProduction Readiness
Read Article