Frontend Performance Guide
A hands-on optimization playbook for achieving perfect Core Web Vitals scores in React and Next.js applications. Covers every performance lever: code splitting, image optimization, caching strategies, bundle analysis, font loading, third-party script management, and runtime profiling. Each technique includes before/after metrics, implementation code, and a decision tree for when to apply it.
Key Features
- Core Web Vitals Deep Dive — Breakdown of LCP, INP, and CLS with specific code patterns that improve each metric
- Code Splitting Strategies — Route-based splitting, component-level lazy loading, and barrel file elimination
- Image Optimization — next/image configuration, responsive srcsets, WebP/AVIF format selection, and priority loading
- Caching Architecture — CDN headers, SWR patterns, service worker precaching, and ISR revalidation
- Bundle Analysis Tools — Webpack Bundle Analyzer config, size budget enforcement in CI, and tree-shaking verification
-
Font Loading Strategy —
font-display: swap, preloading, subsetting, and variable fonts for reduced payload - Third-Party Script Audit — Loading strategies (defer/async/worker) and Partytown integration
Quick Start
- Run a baseline Core Web Vitals audit:
npx unlighthouse --site https://your-app.example.com
- Add the bundle analyzer to your Next.js project:
npm install --save-dev @next/bundle-analyzer
// next.config.ts
import withBundleAnalyzer from '@next/bundle-analyzer';
const config = withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
})({ /* your existing config */ });
export default config;
- Generate the bundle report:
ANALYZE=true npm run build
- Apply optimizations from the guide, then re-measure to verify improvements.
Architecture / How It Works
performance-optimization-guide/
├── core-web-vitals/ # LCP, INP, CLS, TTFB guides
├── techniques/ # Code splitting, images, caching, fonts, scripts
├── configs/ # next.config.optimized.ts, lighthouse-budget.json
├── scripts/ # CWV measurement, CI size budget enforcement
└── checklists/ # Pre-launch and quarterly audit checklists
Usage Examples
Dynamic Import with Loading Fallback
import dynamic from 'next/dynamic';
// Heavy chart library — only load when the component is needed
const AnalyticsChart = dynamic(() => import('@/components/AnalyticsChart'), {
loading: () => <div className="h-64 animate-pulse bg-gray-200 rounded" />,
ssr: false, // client-only component, skip server rendering
});
export function DashboardPage() {
return (
<section>
<h2>Analytics</h2>
<AnalyticsChart data={chartData} />
</section>
);
}
Optimized Image Component
import Image from 'next/image';
// Hero image: priority loading, responsive sizes, AVIF format
export function HeroSection() {
return (
<Image
src="/hero.jpg"
alt="Product showcase"
width={1200}
height={630}
priority // preloads for LCP
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
className="object-cover"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQ..." // tiny blur placeholder
/>
);
}
Performance Budget CI Check
// scripts/size-budget-check.ts
import { readFileSync } from 'fs';
const budgets = [
{ path: '.next/static/chunks/main.js', maxKB: 80 },
{ path: '.next/static/chunks/framework.js', maxKB: 45 },
];
for (const { path, maxKB } of budgets) {
const sizeKB = readFileSync(path).length / 1024;
if (sizeKB > maxKB) { console.error(`OVER: ${path} ${sizeKB.toFixed(1)}KB > ${maxKB}KB`); process.exit(1); }
console.log(`OK: ${path} — ${sizeKB.toFixed(1)}KB / ${maxKB}KB`);
}
Configuration
Lighthouse Performance Budget (lighthouse-budget.json)
[{
"path": "/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "image", "budget": 200 },
{ "resourceType": "total", "budget": 700 }
]
}]
Optimized Next.js Config
const nextConfig: NextConfig = {
images: {
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 60 * 60 * 24 * 30,
},
experimental: {
optimizeCss: true,
optimizePackageImports: ['lucide-react', '@headlessui/react'],
},
headers: async () => [{
source: '/fonts/:path*',
headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }],
}],
};
Best Practices
- Measure before optimizing — Run Lighthouse and CrUX data to identify actual bottlenecks, not guesses
- Optimize LCP first — LCP has the largest impact on perceived speed; focus on hero images and above-the-fold content
-
Eliminate layout shifts — Always set explicit
width/heighton images; useaspect-ratiofor dynamic containers -
Lazy load below the fold — Use
loading="lazy"on images anddynamic()on non-visible components - Audit third-party scripts quarterly — analytics and chat widgets often add 100KB+ of JavaScript silently
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
LCP element loads slowly despite priority
|
Image served without CDN or in unoptimized format | Verify next/image is generating AVIF/WebP and CDN cache headers are set |
| CLS score > 0.1 on mobile | Dynamic content or ads injecting without reserved space | Add min-height to containers that load async content |
| Bundle size keeps growing | Barrel file imports pulling entire libraries | Use optimizePackageImports or import from specific module paths |
| INP > 200ms on interactions | Long tasks blocking the main thread | Break heavy computations into requestIdleCallback or Web Worker chunks |
This is 1 of 11 resources in the Frontend Developer Pro toolkit. Get the complete [Frontend Performance Guide] with all files, templates, and documentation for $29.
Or grab the entire Frontend Developer Pro bundle (11 products) for $129 — save 30%.
Top comments (0)