Three fucking weeks. That's how long I wasted migrating to Vite expecting to be the office hero. Same slow loading, same sluggish clicks, same disappointed stakeholders staring at me during the demo.
Here's what I learned the hard way: build tools make your builds fast, not your app. Your dependencies are still bloated garbage. Your images are still unoptimized 8MB PNGs from designers. Your components still re-render the entire fucking DOM tree when someone types in a search box.
Your App Is Full of Garbage
CRA apps accumulate garbage over time:
- Dependencies you tried once and never removed (depcheck finds these)
- Imports that break tree-shaking in modern bundlers
- IE11 polyfills nobody needs anymore
- UI libraries where you use 3 components out of 50 (Material-UI has optimization docs)
Bundle analyzer showed the same massive chunks. All that dead weight just moved from webpack to Vite. My "optimized" build went from like 3.2MB to maybe 3.1MB. Embarrassing doesn't even cover it.
Do This Before You Touch Any Code
Figure Out What's Actually Slow
Build your CRA app and actually look at the garbage you're shipping:
npm run build
ls -lah build/static/js/ | sort -k5 -hr
npm install --save-dev webpack-bundle-analyzer
npx webpack-bundle-analyzer build/static/js/*.js
Bundle analyzer reveals the shit that's killing you:
- moment.js - like 300KB for date formatting JavaScript already does
- Complete UI libraries - imported all of Material-UI for 2 fucking buttons
- Icon libraries - thousands of React Icons for the handful you actually use
- Old polyfills - still supporting browsers you dropped years ago
First time I ran bundle analyzer, I wanted to throw my laptop out the window. Fifteen forgotten dependencies including some massive PDF library we tried once in 2023 and never bothered removing. Think it was like 60MB or something insane. We used react-pdf to display one document. One.
Measure Before You Migrate
npm install --save-dev lighthouse
npx lighthouse [your-dev-server-url] --view
Forget perfect Lighthouse scores. Focus on user experience:
- LCP under 2.5s - when main content actually loads
- FID under 100ms - how responsive clicks feel
- CLS under 0.1 - how much the page jumps around
Test on slow connections or prepare to get roasted by users. Our app felt snappy in dev on fiber but took forever to load on actual 3G. Like 20+ seconds. Chrome DevTools → Network → "Slow 3G" - prepare to hate yourself. The Network Information API can help you adapt to connection speed.
Performance Auditing: Lighthouse shows you exactly what's killing your load times - JavaScript blocking the main thread, massive images loading before text, and CSS that's never used.
The Web Vitals documentation has more details if you want to go deeper, but honestly just focus on LCP and CLS - those are the ones that actually affect user experience.
Core Web Vitals: Focus on the metrics users actually feel - how long until they see content (LCP), how quickly clicks respond (FID), and how much the page jumps around (CLS).
Delete Your Unused Crap
Migration breaks imports anyway. Perfect time to delete dependencies you don't need:
npm install --save-dev depcheck
npx depcheck
First time I ran depcheck
on our production app, I just stared at my screen. Twenty-seven unused packages. Including jsPDF (massive), react-virtualized (we switched to react-window ages ago), and three different fucking date libraries because apparently we couldn't make up our minds.
Delete this shit immediately:
- moment.js - huge for date formatting that browsers already do
- Full lodash imports - most of lodash is just worse Array methods now
- Complete UI libraries - importing half a megabyte of Material-UI for two buttons
- IE11 polyfills - Microsoft killed IE11, let it fucking die
- Test utilities - leftover Enzyme imports from like 2019
Replace the big stuff:
// Old way: massive date library
import moment from 'moment';
const formatted = moment(date).format('YYYY-MM-DD');
// New way: browser handles it
const formatted = new Intl.DateTimeFormat('en-CA').format(date);
Look, if your team actually knows Material-UI inside and out and ships fast, maybe the bloat is worth it. I've watched teams spend months replacing MUI with some "lightweight" alternative, break half their shit, and end up with the same bundle size anyway. Pick your battles.
Use bundlephobia.com to check dependency sizes before installing. For dates, date-fns tree-shakes properly and Day.js is tiny. The webpack documentation explains tree-shaking in detail if you want to understand why imports matter so much.
Fix Your Imports
Modern bundlers hate bad imports. Fix them during migration:
// Old CRA way - loads everything
import Dashboard from './Dashboard';
// Modern way - lazy loading
const Dashboard = lazy(() => import('./Dashboard'));
<Suspense fallback={<div>Loading...</div>}>
<Dashboard />
</Suspense>
// Bad imports
import { Button } from '@mui/material';
// Better imports
import Button from '@mui/material/Button';
Migration Won't Magically Optimize Your Build
Copy this Vite config if you want bundle analysis:
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer';
export default {
plugins: [
process.env.ANALYZE && visualizer({
filename: 'dist/stats.html',
open: true
})
],
build: {
target: 'es2020',
chunkSizeWarningLimit: 1000,
}
};
Test before and after migration:
## Before
npm run build
ls -lah build/static/js/
## After
npm run build
ls -lah dist/assets/
Don't trust your gut - "feels faster" is how I shipped a migration that actually made things slower. Lighthouse CI caught it weeks later when a client complained. Set up performance monitoring to catch regressions automatically.