Agent skill
performance-optimization
Skill for performance profiling and optimization of web applications. Use when conducting Lighthouse audits, analyzing bundles, implementing code splitting, optimizing images, configuring caching strategies, or improving Core Web Vitals. Provides patterns for frontend and backend performance improvements, including browser, CDN, and server caching.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/performance-optimization
SKILL.md
Performance Optimization
Skill for profiling and optimizing web application performance.
Overview
This skill provides guidance for:
- Performance Profiling - Lighthouse, bundle analysis, profiling tools
- Frontend Optimization - Code splitting, lazy loading, image optimization
- Caching Strategies - Browser, CDN, server-side caching
- Core Web Vitals - LCP, FID/INP, CLS optimization
Core Web Vitals
Metrics Overview
| Metric | Target | Description |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | Time to render largest content element |
| INP (Interaction to Next Paint) | < 200ms | Responsiveness to user interactions |
| CLS (Cumulative Layout Shift) | < 0.1 | Visual stability during page load |
Measurement Tools
# Lighthouse CLI
npx lighthouse https://example.com --output=json --output-path=./lighthouse-report.json
# Web Vitals in code
npm install web-vitals
// lib/web-vitals.ts
import { onLCP, onINP, onCLS } from 'web-vitals';
export function reportWebVitals() {
onLCP((metric) => {
console.log('LCP:', metric.value);
// Send to analytics
});
onINP((metric) => {
console.log('INP:', metric.value);
});
onCLS((metric) => {
console.log('CLS:', metric.value);
});
}
Lighthouse Audits
Running Audits
# Full audit
npx lighthouse https://example.com --view
# Specific categories
npx lighthouse https://example.com --only-categories=performance,accessibility
# Mobile simulation
npx lighthouse https://example.com --preset=mobile
# CI integration
npx lighthouse https://example.com --budget-path=./budget.json --output=json
Performance Budget
// budget.json
[
{
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "image", "budget": 500 },
{ "resourceType": "stylesheet", "budget": 100 },
{ "resourceType": "total", "budget": 1000 }
],
"resourceCounts": [
{ "resourceType": "script", "budget": 10 },
{ "resourceType": "third-party", "budget": 5 }
],
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "first-contentful-paint", "budget": 1500 },
{ "metric": "interactive", "budget": 3500 }
]
}
]
Common Lighthouse Issues and Fixes
| Issue | Impact | Fix |
|---|---|---|
| Render-blocking resources | LCP | Async/defer scripts, inline critical CSS |
| Large DOM size | All | Virtualization, pagination |
| Unused JavaScript | LCP | Code splitting, tree shaking |
| Unoptimized images | LCP | Next/Image, WebP, lazy loading |
| Layout shifts | CLS | Size attributes, font-display |
| Long tasks | INP | Code splitting, web workers |
Bundle Analysis
Webpack Bundle Analyzer
# Install
npm install --save-dev webpack-bundle-analyzer
# Next.js configuration
npm install @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
// Next.js config
});
# Run analysis
ANALYZE=true npm run build
Identifying Bundle Issues
| Issue | Indicator | Solution |
|---|---|---|
| Large dependencies | Single package > 100KB | Find smaller alternative |
| Duplicate packages | Same package multiple versions | Dedupe, peer dependencies |
| Unused exports | Large modules partially used | Tree shaking, selective imports |
| Dev dependencies in prod | moment locales, lodash full | Selective imports |
Import Optimization
// BAD: Imports entire library
import _ from 'lodash';
const result = _.debounce(fn, 300);
// GOOD: Import only what you need
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
// BAD: Barrel imports
import { Button, Input, Modal } from '@/components';
// GOOD: Direct imports (when barrel causes issues)
import { Button } from '@/components/Button';
import { Input } from '@/components/Input';
Code Splitting Strategies
Route-Based Splitting (Next.js)
// Automatic with App Router - each page is a separate chunk
// app/dashboard/page.tsx - separate chunk
// app/settings/page.tsx - separate chunk
Component-Based Splitting
// Dynamic import for heavy components
import dynamic from 'next/dynamic';
// Lazy load with loading state
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <ChartSkeleton />,
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({ isAdmin }) {
return (
<div>
<HeavyChart data={data} />
{isAdmin && <AdminPanel />}
</div>
);
}
Library Splitting
// Lazy load heavy libraries
const loadPdfLib = () => import('pdf-lib');
async function generatePdf() {
const { PDFDocument } = await loadPdfLib();
const doc = await PDFDocument.create();
// ...
}
Image Optimization
Next.js Image Component
import Image from 'next/image';
// Responsive image with automatic optimization
export function HeroImage() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // Preload for LCP
placeholder="blur"
blurDataURL={blurDataUrl}
/>
);
}
// Fill container
export function BackgroundImage() {
return (
<div className="relative w-full h-64">
<Image
src="/background.jpg"
alt="Background"
fill
style={{ objectFit: 'cover' }}
sizes="100vw"
/>
</div>
);
}
Image Format Selection
| Format | Use Case | Browser Support |
|---|---|---|
| WebP | General purpose, photos | Modern browsers |
| AVIF | Best compression, photos | Chrome, Firefox |
| SVG | Icons, logos, illustrations | All |
| PNG | Transparency needed | All |
| JPEG | Photos (fallback) | All |
Responsive Images
// next.config.js
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
formats: ['image/avif', 'image/webp'],
},
};
// Specify sizes for responsive loading
<Image
src="/product.jpg"
alt="Product"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
Caching Strategies
Browser Caching
// next.config.js - Static asset caching
module.exports = {
async headers() {
return [
{
source: '/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
source: '/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=0, must-revalidate',
},
],
},
];
},
};
CDN Caching
// API route with CDN caching
export async function GET(request: Request) {
const data = await fetchData();
return Response.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
},
});
}
Cache Control Headers
| Directive | Use Case |
|---|---|
public, max-age=31536000, immutable |
Versioned static assets (CSS, JS) |
public, max-age=3600 |
Semi-static content |
public, s-maxage=60, stale-while-revalidate=300 |
CDN caching with background refresh |
private, no-cache |
User-specific data |
no-store |
Sensitive data |
Server-Side Caching (FastAPI)
# Redis caching for API responses
from fastapi import FastAPI
from functools import wraps
import redis
import json
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def cache_response(ttl_seconds: int = 300):
"""Cache decorator for API endpoints."""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
# Generate cache key
cache_key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
# Check cache
cached = redis_client.get(cache_key)
if cached:
return json.loads(cached)
# Execute and cache
result = await func(*args, **kwargs)
redis_client.setex(cache_key, ttl_seconds, json.dumps(result))
return result
return wrapper
return decorator
@app.get("/products")
@cache_response(ttl_seconds=300)
async def get_products():
return await product_service.get_all()
Database Query Caching
# SQLAlchemy query caching
from sqlalchemy import event
from functools import lru_cache
class CachedRepository:
def __init__(self, session):
self.session = session
self._cache = {}
async def get_by_id(self, entity_id: int):
cache_key = f"entity:{entity_id}"
if cache_key in self._cache:
return self._cache[cache_key]
result = await self.session.get(Entity, entity_id)
self._cache[cache_key] = result
return result
def invalidate(self, entity_id: int):
cache_key = f"entity:{entity_id}"
self._cache.pop(cache_key, None)
Frontend Performance Patterns
Virtualization for Long Lists
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 5,
});
return (
<div ref={parentRef} className="h-96 overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
transform: `translateY(${virtualItem.start}px)`,
height: `${virtualItem.size}px`,
}}
>
{items[virtualItem.index].name}
</div>
))}
</div>
</div>
);
}
Debouncing User Input
import { useDeferredValue, useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';
function SearchInput() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// Or with explicit debounce
const debouncedSearch = useDebouncedCallback(
(value: string) => {
performSearch(value);
},
300
);
return (
<input
value={query}
onChange={(e) => {
setQuery(e.target.value);
debouncedSearch(e.target.value);
}}
/>
);
}
Optimistic Updates
import { useMutation, useQueryClient } from '@tanstack/react-query';
function TodoItem({ todo }) {
const queryClient = useQueryClient();
const toggleMutation = useMutation({
mutationFn: (completed: boolean) =>
api.updateTodo(todo.id, { completed }),
onMutate: async (completed) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['todos'] });
// Snapshot previous value
const previousTodos = queryClient.getQueryData(['todos']);
// Optimistically update
queryClient.setQueryData(['todos'], (old: Todo[]) =>
old.map((t) =>
t.id === todo.id ? { ...t, completed } : t
)
);
return { previousTodos };
},
onError: (err, completed, context) => {
// Rollback on error
queryClient.setQueryData(['todos'], context?.previousTodos);
},
});
return (
<input
type="checkbox"
checked={todo.completed}
onChange={(e) => toggleMutation.mutate(e.target.checked)}
/>
);
}
Backend Performance Patterns
Database Query Optimization
# Eager loading to avoid N+1 queries
from sqlalchemy.orm import joinedload, selectinload
# BAD: N+1 queries
users = session.query(User).all()
for user in users:
print(user.orders) # Separate query for each user!
# GOOD: Eager load relationships
users = session.query(User).options(
selectinload(User.orders)
).all()
# For nested relationships
users = session.query(User).options(
selectinload(User.orders).selectinload(Order.items)
).all()
Connection Pooling
# SQLAlchemy connection pool configuration
from sqlalchemy import create_engine
engine = create_engine(
DATABASE_URL,
pool_size=20, # Number of persistent connections
max_overflow=10, # Additional connections when pool exhausted
pool_timeout=30, # Timeout waiting for connection
pool_recycle=1800, # Recycle connections after 30 mins
pool_pre_ping=True, # Verify connection before use
)
Async Operations
from fastapi import FastAPI
import asyncio
@app.get("/dashboard")
async def get_dashboard():
# Run independent queries concurrently
user_data, orders, notifications = await asyncio.gather(
get_user_profile(),
get_recent_orders(),
get_notifications(),
)
return {
"user": user_data,
"orders": orders,
"notifications": notifications,
}
Performance Monitoring
Real User Monitoring (RUM)
// Send performance data to analytics
export function trackPagePerformance() {
if (typeof window === 'undefined') return;
window.addEventListener('load', () => {
setTimeout(() => {
const timing = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const metrics = {
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
ttfb: timing.responseStart - timing.requestStart,
download: timing.responseEnd - timing.responseStart,
domInteractive: timing.domInteractive - timing.fetchStart,
domComplete: timing.domComplete - timing.fetchStart,
loadComplete: timing.loadEventEnd - timing.fetchStart,
};
// Send to analytics
analytics.track('page_performance', metrics);
}, 0);
});
}
Performance Optimization Checklist
Frontend
- Lighthouse score > 90 for Performance
- LCP < 2.5s
- INP < 200ms
- CLS < 0.1
- Bundle size within budget
- Images optimized (WebP/AVIF, lazy loading)
- Critical CSS inlined
- JavaScript async/deferred
- Code splitting implemented
- Fonts optimized (font-display: swap)
Backend
- Database queries optimized (no N+1)
- Connection pooling configured
- Caching strategy implemented
- Async operations where beneficial
- Response compression enabled
- Indexes on frequently queried columns
Caching
- Static assets cached with long TTL
- CDN configured for static content
- API responses cached appropriately
- Cache invalidation strategy defined
References
For detailed guidance, see:
references/lighthouse-guide.md- Comprehensive Lighthouse optimizationreferences/caching-patterns.md- Advanced caching strategies
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?