Performance Optimization Guide
Complete guide to optimizing application performance.
Performance Metrics
Core Web Vitals
- LCP (Largest Contentful Paint): < 2.5s
- FID (First Input Delay): < 100ms
- CLS (Cumulative Layout Shift): < 0.1
- FCP (First Contentful Paint): < 1.8s
- TTI (Time to Interactive): < 3.8s
Monitoring
import { usePerformance } from '@/hooks/usePerformance';
function MyComponent() {
const { recordMetric } = usePerformance();
useEffect(() => {
const start = performance.now();
// Do work
const duration = performance.now() - start;
recordMetric('component_render', duration);
}, []);
}
Code Optimization
Code Splitting
Route-based Splitting (Automatic)
// Next.js automatically splits by route
// No action needed
Component Splitting
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <LoadingState />,
ssr: false, // If client-only
});
Library Splitting
// Import only what you need
import { debounce } from '@/lib/utils';
// Instead of: import * from '@/lib/utils';
Bundle Optimization
Tree Shaking
// Use named imports
import { function1, function2 } from '@/lib/utils';
// Instead of: import utils from '@/lib/utils';
Package Optimization
// next.config.ts
experimental: {
optimizePackageImports: ['@/components', '@/lib'],
}
Analyze Bundle
npm run analyze
Lazy Loading
Components
import dynamic from 'next/dynamic';
const LazyComponent = dynamic(() => import('./LazyComponent'));
Images
import Image from 'next/image';
<Image
src="/image.jpg"
width={800}
height={600}
loading="lazy"
alt="Description"
/>
Routes
// Next.js automatically lazy loads routes
// No action needed
Image Optimization
Next.js Image Component
import Image from 'next/image';
<Image
src="/image.jpg"
width={800}
height={600}
alt="Description"
priority={false} // Lazy load
placeholder="blur"
/>
Image Formats
// next.config.ts
images: {
formats: ['image/avif', 'image/webp'],
}
Responsive Images
<Image
src="/image.jpg"
width={800}
height={600}
sizes="(max-width: 768px) 100vw, 50vw"
alt="Description"
/>
Image Compression
import { compressImage } from '@/lib/image-handling';
const compressed = await compressImage(file, 1920, 1080, 0.8);
Caching Strategies
Client-Side Caching
API Cache
import { apiCache } from '@/lib/api-cache';
const data = apiCache.get('key') || await fetchData();
apiCache.set('key', data);
Advanced Cache
import { advancedCache } from '@/lib/advanced-cache';
const data = await advancedCache.get('key', fetcher, {
strategy: 'stale-while-revalidate',
ttl: 60000,
});
Browser Cache
// next.config.ts
async headers() {
return [
{
source: '/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
];
}
Server-Side Caching
Static Generation
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }];
}
ISR (Incremental Static Regeneration)
export const revalidate = 3600; // Revalidate every hour
API Route Caching
export async function GET() {
return NextResponse.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
},
});
}
State Management
Optimize State Updates
Use Local State When Possible
// Prefer local state
const [localState, setLocalState] = useState();
// Over global state
const [globalState, setGlobalState] = useStore(store);
Memoization
import { useMemo, useCallback } from 'react';
const expensiveValue = useMemo(() => {
return computeExpensiveValue(data);
}, [data]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
Avoid Unnecessary Re-renders
import { memo } from 'react';
const MyComponent = memo(({ data }) => {
return <div>{data}</div>;
});
Network Optimization
Request Optimization
Debounce Search
import { useDebounce } from '@/hooks/useDebounce';
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
performSearch(debouncedQuery);
}
}, [debouncedQuery]);
Throttle Events
import { useThrottle } from '@/hooks/useThrottle';
const [scrollY, setScrollY] = useState(0);
const throttledScrollY = useThrottle(scrollY, 100);
Batch Requests
import { fetchAll } from '@/lib/data-fetching';
const results = await fetchAll([
'/api/data1',
'/api/data2',
'/api/data3',
]);
Resource Hints
// next.config.ts
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
],
},
];
}
Preload Critical Resources
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
Prefetch Routes
import Link from 'next/link';
<Link href="/page" prefetch={true}>Page</Link>
Rendering Optimization
Server Components
Use Server Components When Possible
// Server Component (default)
export default function ServerComponent() {
return <div>Server rendered</div>;
}
Client Components Only When Needed
'use client';
export default function ClientComponent() {
return <div>Client rendered</div>;
}
Virtual Scrolling
import { VirtualList } from '@/components/VirtualList';
<VirtualList
items={largeList}
itemHeight={50}
renderItem={(item) => <div>{item}</div>}
/>
Intersection Observer
import { useIntersectionObserver } from '@/hooks/useIntersectionObserver';
const { ref, isIntersecting } = useIntersectionObserver({
threshold: 0.1,
});
return (
<div ref={ref}>
{isIntersecting && <HeavyComponent />}
</div>
);
Font Optimization
Font Loading
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Optimize font loading
preload: true,
});
Font Display
@font-face {
font-display: swap; /* Prevents invisible text */
}
Performance Budgets
Configure Budgets
import { performanceBudgetMonitor } from '@/lib/performance-budget';
const results = performanceBudgetMonitor.checkBudgets({
FCP: 1500,
LCP: 2000,
FID: 100,
CLS: 0.1,
});
Monitor Performance
import { usePerformance } from '@/hooks/usePerformance';
function MyComponent() {
const { metrics, recordMetric } = usePerformance();
useEffect(() => {
recordMetric('component_mount', Date.now());
}, []);
}
Memory Optimization
Monitor Memory
import { useMemory } from '@/hooks/useMemory';
function MyComponent() {
const { memory } = useMemory();
if (memory && memory.used > memory.limit * 0.9) {
// Handle high memory usage
}
}
Cleanup
useEffect(() => {
const subscription = subscribe();
return () => {
subscription.unsubscribe(); // Cleanup
};
}, []);
Best Practices
1. Measure First
// Use Lighthouse
// Use Web Vitals
// Use Performance API
2. Optimize Critical Path
- Load critical CSS first
- Defer non-critical JavaScript
- Optimize above-the-fold content
3. Minimize JavaScript
- Code splitting
- Tree shaking
- Remove unused code
4. Optimize Assets
- Compress images
- Use modern formats
- Minimize file sizes
5. Use Caching
- Browser cache
- Service Worker
- CDN caching
Performance Checklist
- [ ] Code splitting implemented
- [ ] Images optimized
- [ ] Caching configured
- [ ] Bundle size optimized
- [ ] Fonts optimized
- [ ] Performance budgets met
- [ ] Web Vitals tracked
- [ ] Memory monitored
- [ ] Lazy loading used
- [ ] Server components used
Tools
Analysis Tools
- Lighthouse: Performance auditing
- Web Vitals: Core Web Vitals
- Bundle Analyzer: Bundle size analysis
- React DevTools Profiler: Component profiling
Monitoring
- Web Vitals API: Real user monitoring
- Performance API: Custom metrics
- Error Tracking: Error monitoring