React Works in Development
but Fails After Production Build
“It works on my machine!” — The classic developer lament.
Your React app runs perfectly in npm start but breaks in production. This guide
provides a complete debugging framework to identify and fix production-only
issues, with real solutions for every common cause.
📑 Table of Contents
1. Introduction
You've just deployed your React app to production, and everything seems fine — until a user
reports a crash, or you see a blank white page. You test locally with npm start
and it works flawlessly. This is the production-only bug — one of the most
frustrating experiences in frontend development.
The differences between development and production are many: minified code, different environment variables, optimized builds, and different server configurations. This guide will walk you through the systematic process of identifying and fixing these elusive issues, covering everything from environment variables to service worker caching.
process.env variables that are not available in production, or relying on
development-only features like hot reloading.
2. Common Causes
Before diving into specific fixes, let's outline the usual suspects. Here's what changes between development and production builds:
- Environment variables:
NODE_ENV=developmentvsproduction, and custom variables likeREACT_APP_*may be missing. - Code minification: Variable names are shortened, which can break code
that relies on
function.nameorinstanceofchecks. - Dead code elimination: Tree-shaking removes unused code, which might be needed for side effects or polyfills.
- Server configuration: Production servers (Nginx, Apache, Vercel) may not handle client-side routing correctly.
- CORS policies: Production API endpoints often have stricter CORS rules.
- Service workers: Caching can cause stale assets to be served.
- Polyfills: Development servers often include polyfills automatically, but production builds may not.
- Lazy loading & code splitting: Dynamic imports might fail if paths are incorrect in production.
npm run build and serve it with a static server like serve -s build.
3. Environment Variables
In development, you often have a .env.development file with API endpoints and keys.
In production, you need a .env.production file or inject variables differently.
📌 How React Handles Environment Variables
With Create React App (CRA), only variables prefixed with REACT_APP_ are embedded
in the build. In Vite, you use VITE_ prefix. These variables are replaced at
build time, not at runtime.
Then in your code:
🔧 What to Check
- Ensure
.env.productionexists and has the correct values. - Check that you're not using
process.env.NODE_ENVfor logic that affects production. - For server-side rendered apps (Next.js), use
NEXT_PUBLIC_*variables. - Avoid using
process.envdirectly in frontend code without the prefix.
dotenv for Node.js backends,
and always commit .env.example but not the real .env files.
4. Minification & Dead Code
Production builds minify your code, rename variables, and remove code considered "unused." This can break things if you rely on:
Function.prototype.namefor component identification.instanceofchecks with custom classes.- Side effects that are removed by tree-shaking.
🔧 How to Fix
- Avoid using
function.namefor logic — use a static property or displayName. - Use
typeoforconstructor.namecarefully. - Mark files with side effects in
package.json("sideEffects": falsemay cause issues). - In CRA, you can customize the build with
react-app-rewiredorcracoto change minification settings.
To debug, you can build with source maps enabled to see the original code.
5. Routing & Server Configuration
React apps often use client-side routing (React Router). In development, the dev server
handles all routes. In production, your web server must be configured to serve
index.html for all routes, otherwise you get a 404 when refreshing a sub-route.
📌 Server Configuration Examples
Nginx:
Apache: Use a .htaccess file:
Vercel/Netlify: Use a _redirects file or vercel.json.
🔧 Check basename
If your app is deployed to a subdirectory, set the basename in React Router:
%PUBLIC_URL% in your index.html
to ensure asset paths are correct.
6. CORS & API Issues
In development, you might use a proxy to avoid CORS. In production, the proxy is not present, so your API requests go directly to the API server. This can trigger CORS errors if the server isn't configured correctly.
🔧 Fixes
- Ensure your API server includes CORS headers (
Access-Control-Allow-Origin) with your production domain. - If you control the API, add your production domain to the allowed origins.
- Alternatively, use a backend proxy (like a Node.js server) to forward requests, avoiding CORS entirely.
- In Next.js, use API routes to proxy requests.
7. Service Worker & Caching
If you're using a service worker (e.g., CRA's Workbox), it might cache old assets, causing a mismatch between the JavaScript bundle and the HTML. This can lead to errors that don't appear in development.
🔧 How to Fix
- Clear the service worker cache: In DevTools, go to Application → Service Workers and unregister.
- Ensure your service worker updates correctly by implementing versioning.
- In CRA, you can customize the service worker with
cracoor ejecting. - For a quick fix, you can disable the service worker in production (but this removes offline capabilities).
Also check browser caching: Sometimes the index.html is cached, causing old
assets to load. Use cache-control headers correctly.
8. Debugging Techniques
Here's how to systematically debug production-only issues.
📌 Step 1: Replicate Production Locally
Build your app and serve it with a static server:
Visit http://localhost:5000 and test. If the error doesn't appear here, the
issue is with your hosting environment.
📌 Step 2: Enable Source Maps
Build with source maps to get readable stack traces.
Then open the browser console and inspect the error.
📌 Step 3: Use Error Boundaries
Wrap your app with an error boundary to catch errors and display a fallback UI.
📌 Step 4: Use Console Logs in Production
You can keep some console.log statements in production (be careful not to leak
sensitive info). They can help trace the flow.
📌 Step 5: Leverage Logging Services
Tools like Sentry, LogRocket, or Datadog can capture production errors and provide context.
📌 Step 6: Analyze Bundle
Use source-map-explorer to visualize bundle composition.
This helps find duplicate dependencies or unexpected large files.
10. Best Practices
- Test production build locally: Always run
npm run buildand serve it locally before deploying. - Use environment variables correctly: Separate development and production configurations.
- Implement error boundaries: Catch runtime errors gracefully.
- Use logging services: Integrate Sentry or similar to monitor production errors.
- Set up CI/CD pipelines: Automatically build and test your app before deployment.
- Optimize bundle: Use code splitting and lazy loading to reduce bundle size.
- Configure server for SPA routing: Ensure
index.htmlis served for all routes. - Manage caching: Use proper Cache-Control headers and versioned asset filenames.
- Test on multiple browsers: Production errors can be browser-specific.
- Keep dependencies updated: Outdated packages can cause production issues.
0 Comments
thanks for your comments!