| name | frontend-performance |
| description | Optimize Next.js frontend performance using Lighthouse, bundle analysis, and animation best practices. Use when diagnosing slow pages, optimizing bundle size, or improving Core Web Vitals (LCP, TBT, CLS). |
Frontend Performance Optimization
Diagnose and fix performance issues in Next.js applications.
Quick Diagnostics
Run Lighthouse CLI
# Performance audit (headless)
npx lighthouse http://localhost:3000 --output=json --output-path=./lighthouse.json --chrome-flags="--headless" --only-categories=performance
# Parse key metrics
cat lighthouse.json | jq '{
score: .categories.performance.score,
FCP: .audits["first-contentful-paint"].displayValue,
LCP: .audits["largest-contentful-paint"].displayValue,
TBT: .audits["total-blocking-time"].displayValue,
CLS: .audits["cumulative-layout-shift"].displayValue
}'
# Find slow scripts
cat lighthouse.json | jq '.audits["bootup-time"].details.items | .[0:8]'
# Main thread breakdown
cat lighthouse.json | jq '.audits["mainthread-work-breakdown"].details.items'
Important: Always test production builds (next build && next start), not dev mode. Dev mode has 2-10x overhead from HMR, source maps, and no optimizations.
Bundle Analysis
# Install
bun add -d @next/bundle-analyzer
# Run analysis
ANALYZE=true bun run build
Configure in next.config.js:
import bundleAnalyzer from '@next/bundle-analyzer'
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
})
export default withBundleAnalyzer(nextConfig)
Common Fixes
1. optimizePackageImports
For libraries with many exports (icons, utilities, animation libraries):
// next.config.js
const nextConfig = {
experimental: {
optimizePackageImports: [
'framer-motion',
'lucide-react',
'@phosphor-icons/react',
'lodash',
'date-fns',
'@heroicons/react',
],
},
}
This ensures tree-shaking works correctly - only imports you use get bundled.
2. Framer Motion - Variants Pattern
WRONG - Creates N animation controllers:
// Each element has its own animation state - expensive!
{items.map((item, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }} // Individual delays
>
{item}
</motion.div>
))}
RIGHT - Single controller with staggerChildren:
// Parent controls all children - efficient!
const containerVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.05,
},
},
}
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { type: 'spring' as const, damping: 15 }
},
}
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
>
{items.map((item, i) => (
<motion.div key={i} variants={itemVariants}>
{item}
</motion.div>
))}
</motion.div>
Also avoid:
filter: blur()in animations - very expensive- Too many infinite animations (reduce or use CSS)
- Individual
transitionprops on children when using variants
3. Move Heavy Computation Server-Side
Keep these out of client bundles:
- Syntax highlighting: use
shikiserver-side, notprism-react-renderer - Markdown parsing: render on server
- Date formatting libraries: consider
Intl.DateTimeFormat - Large data transformations: API routes or server components
4. Image Optimization
// Always use next/image
import Image from 'next/image'
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // For LCP images
placeholder="blur" // Reduces CLS
/>
5. Font Optimization
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Prevents FOIT
preload: true,
})
6. External Packages (Server Only)
Prevent server-only packages from being bundled:
// next.config.js
const nextConfig = {
serverExternalPackages: ['sharp', 'canvas'],
}
Performance Targets
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP | ≤2.5s | 2.5-4s | >4s |
| FCP | ≤1.8s | 1.8-3s | >3s |
| TBT | ≤200ms | 200-600ms | >600ms |
| CLS | ≤0.1 | 0.1-0.25 | >0.25 |
Debugging Workflow
- Run production Lighthouse - Get baseline metrics
- Check bootup-time audit - Find slow scripts
- Run bundle analyzer - Identify large chunks
- Fix largest issues first - Usually 1-2 packages cause most problems
- Re-test - Verify improvements
Quick Wins Checklist
- Test production build, not dev
- Add
optimizePackageImportsfor icon/utility libraries - Use Framer Motion variants pattern
- Remove
filter: blur()from animations - Add
priorityto LCP images - Use
next/fontwithdisplay: swap - Move heavy libraries to server components