^.^;
Back to Blog
Blog Post

React Performance: From 2.5s to 180ms Load Time

Real-world optimization techniques that reduced my portfolio's load time by 93%. Code splitting, lazy loading, image optimization, and more.

J
JG
Author
2025-11-20
Published
◆ ◆ ◆

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 analyze

Using @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

END OF ARTICLE
J

About JG

Full-stack developer specializing in web performance, authentication systems, and developer experience. Passionate about sharing real-world debugging stories and optimization techniques.

Terms of ServiceLicense AgreementPrivacy Policy
Copyright © 2025 JMFG. All rights reserved.