No articles found
Try different keywords or browse our categories
How to Fix React app works locally but not after build Error
Learn how to fix React apps that work locally but fail after build. Complete guide with solutions for production deployment and build optimization.
The ‘React app works locally but not after build’ issue is a common challenge developers face when deploying React applications. This problem occurs when code that functions perfectly in development mode fails in the production build due to differences in build configurations, optimizations, and environment settings.
This comprehensive guide provides complete solutions to resolve build-related issues with practical examples and deployment optimization techniques.
Understanding Build vs Development Differences
React applications behave differently in development and production due to various factors:
- Minification and optimization in production builds
- Environment variable differences
- Tree shaking and dead code elimination
- Different module resolution
- Production-specific error handling
Common Build Issues:
ReferenceError: process is not definedTypeError: Cannot read property of undefinedModule not founderrorsChunkLoadErrorin code splittingUncaught TypeErrorin production only
Common Causes and Solutions
1. Environment Variable Issues
Development and production environments handle environment variables differently.
❌ Problem Scenario:
// This works in development but fails in production
function BadComponent() {
// ❌ process.env is not available in browser after build
const apiUrl = process.env.REACT_APP_API_URL;
useEffect(() => {
fetch(apiUrl) // This might be undefined in production
.then(response => response.json())
.then(setData);
}, []);
return <div>{data}</div>;
}
✅ Solution: Proper Environment Handling
// Correct approach - handle environment variables safely
function GoodComponent() {
// ✅ Provide fallback values
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:3001/api';
useEffect(() => {
fetch(apiUrl)
.then(response => response.json())
.then(setData)
.catch(error => {
console.error('API Error:', error);
// Handle error gracefully
});
}, []);
return <div>{data}</div>;
}
// Alternative: Create a config file
// config.js
const config = {
apiUrl: process.env.REACT_APP_API_URL || 'http://localhost:3001/api',
debug: process.env.NODE_ENV === 'development'
};
export default config;
// Component using config
function ComponentWithConfig() {
const [data, setData] = useState(null);
useEffect(() => {
fetch(config.apiUrl)
.then(response => response.json())
.then(setData);
}, []);
return <div>{data}</div>;
}
2. Module Import/Export Issues
Different module systems can cause issues in production builds.
❌ Problem Scenario:
// This might work locally but fail after build
import someModule from './someModule'; // Might not work if it's a CommonJS module
// ❌ Default vs named exports confusion
const { someFunction } = someModule; // This might fail if it's a default export
✅ Solution: Proper Module Handling
// Correct approach - handle different export types
import someModule, { someFunction } from './someModule'; // Handle both default and named
// Or use dynamic imports for complex modules
async function loadModule() {
try {
const module = await import('./someModule');
return module.default || module;
} catch (error) {
console.error('Failed to load module:', error);
return null;
}
}
// Component using dynamic import
function DynamicComponent() {
const [module, setModule] = useState(null);
useEffect(() => {
loadModule().then(setModule);
}, []);
return module ? <div>Module loaded</div> : <div>Loading...</div>;
}
Solution 1: Proper Build Configuration
Configure your build process correctly for production.
Webpack Configuration:
// webpack.prod.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
optimization: {
splitChunks: {
chunks: 'all',
},
minimize: true,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
['@babel/preset-react', { runtime: 'automatic' }]
],
plugins: [
'@babel/plugin-transform-runtime'
]
}
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
resolve: {
extensions: ['.js', '.jsx'],
},
};
Package.json Scripts:
{
"name": "my-react-app",
"version": "1.0.0",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"build:analyze": "npm run build && npx webpack-bundle-analyzer dist/static/js/*.js",
"build:prod": "NODE_ENV=production react-scripts build"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0",
"webpack-bundle-analyzer": "^4.0.0"
}
}
Solution 2: Environment-Specific Code
Handle code that behaves differently in development vs production.
// Environment-specific code handling
function EnvironmentAwareComponent() {
const [isDevelopment, setIsDevelopment] = useState(false);
useEffect(() => {
// ✅ Check environment properly
const isDev = process.env.NODE_ENV === 'development';
setIsDevelopment(isDev);
if (isDev) {
// Development-specific code
console.log('Running in development mode');
} else {
// Production-specific code
console.log('Running in production mode');
}
}, []);
return (
<div>
<p>Environment: {isDevelopment ? 'Development' : 'Production'}</p>
{isDevelopment && <div>Development tools</div>}
</div>
);
}
// Service worker registration (production only)
function registerServiceWorker() {
if ('serviceWorker' in navigator && process.env.NODE_ENV === 'production') {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered: ', registration);
})
.catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
}
// Component with service worker
function App() {
useEffect(() => {
registerServiceWorker();
}, []);
return <div>My App</div>;
}
Solution 3: Proper Error Handling and Logging
Implement robust error handling for production builds.
// Production-safe error handling
function SafeErrorHandlingComponent() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
// ✅ Production-safe error handling
setError(err.message);
// Log error for debugging (only in development or with error tracking service)
if (process.env.NODE_ENV === 'development') {
console.error('Fetch error:', err);
} else {
// Send to error tracking service in production
// logErrorToService(err);
}
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
}
// Global error boundary for production
import React from 'react';
class ProductionErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
this.setState({
error: error,
errorInfo: errorInfo
});
// ✅ Log error to service in production
if (process.env.NODE_ENV === 'production') {
// logErrorToService(error, errorInfo);
}
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
{process.env.NODE_ENV === 'development' && (
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
)}
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
);
}
return this.props.children;
}
}
Solution 4: Asset and Path Management
Handle assets and paths correctly for production builds.
// Proper asset management for production
function AssetManagementComponent() {
// ✅ Use process.env.PUBLIC_URL for static assets
const logoPath = `${process.env.PUBLIC_URL}/logo.svg`;
// ✅ Use absolute paths for public folder assets
const backgroundImage = `${process.env.PUBLIC_URL}/images/background.jpg`;
return (
<div style={{ backgroundImage: `url(${backgroundImage})` }}>
<img src={logoPath} alt="Logo" />
<h1>Asset Management</h1>
</div>
);
}
// Dynamic asset loading
function DynamicAssetComponent({ assetName }) {
const [assetUrl, setAssetUrl] = useState('');
useEffect(() => {
// ✅ Handle dynamic asset loading safely
if (assetName) {
const url = `${process.env.PUBLIC_URL}/assets/${assetName}`;
setAssetUrl(url);
}
}, [assetName]);
return assetUrl ? <img src={assetUrl} alt={assetName} /> : <div>Loading asset...</div>;
}
// CSS asset management
function CSSAssetComponent() {
// ✅ Import CSS modules for production safety
// import styles from './Component.module.css';
return (
<div className="component">
<h1>Styling for Production</h1>
</div>
);
}
Solution 5: Code Splitting and Lazy Loading
Implement proper code splitting for production builds.
// Code splitting with React.lazy
import { lazy, Suspense } from 'react';
// ✅ Lazy load heavy components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const Dashboard = lazy(() => import('./Dashboard'));
const Reports = lazy(() => import('./Reports'));
function AppWithCodeSplitting() {
return (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/dashboard">Dashboard</Link>
<Link to="/reports">Reports</Link>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Suspense>
</div>
);
}
// Dynamic import with error handling
function DynamicImportComponent() {
const [Component, setComponent] = useState(null);
useEffect(() => {
const loadComponent = async () => {
try {
const module = await import('./SomeComponent');
setComponent(() => module.default || module);
} catch (error) {
console.error('Failed to load component:', error);
// Handle error gracefully
}
};
loadComponent();
}, []);
return Component ? <Component /> : <div>Loading...</div>;
}
// Route-based code splitting
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const Contact = lazy(() => import('./routes/Contact'));
function AppRouter() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading route...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Solution 6: Build Optimization Techniques
Optimize your build for production performance.
// Build optimization techniques
function OptimizationComponent() {
const [items, setItems] = useState([]);
const [filter, setFilter] = useState('');
// ✅ Memoize expensive calculations
const filteredItems = useMemo(() => {
if (!filter) return items;
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]); // Only recalculate when dependencies change
// ✅ Memoize functions to prevent unnecessary re-renders
const handleFilterChange = useCallback((e) => {
setFilter(e.target.value);
}, []);
// ✅ Use virtualization for large lists
const [visibleItems, setVisibleItems] = useState([]);
useEffect(() => {
// Implement virtualization logic for large datasets
const startIndex = 0;
const endIndex = Math.min(50, items.length);
setVisibleItems(items.slice(startIndex, endIndex));
}, [items]);
return (
<div>
<input
value={filter}
onChange={handleFilterChange}
placeholder="Filter items..."
/>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
// Performance monitoring
function PerformanceMonitoring() {
const [renderTime, setRenderTime] = useState(0);
useEffect(() => {
const startTime = performance.now();
// Component logic here
const endTime = performance.now();
setRenderTime(endTime - startTime);
}, []);
// ✅ Only log performance in development
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
console.log(`Component rendered in ${renderTime}ms`);
}
}, [renderTime]);
return <div>Performance monitoring</div>;
}
Solution 7: Testing Production Builds Locally
Test your production build locally before deployment.
// Testing production builds
// scripts/test-build.js
const { spawn } = require('child_process');
const path = require('path');
function testProductionBuild() {
console.log('Building production version...');
const buildProcess = spawn('npm', ['run', 'build'], {
cwd: process.cwd(),
stdio: 'inherit'
});
buildProcess.on('close', (code) => {
if (code === 0) {
console.log('Build successful! Testing locally...');
// Serve the build locally
const serveProcess = spawn('npx', ['serve', '-s', 'build'], {
cwd: process.cwd(),
stdio: 'inherit'
});
console.log('Serving build at http://localhost:3000');
console.log('Press Ctrl+C to stop');
} else {
console.error('Build failed!');
process.exit(1);
}
});
}
// Run the test
testProductionBuild();
// Alternative: Jest setup for production testing
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
testMatch: [
'**/__tests__/**/*.{js,jsx}',
'**/?(*.)+(spec|test).{js,jsx}',
],
// Set NODE_ENV for tests
testEnvironmentOptions: {
url: 'http://localhost:3000',
},
};
Solution 8: Deployment Configuration
Configure your deployment properly for production builds.
For Netlify:
# netlify.toml
[build]
command = "npm run build"
publish = "build"
environment = { NODE_VERSION = "18" }
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[context.production.environment]
NODE_ENV = "production"
For Vercel:
// vercel.json
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/static-build",
"config": {
"distDir": "build"
}
}
],
"routes": [
{
"src": "/(.*)",
"dest": "/index.html"
}
]
}
For GitHub Pages:
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '18'
- run: npm ci
- run: npm run build
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build
Solution 9: Debugging Production Issues
Implement debugging strategies for production builds.
// Production debugging utilities
const DebugUtils = {
// Safe logging for production
log: (message, data = null) => {
if (process.env.NODE_ENV === 'development') {
console.log(message, data);
} else if (process.env.REACT_APP_DEBUG === 'true') {
// Only log if debug is explicitly enabled in production
console.log(message, data);
}
},
// Error reporting for production
reportError: (error, context = {}) => {
if (process.env.NODE_ENV === 'production') {
// Send error to external service
fetch('/api/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
}),
}).catch(console.error);
} else {
// Log in development
console.error('Error reported:', error, context);
}
},
// Performance monitoring
measurePerformance: (name, fn) => {
if (process.env.NODE_ENV === 'development') {
const start = performance.now();
const result = fn();
const end = performance.now();
console.log(`${name} took ${end - start} milliseconds`);
return result;
}
return fn();
}
};
// Component using debug utilities
function DebugComponent() {
const [data, setData] = useState(null);
useEffect(() => {
DebugUtils.log('Component mounted');
const fetchData = async () => {
try {
DebugUtils.log('Starting data fetch');
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
DebugUtils.log('Data fetch completed');
} catch (error) {
DebugUtils.reportError(error, { component: 'DebugComponent' });
}
};
fetchData();
}, []);
return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
}
Performance Considerations
Optimized Production Build:
// Production-optimized patterns
function ProductionOptimizedComponent() {
const [state, setState] = useState({
data: [],
loading: false,
error: null
});
// ✅ Use functional updates to prevent race conditions
const updateState = useCallback((updater) => {
setState(prev => ({
...prev,
...updater(prev)
}));
}, []);
// ✅ Batch related state updates
const handleDataLoad = useCallback((newData) => {
updateState(() => ({
data: newData,
loading: false,
error: null
}));
}, [updateState]);
// ✅ Memoize expensive operations
const processedData = useMemo(() => {
return state.data.map(item => ({
...item,
processed: true
}));
}, [state.data]);
return (
<div>
{state.loading && <div>Loading...</div>}
{state.error && <div>Error: {state.error}</div>}
<ul>
{processedData.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
Security Considerations
Production Security:
// Secure production practices
function SecureProductionComponent() {
const [userInput, setUserInput] = useState('');
const handleInputChange = (e) => {
// ✅ Sanitize user input before processing
const sanitizedInput = e.target.value
.replace(/[<>]/g, '') // Basic XSS prevention
.substring(0, 1000); // Limit input length
setUserInput(sanitizedInput);
};
// ✅ Secure API calls with proper headers
const secureApiCall = useCallback(async () => {
try {
const response = await fetch('/api/secure-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
// Don't include credentials unless necessary
// 'X-CSRF-Token': getCSRFToken(),
},
body: JSON.stringify({ data: userInput }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error('Secure API call failed:', error);
throw error;
}
}, [userInput]);
return (
<div>
<input
value={userInput}
onChange={handleInputChange}
placeholder="Enter secure input..."
/>
</div>
);
}
Common Mistakes to Avoid
1. Development-Only Code in Production:
// ❌ Don't do this
function BadDevelopmentCode() {
useEffect(() => {
// ❌ This will fail in production if process is not defined
if (process.browser) {
// Browser-specific code
}
}, []);
}
2. Hardcoded Development URLs:
// ❌ Don't do this
function BadUrlComponent() {
useEffect(() => {
fetch('http://localhost:3001/api/data') // ❌ Hardcoded dev URL
.then(response => response.json())
.then(setData);
}, []);
}
3. Missing Error Boundaries:
// ❌ Don't do this
function BadErrorHandling() {
useEffect(() => {
// ❌ No error handling - will crash in production
fetch('/api/data').then(response => response.json()).then(setData);
}, []);
}
Alternative Solutions
Using React DevTools for Production:
// Production-optimized component for DevTools
function ProductionOptimizedComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(response => response.json())
.then(setData)
.catch(error => {
console.error('Production error:', error);
// Fallback to default data
setData({ error: true });
});
}, []);
return (
<div data-testid="production-component">
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}
Feature Detection:
// Check for production features
function ProductionFeatureChecker() {
const [isProduction, setIsProduction] = useState(false);
useEffect(() => {
setIsProduction(process.env.NODE_ENV === 'production');
}, []);
return (
<div>
{isProduction ? 'Production Mode' : 'Development Mode'}
</div>
);
}
Troubleshooting Checklist
When your React app works locally but not after build:
- Check Environment Variables: Verify all env vars are properly configured
- Review Console Errors: Look for specific error messages in browser console
- Test Build Locally: Serve the build locally to replicate production
- Verify Asset Paths: Ensure all assets use correct paths
- Check Module Imports: Confirm all imports work in production
- Review Error Boundaries: Implement proper error handling
- Analyze Bundle: Use bundle analyzer to identify issues
Conclusion
The ‘React app works locally but not after build’ issue occurs due to differences between development and production environments. By implementing proper build configurations, environment handling, error boundaries, and optimization techniques, you can ensure your React applications work seamlessly in both environments.
The key to resolving this issue is understanding the differences between development and production builds, implementing robust error handling, and testing your production builds thoroughly before deployment. Whether you’re working with Create React App, custom Webpack configurations, or deployment platforms, the solutions provided in this guide will help you create production-ready React applications.
Remember to always test your builds locally, implement proper error handling, and use environment-appropriate configurations to ensure consistent behavior across all environments.
Related Articles
How to Solve React Blank Page After Deploy & Build Error Tutorial
Learn how to fix React blank page errors after deployment. Complete guide with solutions for production builds and deployment optimization.
How to Fix Vite build works locally but fails in production Error
Learn how to fix Vite builds that work locally but fail in production. Complete guide with solutions for deployment and build optimization.
How to Fix React Router 404 Error on Page Refresh Error
Learn how to fix React Router 404 errors on page refresh. Complete guide with solutions for client-side routing and server configuration.