Lighthouse 100: Extreme Performance on Real Apps
Perfect Lighthouse scores aren't just for toy demos. Here's how we consistently ship production apps that score 100 on SEO and 98+ on Performance.
The Performance Budget
Start with constraints:
- LCP (Largest Contentful Paint) ≤ 2.5s
- INP (Interaction to Next Paint) ≤ 200ms
- CLS (Cumulative Layout Shift) ≤ 0.1
These aren't suggestions. They're requirements.
1. Server-First Rendering
Client-side JavaScript is the enemy of performance. Use Next.js Server Components by default. Ship HTML that works without JS.
// ❌ Client-heavy
'use client'
export default function Page() {
const [data, setData] = useState(null)
useEffect(() => {
fetch('/api/data').then(r => setData(r))
}, [])
return {data?.title}
}
// ✅ Server-first
export default async function Page() {
const data = await fetchData()
return {data.title}
}
2. Image Optimization
Images kill performance. Use next/image with proper sizing:
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // Above fold
sizes="(max-width: 768px) 100vw, 1200px"
/>
Key wins:
- Automatic WebP/AVIF format selection
- Lazy loading below the fold
- Responsive srcset generation
3. Font Loading Strategy
Web fonts block rendering. Use Next.js font optimization:
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Prevent FOIT
preload: true,
})
4. Resource Hints
Tell the browser what's coming:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="dns-prefetch" href="https://api.example.com" />
5. Bundle Size Discipline
Every dependency costs performance. Audit regularly:
npx @next/bundle-analyzer
Questions to ask:
- Do we really need this library?
- Can we lazy load this?
- Is there a lighter alternative?
6. Caching Strategy
Use ISR (Incremental Static Regeneration) for content:
export const revalidate = 3600 // Revalidate hourly
Add Cloudflare in front for edge caching. Purge on content updates.
7. Prevent Layout Shift
Reserve space for dynamic content:
// ❌ Causes CLS
<div className="dynamic-height">
{loading ? <Spinner /> : <Content />}
</div>
// ✅ Fixed height
<div className="h-96">
{loading ? <Spinner /> : <Content />}
</div>
Monitoring in Production
Lighthouse scores in CI don't guarantee real-user performance. Use Real User Monitoring:
- Vercel Analytics (built-in for Next.js)
- Google Search Console (Core Web Vitals report)
- Custom Web Vitals tracking with analytics
The Workflow
- Lighthouse CI in PR checks (block on regressions)
- Bundle size checks (block on growth > 5%)
- Manual Lighthouse audit before deploy
- Monitor Core Web Vitals post-deploy
Perfect scores aren't magic. They're the result of disciplined engineering and performance-first architecture.