--- name: performance-optimization description: Web performance optimization including bundle analysis, lazy loading, caching strategies, and Core Web Vitals --- # Performance Optimization ## Bundle Analysis and Code Splitting ```typescript // Dynamic import for route-level code splitting const Dashboard = lazy(() => import("./pages/Dashboard")); const Settings = lazy(() => import("./pages/Settings")); function App() { return ( }> } /> } /> ); } ``` ```javascript // vite.config.ts - manual chunk splitting export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { vendor: ["react", "react-dom"], charts: ["recharts", "d3"], editor: ["@monaco-editor/react"], }, }, }, }, }); ``` ```bash # Analyze bundle composition npx vite-bundle-visualizer npx source-map-explorer dist/assets/*.js ``` ## Image Optimization ```tsx import Image from "next/image"; function ProductImage({ src, alt }: { src: string; alt: string }) { return ( {alt} ); } ``` ```html Product photo ``` ## Caching Headers ```typescript function setCacheHeaders(res: Response, options: CacheOptions) { if (options.immutable) { res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); return; } if (options.revalidate) { res.setHeader("Cache-Control", `public, max-age=0, s-maxage=${options.revalidate}, stale-while-revalidate=${options.staleWhileRevalidate ?? 86400}`); return; } res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); } app.use("/assets", (req, res, next) => { setCacheHeaders(res, { immutable: true }); next(); }); app.use("/api", (req, res, next) => { setCacheHeaders(res, { revalidate: 60, staleWhileRevalidate: 3600 }); next(); }); ``` ## Virtual Lists for Large Data ```tsx import { useVirtualizer } from "@tanstack/react-virtual"; function VirtualList({ items }: { items: Item[] }) { const parentRef = useRef(null); const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 50, overscan: 5, }); return (
{virtualizer.getVirtualItems().map((virtualRow) => (
))}
); } ``` ## Core Web Vitals Monitoring ```typescript import { onCLS, onINP, onLCP } from "web-vitals"; function sendMetric(metric: { name: string; value: number; id: string }) { navigator.sendBeacon("/api/vitals", JSON.stringify(metric)); } onCLS(sendMetric); onINP(sendMetric); onLCP(sendMetric); ``` - **LCP** (Largest Contentful Paint): < 2.5s. Preload hero images, optimize server response time. - **INP** (Interaction to Next Paint): < 200ms. Avoid long tasks, use `requestIdleCallback`. - **CLS** (Cumulative Layout Shift): < 0.1. Set explicit dimensions on images and embeds. ## Anti-Patterns - Loading all JavaScript upfront instead of code-splitting by route - Serving unoptimized images (no WebP/AVIF, no responsive sizes) - Missing `width` and `height` on images (causes layout shift) - Using `Cache-Control: no-cache` on static assets with content hashes - Rendering thousands of DOM nodes instead of virtualizing lists - Blocking the main thread with synchronous computation ## Checklist - [ ] Routes lazy-loaded with dynamic `import()` and Suspense - [ ] Bundle analyzed and vendor chunks separated - [ ] Images served in WebP/AVIF with responsive `sizes` attribute - [ ] LCP image preloaded with `fetchpriority="high"` - [ ] Static assets cached with immutable headers and content hashes - [ ] Lists with 100+ items use virtualization - [ ] Core Web Vitals monitored in production (LCP, INP, CLS) - [ ] No render-blocking resources in the critical path