web-performance-web-performance ยท diff
git:20260202.b236384 to git:20260316.00cb75b
138 added, 113 removed. Audit A to A.
---
name: web-performance-web-performance
- description: Bundle optimization, render performance
+ description: Bundle optimization, render performance, Core Web Vitals
---
- # Performance Standards
+ # Web Performance Patterns
- > **Quick Guide:** Build performance? Turborepo caching with >80% hit rate. Bundle budgets? < 200KB main bundle. Core Web Vitals? LCP < 2.5s, INP < 200ms, CLS < 0.1. React patterns? Strategic memo/useMemo (or React Compiler), lazy loading, virtualization for 100+ items. Monitoring? Real User Monitoring with web-vitals library.
+ > **Quick Guide:** Bundle budgets: < 200KB main bundle gzipped. Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1. Profile before optimizing -- measure actual bottlenecks, don't guess. Lazy load routes and heavy libraries. Use React Compiler (React 19+) for automatic memoization; manual memo only when profiling proves a bottleneck. Monitor real users with web-vitals library, not just Lighthouse.
---
<critical_requirements>
## CRITICAL: Before Optimizing Performance
- **(You MUST set performance budgets BEFORE building features - bundle size limits, Core Web Vitals targets, build time thresholds)**
+ **(You MUST profile BEFORE optimizing - measure actual bottlenecks with browser DevTools, framework profiler, or Lighthouse)**
- **(You MUST profile BEFORE optimizing - measure actual bottlenecks with Chrome DevTools, React Profiler, or Lighthouse)**
+ **(You MUST set performance budgets BEFORE building features - bundle size limits and Core Web Vitals targets)**
- **(You MUST use named constants for ALL performance thresholds - no magic numbers like `< 200KB` or `< 2.5s`)**
+ **(You MUST use named constants for ALL performance thresholds - no magic numbers like `200` or `2.5`)**
- **(You MUST monitor Core Web Vitals in production with web-vitals library - track LCP, INP, CLS for real users)**
+ **(You MUST monitor Core Web Vitals in production - track LCP, INP, CLS for real users, not just lab metrics)**
**(You MUST lazy load route components and heavy libraries - code splitting prevents large initial bundles)**
</critical_requirements>
---
- **Detailed Resources:**
-
- - For code examples, see [examples.md](examples.md)
- - For decision frameworks and anti-patterns, see [reference.md](reference.md)
-
- ---
-
- **Auto-detection:** Core Web Vitals, bundle size optimization, LCP, INP, CLS, FID, lazy loading, memoization, React Compiler, performance monitoring, web-vitals library, Turborepo caching, bundle budget, virtualization
+ **Auto-detection:** Core Web Vitals, bundle size optimization, LCP, INP, CLS, lazy loading, code splitting, memoization, React Compiler, performance monitoring, web-vitals library, bundle budget, virtualization, react-window, TanStack Virtual
**When to use:**
- Optimizing Core Web Vitals (LCP < 2.5s, INP < 200ms, CLS < 0.1)
- Setting and enforcing bundle size budgets (< 200KB main bundle)
- - Implementing React performance patterns (strategic memo, lazy loading, virtualization)
+ - Implementing runtime performance patterns (strategic memo, lazy loading, virtualization)
- Monitoring performance with web-vitals library in production
- - Configuring Turborepo caching for build performance (> 80% cache hit rate)
+ - Code splitting and tree shaking to reduce initial bundle
**When NOT to use:**
- Before measuring (premature optimization adds complexity without benefit)
- For simple components (memoizing cheap renders adds overhead)
- Internal admin tools with < 10 users (ROI too low)
- Prototypes and MVPs (optimize after validating product-market fit)
**Key patterns covered:**
- - Core Web Vitals targets (LCP < 2.5s, INP < 200ms, CLS < 0.1)
+ - Core Web Vitals targets and improvement strategies (LCP, INP, CLS)
- Bundle size budgets (< 200KB main, < 500KB total initial load)
- - Strategic React optimization (memo/useMemo when needed, not everywhere - profile first)
- - Image optimization (WebP/AVIF, lazy loading, Next.js Image component)
- - Build performance with Turborepo caching and parallel execution
+ - Strategic memoization (profile first; React Compiler handles most cases)
+ - Code splitting (route-based lazy loading, dynamic imports, tree shaking)
+ - Image optimization (modern formats, lazy loading, responsive images)
---
+ **Detailed Resources:**
+
+ - [examples/core.md](examples/core.md) - React memoization, virtual scrolling, debouncing
+ - [examples/code-splitting.md](examples/code-splitting.md) - Lazy loading, tree shaking, bundle budgets
+ - [examples/web-vitals.md](examples/web-vitals.md) - LCP, INP, CLS patterns and monitoring
+ - [examples/image-optimization.md](examples/image-optimization.md) - Image formats, lazy loading, responsive images
+ - [reference.md](reference.md) - Decision frameworks and anti-patterns
+
+ ---
+
<philosophy>
## Philosophy
Performance is a feature, not an afterthought. Fast applications improve user experience, conversion rates, and SEO rankings. Performance optimization requires measurement before action, budgets before building, and monitoring in production.
- **Core performance principles:**
+ **Core principles:**
- **Measure first, optimize second** - Profile actual bottlenecks, don't guess
- **Set budgets early** - Define bundle size limits and Core Web Vitals targets before building
- **Monitor real users** - Lab metrics (Lighthouse) differ from real-world performance (RUM)
- **Optimize strategically** - Memoize expensive operations, not everything
- **Lazy load by default** - Load code when needed, not upfront
</philosophy>
---
<patterns>
## Core Patterns
- ### Pattern 1: Build Performance with Turborepo Caching
-
- Use Turborepo to cache build artifacts across machines (local and CI). Target > 80% cache hit rate for optimal build performance.
-
- #### Build Time Targets
-
- ```typescript
- // constants/performance.ts
- export const BUILD_TIME_TARGETS = {
- FULL_BUILD_COLD_CACHE_MINUTES: 2,
- INCREMENTAL_BUILD_WARM_CACHE_SECONDS: 30,
- DEVELOPMENT_REBUILD_HMR_SECONDS: 5,
- PRODUCTION_BUILD_MINUTES: 5,
- } as const;
-
- export const CACHE_HIT_RATE_TARGET_PERCENT = 80;
- ```
-
- **Targets:**
-
- - **Full build** (cold cache): < 2 min
- - **Incremental build** (warm cache): < 30s
- - **Development rebuild** (HMR): < 5s
- - **Production build**: < 5 min
- - **Cache hit rate**: > 80%
-
- For detailed Turborepo configuration examples, see [examples.md](examples.md#turborepo-configuration).
-
- ---
-
- ### Pattern 2: Bundle Size Budgets
+ ### Pattern 1: Bundle Size Budgets
Set and enforce bundle size limits to prevent bloat. Main bundle should be < 200KB gzipped for fast downloads on 3G networks.
- #### Bundle Size Constants
-
```typescript
// constants/bundle-budgets.ts
export const BUNDLE_SIZE_BUDGETS_KB = {
MAIN_BUNDLE_GZIPPED: 200,
VENDOR_BUNDLE_GZIPPED: 150,
ROUTE_BUNDLE_GZIPPED: 100,
TOTAL_INITIAL_LOAD_GZIPPED: 500,
MAIN_CSS_GZIPPED: 50,
CRITICAL_CSS_INLINE: 14, // Fits in first TCP packet
} as const;
-
- export const IMAGE_SIZE_BUDGETS_KB = {
- HERO_IMAGE: 200,
- THUMBNAIL: 50,
- } as const;
```
- #### Recommended Budgets
-
- **JavaScript bundles:**
-
- - **Main bundle**: < 200 KB (gzipped)
- - **Vendor bundle**: < 150 KB (gzipped)
- - **Route bundles**: < 100 KB each (gzipped)
- - **Total initial load**: < 500 KB (gzipped)
-
**Why these limits:** 200 KB โ 1 second download on 3G, faster Time to Interactive (TTI), better mobile performance
- **CSS bundles:**
-
- - **Main CSS**: < 50 KB (gzipped)
- - **Critical CSS**: < 14 KB (inlined, fits in first TCP packet)
-
- **Images:**
+ **Recommended budgets:**
- - **Hero images**: < 200 KB (use WebP/AVIF)
- - **Thumbnails**: < 50 KB
- - **Icons**: Use SVG or icon fonts
+ - **Main bundle**: < 200 KB gzipped
+ - **Vendor bundle**: < 150 KB gzipped
+ - **Route bundles**: < 100 KB each gzipped
+ - **Total initial load**: < 500 KB gzipped
+ - **Main CSS**: < 50 KB gzipped
+ - **Critical CSS**: < 14 KB inlined (fits in first TCP packet)
- For code splitting and bundle analysis examples, see [examples.md](examples.md#code-splitting-strategies).
+ For enforcement examples, see [examples/code-splitting.md](examples/code-splitting.md).
---
- ### Pattern 3: Core Web Vitals Optimization
+ ### Pattern 2: Core Web Vitals Optimization
Optimize for Google's Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1. These metrics impact SEO and user experience.
- **Note:** INP (Interaction to Next Paint) replaced FID (First Input Delay) as a Core Web Vital on March 12, 2024. FID is now deprecated.
-
- #### Core Web Vitals Constants
-
```typescript
// constants/web-vitals.ts
export const CORE_WEB_VITALS_THRESHOLDS = {
LCP_SECONDS: 2.5, // Largest Contentful Paint
- FID_MS: 100, // First Input Delay
- INP_MS: 200, // Interaction to Next Paint (replacing FID)
+ INP_MS: 200, // Interaction to Next Paint
CLS_SCORE: 0.1, // Cumulative Layout Shift
FCP_SECONDS: 1.8, // First Contentful Paint
TTI_SECONDS: 3.8, // Time to Interactive
TBT_MS: 300, // Total Blocking Time
TTFB_MS: 800, // Time to First Byte
} as const;
```
#### LCP (Largest Contentful Paint): < 2.5s
- Measures loading performance. When largest element becomes visible.
-
- **How to improve:**
+ Measures loading performance -- when the largest visible element renders.
- - Optimize images (WebP, lazy loading)
- - Minimize render-blocking resources
- - Use CDN for static assets
- - Server-side rendering (SSR) or Static Site Generation (SSG)
+ **How to improve:** Optimize images (modern formats, preload hero images), minimize render-blocking resources, use CDN for static assets, SSR or SSG for critical content.
#### INP (Interaction to Next Paint): < 200ms
- Measures interactivity. Time from user interaction to next visual update. INP replaced FID as a Core Web Vital on March 12, 2024.
+ Measures interactivity across ALL user interactions (replaced FID in March 2024). Includes input delay, processing time, and presentation delay.
- **Why INP replaced FID:** FID only measured the first interaction and only the input delay. INP measures ALL interactions throughout the page visit, including input delay, processing time, and presentation delay.
+ **How to improve:** Minimize JavaScript execution time, code split to load less JS upfront, use web workers for heavy computation, break up long tasks (> 50ms) with `scheduler.yield()` or `setTimeout`.
- **How to improve:**
+ #### CLS (Cumulative Layout Shift): < 0.1
- - Minimize JavaScript execution time
- - Code splitting (load less JS upfront)
- - Use web workers for heavy computation
- - Debounce/throttle expensive event handlers
- - Break up long tasks (> 50ms) with `scheduler.yield()` or `setTimeout`
+ Measures visual stability -- prevents unexpected layout shifts.
- #### CLS (Cumulative Layout Shift): < 0.1
+ **How to improve:** Set explicit image/video dimensions, reserve space for dynamic content (ads, embeds), avoid injecting content above existing content, use `font-display: swap` with `size-adjust`.
- Measures visual stability. Prevents unexpected layout shifts.
+ For detailed examples and monitoring setup, see [examples/web-vitals.md](examples/web-vitals.md).
- **How to improve:**
+ ---
- - Set image/video dimensions
- - Reserve space for dynamic content
- - Avoid injecting content above existing content
- - Use `font-display: swap` with size-adjust
+ ### Pattern 3: Code Splitting and Lazy Loading
- For detailed Core Web Vitals examples and production monitoring setup, see [examples.md](examples.md#core-web-vitals-examples).
+ Lazy load route components and heavy libraries. Code splitting keeps the initial bundle small by loading code on demand.
+ ```typescript
+ import { lazy, Suspense } from 'react';
+
+ // Route-based splitting - each route is a separate chunk
+ const Dashboard = lazy(() => import('./pages/dashboard'));
+ const Reports = lazy(() => import('./pages/reports'));
+
+ export function App() {
+ return (
+ <Suspense fallback={<PageLoader />}>
+ <Routes>
+ <Route path="/dashboard" element={<Dashboard />} />
+ <Route path="/reports" element={<Reports />} />
+ </Routes>
+ </Suspense>
+ );
+ }
+ ```
+
+ **Why good:** Splits bundle by route, loads components on demand, users only download what they navigate to
+
+ **When to lazy load:** Route components, heavy feature modules, modals/dialogs, below-fold content
+
+ **When NOT to lazy load:** Above-fold components, error boundaries, loading states
+
+ For tree shaking and bundle enforcement, see [examples/code-splitting.md](examples/code-splitting.md).
+
+ ---
+
+ ### Pattern 4: Strategic Memoization
+
+ Profile before memoizing. React Compiler (v1.0, Oct 2025) auto-memoizes in most cases. Manual memo only when profiling proves a bottleneck.
+
+ ```typescript
+ // Only memoize when profiling shows > 5ms render time
+ const EXPENSIVE_RENDER_MS = 5;
+
+ // โ
Expensive calculation with large dataset
+ const sortedRows = useMemo(
+ () => [...rows].sort((a, b) => compareValues(a[sortColumn], b[sortColumn])),
+ [rows, sortColumn],
+ );
+
+ // โ Trivial calculation - memo overhead exceeds cost
+ const doubled = useMemo(() => value * 2, [value]);
+ ```
+
+ **React Compiler (React 19+):** Automatically memoizes components, values, and functions at build time. Manual `useMemo`/`useCallback`/`React.memo` rarely needed. Only add manual memo for: third-party interop, non-pure computations, or when profiling shows the compiler missed an optimization.
+
+ For complete memoization patterns, see [examples/core.md](examples/core.md).
+
</patterns>
---
+ <red_flags>
+
+ ## RED FLAGS
+
+ **High Priority Issues:**
+
+ - No performance budgets defined -- bundle sizes grow unnoticed, Core Web Vitals degrade
+ - Memoizing everything without profiling -- adds overhead, increases complexity, premature optimization
+ - Not lazy loading routes -- massive initial bundles, slow Time to Interactive
+ - Importing entire libraries (`import _ from 'lodash'`) -- bundles unused code, prevents tree shaking
+ - Not optimizing images -- images are 50%+ of page weight; modern formats reduce size 30-50%
+ - Blocking main thread with heavy computation -- causes high INP, use web workers or break up long tasks
+
+ **Medium Priority Issues:**
+
+ - Not monitoring Core Web Vitals in production -- lab metrics differ from real users
+ - Rendering 100+ items without virtualization -- DOM bloat, slow scrolling
+ - No bundle size enforcement in CI -- regressions slip through code review
+ - Using CommonJS imports (`require()`) -- prevents tree shaking
+
+ **Gotchas & Edge Cases:**
+
+ - `React.memo` uses shallow comparison -- deep object props always trigger re-render
+ - `useMemo`/`useCallback` have overhead -- only use for expensive operations (> 5ms)
+ - React Compiler (v1.0) handles memoization automatically -- manual memo is rarely needed
+ - Lighthouse scores differ from real users -- always monitor RUM with web-vitals
+ - Code splitting increases total bundle size slightly (runtime overhead) -- net win for initial load
+ - Virtual scrolling breaks browser find-in-page (Cmd+F)
+ - Lazy loading doesn't work in SSR -- components load on client mount
+ - AVIF support is ~95% (2026) -- always provide WebP fallbacks
+ - Bundle size budgets should account for gzip/brotli compression
+
+ </red_flags>
+
+ ---
+
<critical_reminders>
## CRITICAL REMINDERS
- **(You MUST set performance budgets BEFORE building features - bundle size limits, Core Web Vitals targets, build time thresholds)**
+ **(You MUST profile BEFORE optimizing - measure actual bottlenecks with browser DevTools, framework profiler, or Lighthouse)**
- **(You MUST profile BEFORE optimizing - measure actual bottlenecks with Chrome DevTools, React Profiler, or Lighthouse)**
+ **(You MUST set performance budgets BEFORE building features - bundle size limits and Core Web Vitals targets)**
- **(You MUST use named constants for ALL performance thresholds - no magic numbers like `< 200KB` or `< 2.5s`)**
+ **(You MUST use named constants for ALL performance thresholds - no magic numbers like `200` or `2.5`)**
- **(You MUST monitor Core Web Vitals in production with web-vitals library - track LCP, INP, CLS for real users)**
+ **(You MUST monitor Core Web Vitals in production - track LCP, INP, CLS for real users, not just lab metrics)**
**(You MUST lazy load route components and heavy libraries - code splitting prevents large initial bundles)**
**Failure to follow these rules will result in slow applications, poor Core Web Vitals, large bundles, and degraded user experience.**
</critical_reminders>