search
React star Featured

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.

person By Gautam Sharma
calendar_today January 2, 2026
schedule 14 min read
React Build Deployment Production Error Handling 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 defined
  • TypeError: Cannot read property of undefined
  • Module not found errors
  • ChunkLoadError in code splitting
  • Uncaught TypeError in 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:

  1. Check Environment Variables: Verify all env vars are properly configured
  2. Review Console Errors: Look for specific error messages in browser console
  3. Test Build Locally: Serve the build locally to replicate production
  4. Verify Asset Paths: Ensure all assets use correct paths
  5. Check Module Imports: Confirm all imports work in production
  6. Review Error Boundaries: Implement proper error handling
  7. 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.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

React

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.

January 1, 2026
React

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.

January 2, 2026
React

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.

January 2, 2026