Vite promised lightning-fast development. So why is your dev server taking 30 seconds to start? Why are production bundles hitting 2MB?
The culprits are almost always the same four patterns—and they're all fixable in under an hour. This guide shows you exactly what's slowing down your Vite React app and how to fix it.
The four things killing your Vite performance
Every slow Vite project I've profiled had at least one of these issues:
- Barrel file imports - Re-exporting from index files forces Vite to process thousands of modules
- Babel transforms - The default React plugin uses Babel, which is slow
- SVG-as-components - Converting SVGs to React components adds build overhead
- Large production chunks - Without manual chunking, everything lands in one file
Let's fix each one.
Fix dev server speed
Switch to SWC (5-minute fix, 10x faster HMR)
The single biggest dev server speedup is replacing Babel with SWC—a Rust-based compiler that's dramatically faster.
npm uninstall @vitejs/plugin-react
npm install @vitejs/plugin-react-swc
// vite.config.js
import react from '@vitejs/plugin-react-swc';
export default {
plugins: [react()],
};
That's it. HMR that took 3-4 seconds can drop to under 500ms.
Important: If you're using the standard @vitejs/plugin-react, don't configure Babel options unless you absolutely need them. Custom Babel config prevents esbuild from being used during builds.
Stop using barrel files
Barrel files are the index.js files that re-export everything:
// components/index.js (barrel file)
export { Button } from './Button';
export { Card } from './Card';
export { Modal } from './Modal';
// ... 50 more exports
When you import from a barrel:
// Slow - processes ALL exports even though you only want Button
import { Button } from '@/components';
Vite has to fetch and transform every file the barrel exports. For large codebases, this means thousands of modules on startup.
Fix: Import directly from source files:
// Fast - only processes Button
import { Button } from '@/components/Button';
Yes, it's more verbose. Your dev server will thank you.
Don't transform SVGs into components
Popular plugins like vite-plugin-svgr convert SVGs to React components:
// Slow - SVG becomes a full React component
import Logo from './logo.svg?react';
Each SVG gets transformed, adding build overhead. For a few icons it's fine. For 50+ icons, it adds up.
Better approach: Import as URLs and use <img> tags:
// Fast - no transform needed
import logoUrl from './logo.svg';
function Header() {
return <img src={logoUrl} alt="Logo" width={120} height={40} />;
}
For icons that need styling, consider:
- CSS
mask-imagefor color changes - Icon fonts
- A small set of inline SVG components for critical icons only
Use server warmup
Vite can pre-transform frequently used files on startup:
// vite.config.js
export default {
server: {
warmup: {
clientFiles: [
'./src/main.tsx',
'./src/App.tsx',
'./src/components/Layout.tsx',
],
},
},
};
Using --open or server.open: true also helps - Vite automatically warms up your entry point.
Check your browser (often the real culprit)
What feels like a Vite problem is often a browser problem. I've seen teams spend hours debugging Vite configs when a single browser checkbox was the issue.
1. The "Disable cache" trap:
When dev tools are open, Chrome has a "Disable cache" checkbox in the Network tab. If checked, the browser re-fetches every module on every page load instead of using Vite's optimized caching.
Dev tools → Network tab → Uncheck "Disable cache"
This single setting can make HMR feel 10x slower. Only enable it when actively debugging caching issues.
2. Browser extensions:
Extensions like React DevTools, Redux DevTools, or ad blockers intercept network requests. Each interception adds latency. In a Vite app with 500+ modules, this compounds quickly.
Test in incognito mode or a clean browser profile:
# Chrome with clean profile
google-chrome --user-data-dir=/tmp/clean-chrome
# Or just use incognito
google-chrome --incognito
If Vite is dramatically faster in incognito, start disabling extensions one by one to find the culprit.
3. Firefox has known issues:
Firefox's ES module handling has documented bugs that make Vite's unbundled development approach slower. The Vite team recommends using Chrome or Safari for development.
If you must use Firefox, enable the network.http.max-persistent-connections-per-server setting in about:config and increase it from 6 to 10+.
4. Hardware acceleration:
Disabled GPU acceleration forces the browser to use software rendering, which impacts both the dev server and your app's performance. Check:
Chrome → Settings → System → Use hardware acceleration
Fix production bundle size
Enable manual chunking (most impactful change)
By default, Vite puts all vendor code in one massive chunk. For large apps, this single file blocks page load and kills caching—when any dependency updates, users re-download everything.
Split it up with manualChunks:
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
// React core in its own chunk
'vendor-react': ['react', 'react-dom'],
// Router separate
'vendor-router': ['react-router-dom'],
// Heavy libraries isolated
'vendor-charts': ['recharts', 'd3'],
// UI library
'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
},
};
This lets the browser cache stable vendor chunks separately from your frequently-changing app code.
Dynamic chunking for large apps
For more control, use a function:
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
// Group all @radix-ui packages
if (id.includes('@radix-ui')) {
return 'vendor-radix';
}
// Group charting libraries
if (id.includes('recharts') || id.includes('d3')) {
return 'vendor-charts';
}
// React ecosystem together
if (id.includes('react')) {
return 'vendor-react';
}
// Everything else
return 'vendor';
}
},
},
},
},
};
Lazy load routes
Don't load every route upfront. Use React.lazy for route-based code splitting:
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Each route becomes its own chunk, loaded only when visited. This directly improves LCP.
Enable compression
Compress your output for smaller transfer sizes:
npm install vite-plugin-compression
// vite.config.js
import compression from 'vite-plugin-compression';
export default {
plugins: [
compression({
algorithm: 'gzip',
}),
compression({
algorithm: 'brotliCompress',
}),
],
};
This generates .gz and .br files. Configure your server to serve them.
Analyze your bundle
Before optimizing, see what's actually in your bundle:
npm install rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer';
export default {
plugins: [
visualizer({
open: true,
gzipSize: true,
filename: 'bundle-analysis.html',
}),
],
};
Run npm run build and a treemap opens showing exactly where your bytes go.
Common findings:
- Moment.js or date-fns with all locales (switch to day.js or tree-shake)
- Full lodash instead of individual imports
- Multiple versions of the same package
- Unused exports from large libraries
Profile dev server bottlenecks
If your dev server is still slow after the basics, profile it:
vite --profile
Interact with your app, then press p to save a CPU profile. Upload it to speedscope.app to see exactly what's taking time.
Look for:
- Long-running plugin hooks (
buildStart,config,configResolved) - Slow transforms on specific file types
- Dependencies that should be pre-bundled
Force pre-bundling
Vite pre-bundles dependencies to convert CommonJS to ESM. Sometimes it misses things:
// vite.config.js
export default {
optimizeDeps: {
include: [
'some-cjs-package',
'deeply-nested-dependency',
],
},
};
Vite and Core Web Vitals
Vite's build output directly affects your production Core Web Vitals. Here's how each metric is impacted and specific Vite configurations to improve them.
LCP impact
LCP measures when the largest content element becomes visible. Large JavaScript bundles delay LCP because the browser must download, parse, and execute JS before React can render anything.
How Vite affects LCP:
- Single large chunk blocks initial render
- Unoptimized vendor bundles delay hydration
- Missing preload hints slow critical resource loading
Fix it with Vite config:
- Split vendor chunks strategically:
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
// React loads first - it's critical
'vendor-react': ['react', 'react-dom'],
// Everything else loads after
'vendor-other': ['axios', 'date-fns', 'lodash-es'],
},
},
},
},
};
- Lazy load below-fold components:
// Components below the fold shouldn't block LCP
const Comments = lazy(() => import('./Comments'));
const RelatedPosts = lazy(() => import('./RelatedPosts'));
const Footer = lazy(() => import('./Footer'));
- Use modulepreload for critical chunks:
Vite automatically adds <link rel="modulepreload"> for entry points. For other critical chunks, add them manually:
<!-- In your index.html -->
<link rel="modulepreload" href="/assets/vendor-react.js" />
- Optimize the critical rendering path:
// vite.config.js
export default {
build: {
// Inline small chunks to reduce round trips
assetsInlineLimit: 4096, // 4kb
// Generate smaller chunks for better caching
chunkSizeWarningLimit: 500,
},
};
CLS impact
CLS measures visual stability. Vite itself doesn't cause CLS, but how you handle lazy-loaded components does.
Common CLS causes in Vite apps:
- Lazy components without reserved space:
// Bad - layout shifts when component loads
const Chart = lazy(() => import('./Chart'));
function Dashboard() {
return (
<Suspense fallback={<p>Loading...</p>}>
<Chart /> {/* Height unknown until JS loads */}
</Suspense>
);
}
// Good - skeleton matches final dimensions
function Dashboard() {
return (
<Suspense fallback={<div style={{ height: 400, background: '#f0f0f0' }} />}>
<Chart />
</Suspense>
);
}
- Fonts loaded via CSS-in-JS: If your styling solution loads fonts dynamically, text can reflow when fonts load. Use Vite's CSS handling instead:
// vite.config.js
export default {
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/fonts.scss";`,
},
},
},
};
- Images without dimensions:
// Bad - image size unknown
<img src={imageUrl} alt="Product" />
// Good - dimensions prevent CLS
<img src={imageUrl} alt="Product" width={400} height={300} />
INP impact
INP measures interaction responsiveness. Chunk size directly affects INP because parsing large JavaScript files blocks the main thread.
How Vite chunk size affects INP:
When a user clicks a button that triggers a lazy import, the browser must:
- Fetch the chunk
- Parse the JavaScript
- Execute the code
- Run the click handler
Large chunks make step 2 and 3 slow, causing perceptible lag.
Fix it:
- Keep interaction-critical chunks small:
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks(id) {
// Keep interactive UI components in small chunks
if (id.includes('/components/interactive/')) {
return 'ui-interactive';
}
},
},
},
},
};
- Prefetch likely interactions:
// Prefetch the settings page chunk when user hovers the menu
function NavMenu() {
const prefetchSettings = () => {
import('./pages/Settings'); // Starts loading immediately
};
return (
<nav>
<Link to="/settings" onMouseEnter={prefetchSettings}>
Settings
</Link>
</nav>
);
}
- Use web workers for heavy computation:
// vite.config.js - enable worker bundling
export default {
worker: {
format: 'es',
},
};
// Then in your code
const worker = new Worker(new URL('./heavy-calc.worker.js', import.meta.url));
worker.postMessage(data);
worker.onmessage = (e) => setResult(e.data);
- Monitor chunk sizes in CI:
// vite.config.js
export default {
build: {
// Fail build if any chunk exceeds 500kb
chunkSizeWarningLimit: 500,
},
};
Quick wins checklist
Dev server speed
- Switch to
@vitejs/plugin-react-swc - Replace barrel imports with direct file imports
- Import SVGs as URLs, not components
- Add critical files to
server.warmup - Test in Chrome/Safari instead of Firefox
- Ensure "Disable cache" is unchecked in dev tools
Production bundle size
- Configure
manualChunksfor vendor splitting - Lazy load routes with
React.lazy - Add
vite-plugin-compressionfor gzip/brotli - Run
rollup-plugin-visualizerto find bloat - Remove unused dependencies
Build speed
- Don't configure Babel options in the React plugin
- Use
optimizeDeps.includefor problematic dependencies - Profile with
vite --profileif still slow
Frequently Asked Questions
Is Vite faster than Webpack?
For dev server, significantly yes. Vite serves native ES modules without bundling during development. For production builds, they're comparable since both use bundlers (Rollup for Vite, Webpack for Webpack).
Should I migrate from Create React App to Vite?
Yes. CRA is deprecated and Vite offers faster development and modern tooling. The migration is usually straightforward - update config files and import paths.
Why is my Vite build slower than expected?
Check for: custom Babel config (prevents esbuild), slow plugins, large source maps, or TypeScript type checking in the build. Run vite build --profile to identify bottlenecks.
Does Vite work with older browsers?
Yes. Use @vitejs/plugin-legacy to generate fallback bundles for browsers that don't support native ES modules. This adds build time but ensures compatibility.
How do I debug Vite performance issues?
Start with vite --profile to generate a CPU profile. Upload to speedscope.app to visualize. Also check Vite's --debug flag for detailed logging about transforms and dependency resolution.
What's next
Your 30-minute action plan:
- Swap to SWC (5 min) — One npm install, one config change
- Find barrel imports (10 min) — Search for
from '@/components'or similar index imports - Add manual chunks (10 min) — Split React, router, and heavy libraries
- Run bundle analyzer (5 min) — See exactly what's bloating your build
Most Vite performance problems disappear after these four changes.
Related guides:
- React performance optimization — Framework-level patterns
- MUI performance guide — If you're using Material UI
Still losing points after optimizing? Run your site through PageSpeedFix to identify exactly what's hurting your Core Web Vitals—sometimes it's not Vite at all.