Users will tolerate a lot — bad design, confusing navigation, even bugs. But they won't tolerate slow. A site that takes 5 seconds to load loses half its visitors before they even see the content.
Performance isn't just about speed. It's about trust. Fast sites feel polished and professional. Slow sites feel broken.
The Performance Budget
Before optimizing anything, I set a performance budget:
- First Contentful Paint (FCP): < 1.5s
- Largest Contentful Paint (LCP): < 2.5s
- Cumulative Layout Shift (CLS): < 0.1
- Total Blocking Time (TBT): < 200ms
These are Google's Core Web Vitals thresholds. Hit them, and you'll rank better in search and convert more users.
What I Optimized
1. Images — The Biggest Culprit
Images are usually 50-70% of a page's total weight. I switched all <img> tags to Next.js <Image>:
import Image from 'next/image'
// ❌ Old way
<img src="/hero.jpg" alt="Hero" />
// ✅ New way
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={630}
priority // for above-the-fold images
/>
What this gives you:
- Automatic format conversion (WebP, AVIF)
- Responsive image sizes
- Lazy loading by default
- Automatic blur-up placeholders
Result: 60% smaller image payloads.
2. Code Splitting — Only Load What You Need
I had a 500KB bundle that included a chart library used on one page. That library was loading on every page.
Solution: Dynamic imports with next/dynamic:
import dynamic from 'next/dynamic'
// ❌ Regular import (loads on every page)
import Chart from './Chart'
// ✅ Dynamic import (loads only when needed)
const Chart = dynamic(() => import('./Chart'), {
loading: () => <p>Loading chart...</p>,
ssr: false // skip server-side rendering if not needed
})
Result: Initial bundle size dropped from 500KB to 120KB.
3. Font Loading — Stop the Flash
Web fonts cause a flash of invisible text (FOIT) or unstyled text (FOUT). I used next/font to eliminate both:
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}
Fonts are now self-hosted, preloaded, and don't block rendering.
Result: Eliminated font-related layout shifts entirely.
4. Prefetching — Make Navigation Instant
Next.js automatically prefetches linked pages when they enter the viewport. But I also added manual prefetching for critical pages:
import { useRouter } from 'next/navigation'
function Nav() {
const router = useRouter()
// Prefetch on hover
const handleMouseEnter = () => {
router.prefetch('/projects')
}
return (
<a href="/projects" onMouseEnter={handleMouseEnter}>
Projects
</a>
)
}
Result: Navigation feels instant. No spinners, no waiting.
5. Third-Party Scripts — Defer Everything
I had Google Analytics, a chat widget, and an email signup form loading synchronously. They were blocking the main thread.
I moved them all to next/script with the afterInteractive strategy:
import Script from 'next/script'
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
/>
Result: Reduced Total Blocking Time by 80%.
6. Database Queries — Stop Waterfalling
I was fetching data in a waterfall:
- Fetch user
- Then fetch user's posts
- Then fetch comments on those posts
I parallelized them:
// ❌ Sequential (slow)
const user = await getUser(id)
const posts = await getPosts(user.id)
const comments = await getComments(posts.map(p => p.id))
// ✅ Parallel (fast)
const [user, posts, comments] = await Promise.all([
getUser(id),
getPosts(id),
getComments(id),
])
Result: Page load time dropped from 2.1s to 0.7s.
Measuring Performance
I use three tools:
- Chrome DevTools Lighthouse — for lab testing
- Vercel Analytics — for real user metrics
- WebPageTest — for detailed waterfall analysis
You can't improve what you don't measure. Run these regularly.
The 80/20 Rule
Most performance wins come from:
- Optimizing images (use Next.js Image)
- Code splitting (dynamic imports)
- Reducing JavaScript (do you really need that library?)
- Caching (CDN, browser cache, service workers)
Start there. Don't waste time micro-optimizing render loops until you've nailed the basics.
Takeaway
Performance is a product feature. It affects SEO, conversions, and user trust. Optimize early, measure often, and never ship a slow page.
Your users might not notice a fast site, but they'll definitely notice a slow one.