Performance Problems
Diagnosing and resolving performance issues, optimization strategies, and monitoring techniques.
Performance Problems
Guide to identifying, diagnosing, and resolving performance issues in your application.
Performance Monitoring
Core Web Vitals
Key Metrics to Monitor:
// Monitor Core Web Vitals
export function measureWebVitals() {
// Largest Contentful Paint (LCP)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
const lastEntry = entries[entries.length - 1]
console.log('LCP:', lastEntry.startTime)
}).observe({ entryTypes: ['largest-contentful-paint'] })
// First Input Delay (FID)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
entries.forEach((entry) => {
console.log('FID:', entry.processingStart - entry.startTime)
})
}).observe({ entryTypes: ['first-input'] })
// Cumulative Layout Shift (CLS)
let clsValue = 0
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value
}
}
console.log('CLS:', clsValue)
}).observe({ entryTypes: ['layout-shift'] })
}
Performance Monitoring Tools
// Next.js performance monitoring
export function reportWebVitals(metric) {
console.log(metric)
// Send to analytics
if (metric.label === 'web-vital') {
gtag('event', metric.name, {
event_category: 'Web Vitals',
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_label: metric.id,
non_interaction: true,
})
}
}
// Add to pages/_app.tsx
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
MyApp.reportWebVitals = reportWebVitals
Bundle Size Optimization
Analyzing Bundle Size
# Analyze bundle size
npm run build
npx @next/bundle-analyzer
# Check specific dependencies
npx bundlephobia <package-name>
# Webpack bundle analyzer
npm install --save-dev webpack-bundle-analyzer
Code Splitting Strategies
// Dynamic imports for route-based splitting
const DynamicComponent = dynamic(
() => import('../components/HeavyComponent'),
{
loading: () => <p>Loading...</p>,
ssr: false, // Disable SSR for client-only components
}
)
// Conditional loading
const AdminPanel = dynamic(
() => import('../components/AdminPanel'),
{
loading: () => <div>Loading admin panel...</div>,
}
)
export default function Dashboard({ user }) {
return (
<div>
<h1>Dashboard</h1>
{user.isAdmin && <AdminPanel />}
</div>
)
}
// Library code splitting
const Chart = dynamic(
() => import('react-chartjs-2').then(mod => mod.Chart),
{ ssr: false }
)
// Multiple dynamic imports
const [ComponentA, ComponentB] = await Promise.all([
import('./ComponentA'),
import('./ComponentB'),
])
Tree Shaking Optimization
// next.config.js
const nextConfig = {
webpack: (config, { isServer }) => {
// Optimize imports
config.resolve.alias = {
...config.resolve.alias,
'lodash': 'lodash-es',
}
// Tree shaking for specific libraries
config.module.rules.push({
test: /\.js$/,
include: /node_modules\/(some-library)/,
use: {
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { modules: false }]],
},
},
})
return config
},
// Reduce JavaScript bundle size
experimental: {
optimizePackageImports: ['lucide-react', 'date-fns'],
},
}
Image Optimization
Next.js Image Component
import Image from 'next/image'
// Optimized image loading
export function ProductImage({ product }) {
return (
<Image
src={product.imageUrl}
alt={product.name}
width={300}
height={200}
priority={product.featured} // Prioritize above-fold images
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..." // Low-quality placeholder
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
quality={85} // Optimize quality vs size
/>
)
}
// Responsive images
export function ResponsiveImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
fill
style={{ objectFit: 'cover' }}
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
25vw"
/>
)
}
Image Format Optimization
// next.config.js
const nextConfig = {
images: {
formats: ['image/avif', 'image/webp'],
domains: ['example.com', 'cdn.example.com'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 31536000, // 1 year
},
}
Database Performance
Query Optimization
// Efficient data fetching
export async function getProductsWithInventory() {
// Single query with joins instead of multiple queries
const { data } = await supabase
.from('products')
.select(`
id,
name,
sku,
price,
inventory!inner(
quantity_on_hand,
warehouse:warehouses(name, location)
)
`)
.eq('is_active', true)
.gte('inventory.quantity_on_hand', 1)
.order('name')
.limit(50)
return data
}
// Pagination for large datasets
export async function getPaginatedProducts(page = 1, limit = 20) {
const offset = (page - 1) * limit
const { data, count } = await supabase
.from('products')
.select('*', { count: 'exact' })
.range(offset, offset + limit - 1)
.order('created_at', { ascending: false })
return {
products: data,
totalCount: count,
currentPage: page,
totalPages: Math.ceil(count / limit),
}
}
Caching Strategies
// React Query for server state management
import { useQuery, useQueryClient } from '@tanstack/react-query'
export function useProducts() {
return useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
})
}
// SWR for data fetching
import useSWR from 'swr'
export function useProductData(productId) {
const { data, error, isLoading } = useSWR(
productId ? `products/${productId}` : null,
fetchProduct,
{
refreshInterval: 30000, // Refresh every 30 seconds
revalidateOnFocus: false,
dedupingInterval: 2000,
}
)
return { product: data, error, isLoading }
}
// Next.js caching
export async function getStaticProps() {
const products = await fetchProducts()
return {
props: { products },
revalidate: 60, // Regenerate page every 60 seconds
}
}
Frontend Performance
Component Optimization
import { memo, useMemo, useCallback } from 'react'
// Memoize expensive calculations
export const ProductList = memo(function ProductList({ products, filters }) {
const filteredProducts = useMemo(() => {
return products.filter(product => {
return filters.category === 'all' || product.category === filters.category
})
}, [products, filters.category])
const handleProductClick = useCallback((productId) => {
// Handle click
console.log('Product clicked:', productId)
}, [])
return (
<div>
{filteredProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onClick={handleProductClick}
/>
))}
</div>
)
})
// Optimize re-renders with React.memo
export const ProductCard = memo(function ProductCard({ product, onClick }) {
return (
<div onClick={() => onClick(product.id)}>
<h3>{product.name}</h3>
<p>{product.description}</p>
</div>
)
})
Virtual Scrolling for Large Lists
import { FixedSizeList as List } from 'react-window'
export function VirtualizedProductList({ products }) {
const Row = ({ index, style }) => (
<div style={style}>
<ProductCard product={products[index]} />
</div>
)
return (
<List
height={600}
itemCount={products.length}
itemSize={120}
width="100%"
>
{Row}
</List>
)
}
Memory Management
Memory Leak Prevention
// Cleanup subscriptions
export function useRealtimeUpdates(productId) {
useEffect(() => {
const subscription = supabase
.channel(`product-${productId}`)
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'products' },
handleUpdate
)
.subscribe()
// Cleanup on unmount
return () => {
subscription.unsubscribe()
}
}, [productId])
}
// Cleanup timers and intervals
export function usePolling(callback, interval) {
useEffect(() => {
const id = setInterval(callback, interval)
return () => clearInterval(id)
}, [callback, interval])
}
// Abort API requests on component unmount
export function useApiCall(url) {
const [data, setData] = useState(null)
useEffect(() => {
const controller = new AbortController()
fetch(url, { signal: controller.signal })
.then(response => response.json())
.then(setData)
.catch(error => {
if (error.name !== 'AbortError') {
console.error('Fetch error:', error)
}
})
return () => controller.abort()
}, [url])
return data
}
Performance Testing
Lighthouse CI
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
lhci:
name: Lighthouse
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- run: npm ci
- run: npm run build
- run: npm install -g @lhci/cli@0.8.x
- run: lhci autorun
// lighthouserc.js
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000'],
startServerCommand: 'npm start',
},
assert: {
assertions: {
'categories:performance': ['warn', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
},
},
upload: {
target: 'temporary-public-storage',
},
},
}
Load Testing
// Load testing with Artillery
// artillery.yml
config:
target: 'http://localhost:3000'
phases:
- duration: 60
arrivalRate: 10
scenarios:
- name: "Product browsing"
flow:
- get:
url: "/"
- get:
url: "/products"
- get:
url: "/products/{{ $randomString() }}"
// Performance monitoring
export function trackPerformance() {
// Track page load time
window.addEventListener('load', () => {
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart
console.log('Page load time:', loadTime)
})
// Track API response times
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.initiatorType === 'fetch') {
console.log('API call duration:', entry.duration)
}
}
})
observer.observe({ entryTypes: ['resource'] })
}
Common Performance Issues
Hydration Mismatches
- Symptom: Slow initial page load, flickering content
- Solution: Ensure server and client render the same content
Large Bundle Sizes
- Symptom: Slow JavaScript download and parsing
- Solution: Code splitting, tree shaking, lazy loading
Unnecessary Re-renders
- Symptom: Sluggish UI interactions
- Solution: Use React.memo, useMemo, useCallback
Memory Leaks
- Symptom: Increasing memory usage over time
- Solution: Cleanup subscriptions, timers, and event listeners
Database N+1 Queries
- Symptom: Multiple database queries for related data
- Solution: Use joins, batch queries, or data loaders
For performance monitoring tools and advanced optimization techniques, refer to the Next.js Performance Documentation.