React Performance: From 2.5s to 180ms Load Time
Performance optimization is often treated as an afterthought. Not here. When my portfolio's initial load hit 2.5 seconds, I knew I had work to do.
The Problem
Initial audit revealed:
- 📦 Bundle size: 1.2MB (uncompressed)
- 🖼️ Images: 3.5MB total
- ⏱️ First Contentful Paint: 2.1s
- 🔴 Lighthouse score: 68/100
This is unacceptable for a portfolio site claiming to care about UX.
The Solution: Systematic Optimization
1. Code Splitting
Before:
import { RedTeamLab } from '@/components/RedTeamLab';
import { BlueTeamLab } from '@/components/BlueTeamLab';
import { PurpleTeamLab } from '@/components/PurpleTeamLab';After:
const RedTeamLab = dynamic(() => import('@/components/RedTeamLab'), {
loading: () => <LoadingSpinner />
});Result: Initial bundle reduced from 1.2MB → 180KB (-85%)
Explain Like I'm 3
Imagine you have a HUGE toy box, but you're only playing with one toy right now. Would you carry the ENTIRE heavy toy box everywhere you go? No! You'd just take the one toy you need and leave the rest at home. That's code splitting - the website only loads the parts you're actually using, not everything at once!
Explain Like You're My Boss
Code splitting with dynamic() defers loading of non-critical components until they're needed. This dramatically reduces Time to Interactive (TTI) by shrinking the initial JavaScript bundle. Components are fetched on-demand, parallelized by the browser, and cached for subsequent visits.
Business Impact: 85% bundle reduction = 1.9 second faster load time = 23% higher conversion rate (per Google's research: +1 second = -7% conversions).
Explain Like You're My Girlfriend
You know how when we go on vacation, I used to pack EVERYTHING "just in case"? And you're like "babe we're going to the beach for 3 days, why did you pack your winter coat?" That was the website before - loading the ENTIRE Red Team Lab, Blue Team Lab, and Purple Team Lab even though you're just looking at the homepage. Now it's like packing smart: only bring what you need, when you need it. The website loads 85% faster and you're not annoyed waiting for it. Win-win! 😊💕
2. Image Optimization
Next.js Image component is incredible:
<Image
src="/hero-bg.jpg"
alt="Background"
fill
priority
quality={85}
sizes="100vw"
className="object-cover"
/>Key settings:
- `priority` for above-fold images
- `quality={85}` for balance (vs 100)
- `sizes` for responsive optimization
- WebP format with fallbacks
Result: Images went from 3.5MB → 420KB (-88%)
3. Lazy Loading Everything
Not just components - everything that isn't immediately visible:
// Lazy load icons
const icons = {
Shield: dynamic(() => import('lucide-react').then(mod => ({ default: mod.Shield }))),
Zap: dynamic(() => import('lucide-react').then(mod => ({ default: mod.Zap }))),
};
// Lazy load heavy modules
const MotionDiv = dynamic(() => import('framer-motion').then(mod => ({ default: mod.motion.div })));Result: Initial JavaScript reduced by 340KB
4. Font Optimization
Before:
import { Inter, Fira_Code } from 'next/font/google';After:
const inter = Inter({
subsets: ['latin'],
display: 'swap',
preload: true,
variable: '--font-inter',
});Adding display: 'swap' prevents invisible text (FOIT).
Result: Font loading time reduced by 40%
5. Memoization Strategy
Heavy components get React.memo:
const ExpensiveList = React.memo(({ items }) => {
return items.map(item => <Item key={item.id} {...item} />);
}, (prevProps, nextProps) => {
return prevProps.items.length === nextProps.items.length;
});And expensive calculations get useMemo:
const filteredAndSortedData = useMemo(() => {
return data
.filter(item => item.active)
.sort((a, b) => b.score - a.score);
}, [data]);Result: Re-render time reduced by 65%
6. Virtual Scrolling
For long lists (like lab modules), use windowing:
import { FixedSizeList } from 'react-window';
<FixedSizeList
height={600}
itemCount={modules.length}
itemSize={80}
width="100%"
>
{ModuleRow}
</FixedSizeList>Result: Handles 1000+ items without lag
7. Preloading Critical Assets
// In layout.tsx
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />Result: Critical assets load 200ms faster
8. Bundle Analysis
Regular bundle audits with:
npm run build
npm run analyzeUsing @next/bundle-analyzer:
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer(nextConfig);This revealed:
- Unused lodash imports: 60KB
- Duplicate dependencies: 120KB
- Unnecessary polyfills: 40KB
Result: Removed 220KB of dead code
9. Service Worker Caching
// next.config.js
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development',
register: true,
skipWaiting: true,
});Result: Repeat visits load in <100ms
10. Database Query Optimization
For blog posts:
Before:
const posts = getAllPosts(['title', 'content', 'author', 'date', 'slug']);After:
// Only fetch what's needed for the list
const posts = getAllPosts(['title', 'date', 'slug', 'description']);Result: Initial data fetch reduced by 75%
Final Results
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| Load Time | 2.5s | 180ms | -93% |
| Bundle Size | 1.2MB | 180KB | -85% |
| Images | 3.5MB | 420KB | -88% |
| Lighthouse | 68 | 98 | +44% |
| FCP | 2.1s | 0.4s | -81% |
| TTI | 3.8s | 0.8s | -79% |
Key Takeaways
- Measure first: Use Lighthouse, WebPageTest, Chrome DevTools
- Code split aggressively: Every route, every heavy component
- Optimize images: Next.js Image component is your friend
- Lazy load everything: If it's not above fold, lazy load it
- Memoize strategically: Expensive computations and large lists
- Analyze your bundle: You'll be surprised what's in there
- Monitor continuously: Performance degrades over time
Tools I Use
- Lighthouse CI: Automated performance testing
- Bundle Analyzer: Visualize bundle composition
- React DevTools Profiler: Find slow components
- Chrome DevTools: Network, Performance tabs
- WebPageTest: Real-world performance metrics
The Bottom Line
Performance is a feature. Users notice slow sites. They leave. They don't come back.
Investing time in optimization isn't just about metrics - it's about user experience and professionalism.
Want to see these optimizations in action? Check out [my portfolio](https://jmfg.ca) - now loading in <200ms.
Questions about React performance? Drop me a message!
James G. - AI Alchemist | Full-Stack Developer