No articles found
Try different keywords or browse our categories
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.
The Vite build works locally but fails in production error is a common issue developers face when their Vite application builds successfully in development but encounters problems during deployment. This problem occurs due to differences between local and production environments, including environment variables, build configurations, and deployment-specific constraints.
This comprehensive guide provides complete solutions to resolve the Vite build production error with practical examples and deployment optimization techniques.
Understanding Vite Build Production Issues
Vite builds can fail in production due to various environmental differences between local development and deployment platforms. These differences include environment variables, file permissions, memory limits, and dependency versions.
Common Error Scenarios:
Build fails on deployment platform but works locallyEnvironment-specific code causing build failuresAsset optimization issues in productionDependency resolution problems in CI/CD
Common Causes and Solutions
1. Environment Variable Differences
The most common cause is different environment variables between local and production environments.
❌ Problem Scenario:
// This works locally but fails in production
// .env.local
VITE_API_URL=http://localhost:3001/api
VITE_DEBUG=true
// .env.production (missing or different)
// VITE_API_URL might be undefined or different
✅ Solution: Proper Environment Configuration
// .env.production
VITE_API_URL=https://api.myapp.com
VITE_DEBUG=false
VITE_APP_NAME=MyProductionApp
// .env.development
VITE_API_URL=http://localhost:3001/api
VITE_DEBUG=true
VITE_APP_NAME=MyDevelopmentApp
// Component using environment variables safely
function ApiClient() {
const [data, setData] = useState(null);
const apiUrl = import.meta.env.VITE_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
});
}, [apiUrl]);
return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
}
2. Build Configuration Issues
Different build configurations can cause failures in production.
❌ Problem Scenario:
// vite.config.js with local-specific settings
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
host: 'localhost', // ❌ This might cause issues in some environments
port: 3000,
},
build: {
// ❌ Local-specific build settings
outDir: 'dist',
assetsDir: 'assets',
},
});
✅ Solution: Environment-Aware Configuration
// vite.config.js with environment awareness
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig(({ mode }) => {
const isProduction = mode === 'production';
const isDevelopment = mode === 'development';
return {
plugins: [react()],
// ✅ Server configuration only for development
...(isDevelopment && {
server: {
host: true,
port: 3000,
open: true,
},
}),
build: {
// ✅ Production-optimized settings
outDir: 'dist',
assetsDir: 'assets',
sourcemap: !isProduction, // Only generate sourcemaps in development
// ✅ Optimize for production
minify: isProduction ? 'terser' : false,
cssMinify: isProduction,
rollupOptions: {
output: {
// ✅ Optimize chunk splitting for production
manualChunks: isProduction
? {
'react-vendor': ['react', 'react-dom'],
'router-vendor': ['react-router-dom'],
}
: undefined,
},
},
},
// ✅ Define environment-specific variables
define: {
'process.env.NODE_ENV': JSON.stringify(mode),
},
};
});
Solution 1: Proper Build Configuration
Configure your build process correctly for production.
Production-Optimized Vite Configuration:
// vite.config.js - Production optimized
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@components': resolve(__dirname, './src/components'),
'@utils': resolve(__dirname, './src/utils'),
},
},
build: {
outDir: 'dist',
assetsDir: 'assets',
// ✅ Production-specific optimizations
minify: 'terser', // Use terser for better minification
cssMinify: true,
sourcemap: false, // Disable sourcemaps in production
rollupOptions: {
output: {
// ✅ Optimize for production
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'router-vendor': ['react-router-dom'],
'utils-vendor': ['lodash', 'axios'],
},
// ✅ Optimize chunk naming
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash].[ext]',
},
},
// ✅ Set production-specific limits
target: 'es2020', // Target modern browsers
cssTarget: 'es2020',
},
// ✅ Optimize dependency pre-bundling for production
optimizeDeps: {
include: [
'react',
'react-dom',
'react/jsx-runtime',
],
},
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
});
Development Configuration:
// vite.config.js - Development optimized
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
host: true,
port: 3000,
open: true,
hmr: true,
},
build: {
sourcemap: true, // Enable sourcemaps in development
minify: false, // Don't minify in development
},
define: {
'process.env.NODE_ENV': JSON.stringify('development'),
},
});
Solution 2: Environment-Specific Code
Handle code that behaves differently in development vs production.
// Environment-aware component
function EnvironmentAwareComponent() {
const [isProduction, setIsProduction] = useState(false);
useEffect(() => {
// ✅ Check environment properly
const isProd = import.meta.env.PROD;
setIsProduction(isProd);
if (isProd) {
// Production-specific code
console.log('Running in production mode');
// Initialize production services
} else {
// Development-specific code
console.log('Running in development mode');
// Initialize development tools
}
}, []);
return (
<div>
<p>Environment: {isProduction ? 'Production' : 'Development'}</p>
{!isProduction && <div>Development tools</div>}
</div>
);
}
// Service worker registration (production only)
function registerServiceWorker() {
if ('serviceWorker' in navigator && import.meta.env.PROD) {
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: Asset and Path Management
Handle assets and paths correctly for production builds.
// Proper asset management for production
function AssetManagementComponent() {
// ✅ Use import.meta.env.BASE_URL for static assets
const logoPath = `${import.meta.env.BASE_URL}/logo.svg`;
// ✅ Use absolute paths for public folder assets
const backgroundImage = `${import.meta.env.BASE_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 = `${import.meta.env.BASE_URL}/assets/${assetName}`;
setAssetUrl(url);
}
}, [assetName]);
return assetUrl ? <img src={assetUrl} alt={assetName} /> : <div>Loading asset...</div>;
}
// CSS asset management
function CSSAssetComponent() {
return (
<div className="component">
<h1>Styling for Production</h1>
</div>
);
}
Solution 4: Dependency Management for Production
Manage dependencies properly for production builds.
Package.json Configuration:
{
"name": "my-vite-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"build:analyze": "vite build && npx vite-bundle-analyzer dist/.vite/stats.json",
"test:build": "npm run build && npm run preview",
"clean": "rm -rf dist node_modules/.vite",
"fresh": "npm run clean && npm install && npm run build"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0"
},
"devDependencies": {
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@vitejs/plugin-react": "^4.0.0",
"vite": "^4.1.0",
"vite-bundle-analyzer": "^0.12.0"
},
"engines": {
"node": ">=14.0.0"
},
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}
Lock File Management:
# ✅ Always commit package-lock.json or yarn.lock
# This ensures consistent dependency versions across environments
# ✅ Update dependencies properly
npm update
# Or for specific packages
npm update react react-dom
# ✅ Check for outdated packages
npm outdated
# ✅ Audit security issues
npm audit
npm audit fix
Solution 5: CI/CD Pipeline Configuration
Configure your CI/CD pipeline for Vite builds.
GitHub Actions:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build for production
run: npm run build
env:
NODE_ENV: production
VITE_API_URL: ${{ secrets.PROD_API_URL }}
VITE_APP_NAME: ${{ secrets.PROD_APP_NAME }}
- name: Run build tests
run: npm run test:build
- name: Deploy to production
run: |
# Deploy commands here
echo "Deploying to production..."
Netlify Configuration:
# netlify.toml
[build]
command = "npm run build"
publish = "dist"
environment = { NODE_VERSION = "18" }
[context.production.environment]
NODE_ENV = "production"
VITE_API_URL = "https://api.myapp.com"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Vercel Configuration:
// vercel.json
{
"builds": [
{
"src": "package.json",
"use": "@vercel/static-build",
"config": {
"distDir": "dist"
}
}
],
"env": {
"NODE_ENV": "production",
"VITE_API_URL": "https://api.myapp.com"
},
"routes": [
{
"src": "/(.*)",
"dest": "/index.html"
}
]
}
Solution 6: 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(import.meta.env.VITE_API_URL);
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 (import.meta.env.DEV) {
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 (import.meta.env.PROD) {
// logErrorToService(error, errorInfo);
}
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
{import.meta.env.DEV && (
<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 7: Performance Optimization for Production
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 (import.meta.env.DEV) {
console.log(`Component rendered in ${renderTime}ms`);
}
}, [renderTime]);
return <div>Performance monitoring</div>;
}
Solution 8: Testing Production Builds
Test your production build before deployment.
// scripts/test-production.js
import { spawn } from 'child_process';
import { resolve } from 'path';
function testProductionBuild() {
console.log('Building production version...');
const buildProcess = spawn('npm', ['run', 'build'], {
cwd: process.cwd(),
stdio: 'inherit',
env: {
...process.env,
NODE_ENV: 'production',
VITE_API_URL: 'https://api.test.com' // Use test API for build testing
}
});
buildProcess.on('close', (code) => {
if (code === 0) {
console.log('Build successful! Testing locally...');
// Serve the build locally
const serveProcess = spawn('npx', ['vite', 'preview', '--port', '4173'], {
cwd: process.cwd(),
stdio: 'inherit'
});
console.log('Serving build at http://localhost:4173');
console.log('Press Ctrl+C to stop');
} else {
console.error('Build failed!');
process.exit(1);
}
});
}
// Run the test
testProductionBuild();
// Jest configuration for production testing
// jest.config.js
export default {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/tests/setup.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:4173',
},
};
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 (import.meta.env.DEV) {
console.log(message, data);
} else if (import.meta.env.VITE_DEBUG === 'true') {
// Only log if debug is explicitly enabled in production
console.log(message, data);
}
},
// Error reporting for production
reportError: (error, context = {}) => {
if (import.meta.env.PROD) {
// 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 (import.meta.env.DEV) {
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(import.meta.env.VITE_API_URL);
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(import.meta.env.VITE_API_URL)
.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(import.meta.env.PROD);
}, []);
return (
<div>
{isProduction ? 'Production Mode' : 'Development Mode'}
</div>
);
}
Troubleshooting Checklist
When your Vite build works locally but fails in production:
- 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 Vite build works locally but fails in production error 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 Vite 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 simple Vite applications or complex projects with many dependencies, the solutions provided in this guide will help you create production-ready Vite 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 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.
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 Solve Vite Outdated Optimize Dep Error in React Project
Learn how to fix the 'Outdated optimize dep' error in Vite with React. Complete guide with solutions for dependency optimization and caching issues.