Why Your App Works Locally But Fails in Production
Real reasons and concrete fixes. AI optimizes for a working demo, not a production system. With 15–30 silent architectural decisions per feature at ~70% accuracy each, the probability of ALL being correct is practically zero.
“It works on my machine” is the oldest joke in software engineering. With AI-generated apps, it’s not a joke — it’s the default state. AI doesn’t think about production. It doesn’t know what a load balancer, connection pool, or secrets manager is. It sees only one thing: your local context, your file, the code running on your machine right now.
When AI builds a feature, it makes 15–30 silent architectural decisions: which database, how to connect to an API, where to store keys, how to handle errors. Each decision has roughly a 70% chance of being correct. But for the entire feature to work in production, all decisions must be correct. The probability? With 20 decisions: 0.7^20 = 0.08%. Practically zero.
This article covers the six most common reasons AI apps work locally and crash in production — with concrete examples and fixes. If you’re building with Cursor, Bolt, Lovable, Replit, or any other AI tool, this guide is for you.
Environment & Configuration
This is the most obvious and simultaneously most common reason for production failures: configuration that only works locally. Your .env file on your laptop has real API keys, correct URLs, working secrets. In production? Placeholders, missing variables, or entirely different addresses.
AI never thinks about configuration management — it only sees local context. When it generates code that connects to an API, it writes http://localhost:3000 directly in the code. When it needs an API key, it creates a variable with a default value. When it builds a database URL, it hardcodes the connection string.
Typical problems:
- Hardcoded URLs —
http://localhost:3000/apiinstead of an environment variable. Works locally, points to nowhere in production. - Missing environment variables — in production you don’t have a
.envfile. You need to set variables in your hosting panel (Vercel, Railway, Render). - HTTP vs HTTPS — locally you use
http://, production requireshttps://. Mixing protocols causes browser blocking (mixed content) and CORS errors. - Different API keys — Stripe test key vs production key. Firebase dev key vs prod. AI doesn’t know the difference exists.
How to fix it
Externalize ALL configuration. Every URL, every key, every secret should come from an environment variable — never from source code. Use a secrets manager (e.g., Doppler, AWS Secrets Manager, Vercel Environment Variables). Create an .env.example file with the name of every required variable (without values) and add it to the repository as documentation.
Database Assumptions
AI loves SQLite. It’s simple, requires no server, it’s just a file. Perfect for a prototype. The problem? Serverless platforms (Vercel, Netlify Functions, AWS Lambda) don’t support SQLite because each request can hit a different instance — there’s no shared filesystem.
But the database isn’t just the engine. It’s a whole set of assumptions AI makes silently:
- Missing indexes — a query runs instantly on 50 rows. On 50,000? It takes 30 seconds. AI doesn’t add indexes because on small data there’s no visible difference.
- No connection pooling — every HTTP request opens a new database connection. With 100 concurrent users, you have 100 open connections. Databases have limits — usually 20–100 connections. Exceed that: crash.
- Schema that looks fine in a demo — no relations, no constraints, no types. Data is inconsistent, but you can’t tell with 50 rows.
- No migrations — AI creates tables “on the fly.” In production you can’t drop the database and recreate it — you have real user data.
How to fix it
Use PostgreSQL or MySQL in production. Configure connection pooling (PgBouncer, Supabase has it built-in). Add indexes on columns used in WHERE and JOIN. Plan a migration strategy — use a tool like Prisma Migrate, Drizzle, or plain SQL files with versioning. Never modify your production schema manually.
Security That Doesn’t Exist
Veracode’s 2025 GenAI Code Security Report found that LLMs introduced an OWASP Top 10 vulnerability in 45% of test cases. Georgetown’s CSET think tank got a similar result: almost half of the code snippets their evaluation produced contained exploitable bugs. This isn’t a marginal problem. And code that “works” on localhost doesn’t have to be secure.
A scan of 5,600 vibe-coded apps by Escape.tech revealed over 2,000 high-impact vulnerabilities and over 400 exposed secrets. The Moltbook agent platform exposed 1.5 million API tokens through a misconfigured Supabase database with no effective Row Level Security. And a scan of Lovable-built apps found 170 projects — roughly one in ten of those analyzed — exposing their databases through missing RLS (CVE-2025-48757): anyone unauthenticated could read and write arbitrary tables.
Problems I see regularly:
- API keys hardcoded in frontend JavaScript — anyone can open DevTools and copy them. I’ve seen this multiple times in client applications.
- No input validation — SQL injection, XSS, path traversal — AI rarely adds validation because on localhost nobody tries to break into your application.
- No Row Level Security — Supabase without RLS means every user can read and modify every other user’s data.
- No rate limiting — without limits, someone can call your API a million times a minute. Result: a huge API bill, a DDoS, or both.
How to fix it
Run an OWASP Top 10 audit. Enable Row Level Security on Supabase. Validate all inputs server-side (never trust the client). Scan your repository for exposed secrets (use gitleaks or trufflehog). Add rate limiting on all public endpoints. Move API keys from frontend to backend.
Silent Failures & Resource Leaks
Your application works for the first 100 requests. Request 1,001 causes a crash — all database connections are exhausted. Why? Because AI never closed them. Every request opened a new connection, but none released it.
The same goes for file handles, WebSocket listeners, timers, and subscriptions. AI creates resources but never releases them. On localhost you don’t notice, because restarting the server every few minutes resets everything. In production the server runs continuously — and the resources run out.
Even worse are silent failures:
- Empty
try/catchblocks — payment fails, webhook doesn’t fire, but the user sees no error. Data disappears silently. - Race conditions — multiple concurrent API calls resolve in random order. No idempotency means duplicates or inconsistent data.
- Hot reload masks errors — in dev mode React shows warnings. In the production build they vanish. One component throws — the entire app goes white because there are no error boundaries.
- No global error handler — an unhandled exception kills the Node.js process. On localhost, restarting is instant. In production, it’s downtime.
How to fix it
Add error boundaries on the frontend (React: ErrorBoundary). Add a global error handler on the backend (process.on('uncaughtException')). Deploy structured logging (Sentry, LogRocket, Pino). Close database connections after use (or use connection pooling). Clean up listeners and timers in useEffect cleanup. Load test — checking that it works once isn’t enough.
The Real-World Damage
These aren’t theoretical threats. These are incidents that have already happened:
- Amazon — four Sev-1 retail incidents in a single week, including a six-hour checkout outage that internal documents tied to roughly 6.3 million lost orders. Reporting linked the trend of incidents to GenAI-assisted changes; Amazon disputes that AI-written code was involved. Either way: even Amazon now requires extra review of AI-assisted production changes.
- Runaway cloud bills — AI-prototyped services deployed without cost controls routinely surprise their owners. AI doesn’t know that
t3.microcosts differently thanr5.4xlarge. Cost scaling is something AI never thinks about. - Stripe integrations — webhook handlers built against deprecated API shapes have shipped duplicate charges for weeks before anyone noticed.
- A Final Round AI survey of 18 CTOs — 16 reported production disasters directly caused by AI-generated code.
- CodeRabbit’s analysis of 470 open-source PRs — AI-authored code had ~1.7× more issues overall, roughly 2.7× more XSS-class findings, and ~8× more excessive-I/O performance problems than human-written PRs.
The pattern repeats: AI generates code that looks good. It passes code review (because it’s readable). It passes tests (because it tests the happy path). Then it explodes in production because nobody checked performance under load, error handling, security, or infrastructure costs.
How to Actually Fix It (Production-Ready Checklist)
Before you deploy your application, go through these points. Every unmet point is a potential production failure. This checklist was built from dozens of AI app audits I’ve conducted for clients.
- All config via environment variables. No hardcoded URLs, keys, or secrets in source code.
- Production database with indexes and connection pooling. Not SQLite. PostgreSQL or MySQL with PgBouncer or built-in pooler.
- HTTPS everywhere, security headers set. No mixing HTTP and HTTPS. CSP, HSTS, X-Frame-Options.
- Row-level security / privacy rules enabled. Every user sees only their own data.
- Error boundaries on frontend, global error handler on backend. No error can silently swallow data.
- Rate limiting on all public endpoints. Protection against abuse and DDoS.
- CI/CD pipeline with automated tests. Every push is tested before deployment.
- Monitoring and alerting (Sentry, CloudWatch). You hear about errors before your users do.
- Load tested with realistic traffic. 100 concurrent users, not one.
- Staging environment mirroring production. Test on a copy of production, not localhost.
- Database migration strategy. You plan schema changes instead of making them ad hoc.
- Backup and disaster recovery plan. What happens when the database goes down? Do you have an answer?
Frequently Asked Questions
Why does everything work perfectly on localhost?
Because localhost is a perfect environment: same-origin (no CORS), zero network latency, dev mode masks warnings, you’re the only user (no race conditions, no load). In production every one of these conditions is different — and any of them can cause a failure.
What’s the most common production failure?
Missing environment variables and hardcoded configuration. It’s trivial, but it accounts for the majority of “works locally, fails in production” issues. Your .env has keys — the production server doesn’t. AI hardcodes URLs in source code instead of using environment variables.
How do I know if my app is production-ready?
Simple question: if you can’t answer “what happens when X fails?” for every critical path, your app is not ready. What happens when the database is unavailable? When the Stripe API returns an error? When 100 users click “Buy” at the same second? If you don’t have answers — you have work to do.
Should I rewrite from scratch?
Usually no. Start with an audit. Identify critical gaps (security, database, configuration). Fix them point by point. A full rewrite only makes sense when the architecture is fundamentally flawed — but in most cases you can fix existing code iteratively without losing the work done so far.
Can I deploy to Vercel/Netlify and call it production?
Platform doesn’t equal production-ready. Vercel and Netlify are great hosting tools, but hosting alone isn’t “production.” You still need proper CORS configuration, monitoring, error handling, security, a proper database, and load testing. The “Deploy” button is the beginning, not the end.
Your app works locally but fails in production?
I’ll help you identify and fix every one of these issues. Audit, fix, deploy — from prototype to production.
Book a free call →