{"kind":"AgentDefinition","metadata":{"namespace":"community","name":"performance-optimization","version":"0.1.0"},"spec":{"agents_md":"---\napplyTo: '**'\ndescription: 'Comprehensive web performance standards based on Core Web Vitals (LCP, INP, CLS), with 50+ anti-patterns, detection regex, framework-specific fixes for modern web frameworks, and modern API guidance.'\n---\n\n# Performance Standards\n\nComprehensive performance rules for web application development. Every anti-pattern includes a severity classification, detection method, Core Web Vitals metric impacted, and corrective code examples.\n\n**Severity levels:**\n\n- **CRITICAL** — Directly degrades a Core Web Vital past the \"poor\" threshold. Must be fixed before merge.\n- **IMPORTANT** — Measurably impacts user experience. Fix in same sprint.\n- **SUGGESTION** — Optimization opportunity. Plan for a future iteration.\n\n---\n\n## Core Web Vitals Quick Reference\n\n### LCP (Largest Contentful Paint)\n\n**Good: \u003c 2.5s | Needs Improvement: 2.5-4s | Poor: \u003e 4s**\n\nMeasures when the largest visible content element finishes rendering. Four sequential phases:\n\n| Phase | Target | What It Measures |\n|-------|--------|-----------------|\n| TTFB | ~40% of budget | Server response time |\n| Resource Load Delay | \u003c 10% | Time between TTFB and LCP resource fetch start |\n| Resource Load Duration | ~40% | Download time for the LCP resource |\n| Element Render Delay | \u003c 10% | Time between download and paint |\n\n### INP (Interaction to Next Paint)\n\n**Good: \u003c 200ms | Needs Improvement: 200-500ms | Poor: \u003e 500ms**\n\nMeasures latency of all user interactions, reports the worst. Three phases:\n\n| Phase | Optimization |\n|-------|-------------|\n| Input Delay | Break long tasks, yield to browser |\n| Processing Time | Keep handlers \u003c 50ms |\n| Presentation Delay | Minimize DOM size, avoid forced layout |\n\n\u003e **Diagnostic tool:** Use the Long Animation Frames (LoAF) API (Chrome 123+) to debug INP issues. LoAF provides better attribution than the legacy Long Tasks API, including script source and rendering time.\n\n### CLS (Cumulative Layout Shift)\n\n**Good: \u003c 0.1 | Needs Improvement: 0.1-0.25 | Poor: \u003e 0.25**\n\nLayout shift sources: images without dimensions, dynamically injected content, web font FOUT, late-loading ads. Shifts within 500ms of user interaction are exempt.\n\n---\n\n## Loading and LCP Anti-Patterns (L1-L10)\n\n### L1: Render-Blocking CSS Without Critical Extraction\n\n- **Severity**: CRITICAL\n- **Detection**: `\u003clink.*rel=\"stylesheet\"` in `\u003chead\u003e` loading large CSS\n- **CWV**: LCP\n\n```html\n\u003c!-- BAD --\u003e\n\u003clink rel=\"stylesheet\" href=\"/styles/main.css\" /\u003e\n\n\u003c!-- GOOD — inline critical CSS (extracted at build time), preload the rest --\u003e\n\u003cstyle\u003e/* critical above-fold CSS, inlined by a tool like Critters/Beasties */\u003c/style\u003e\n\u003clink rel=\"preload\" href=\"/styles/main.css\" as=\"style\" /\u003e\n\u003clink rel=\"stylesheet\" href=\"/styles/main.css\" /\u003e\n```\n\nPrefer build-time critical CSS extraction (e.g., Critters, Beasties, Next.js `experimental.optimizeCss`) plus a normal `\u003clink rel=\"stylesheet\"\u003e`. Avoid the older `media=\"print\" onload=\"this.media='all'\"` trick: inline event handlers are blocked under a strict CSP (no `'unsafe-inline'` / no `script-src-attr 'unsafe-inline'`), which would prevent the stylesheet from ever activating and cause a styling regression. If non-critical CSS truly must be deferred, load it via an **external** script that swaps `media`, not an inline handler.\n\n### L2: Render-Blocking Synchronous Script\n\n- **Severity**: CRITICAL\n- **Detection**: `\u003cscript.*src=` without `async|defer|type=\"module\"`\n- **CWV**: LCP\n\n```html\n\u003c!-- BAD --\u003e\n\u003cscript src=\"/vendor/analytics.js\"\u003e\u003c/script\u003e\n\n\u003c!-- GOOD --\u003e\n\u003cscript src=\"/vendor/analytics.js\" defer\u003e\u003c/script\u003e\n```\n\n### L3: Missing Preconnect to Critical Origins\n\n- **Severity**: IMPORTANT\n- **Detection**: Third-party API/CDN URLs without `\u003clink rel=\"preconnect\"\u003e`\n- **CWV**: LCP\n\n```html\n\u003clink rel=\"preconnect\" href=\"https://api.example.com\" /\u003e\n\u003clink rel=\"dns-prefetch\" href=\"https://analytics.example.com\" /\u003e\n```\n\n### L4: Missing Preload for LCP Resource\n\n- **Severity**: CRITICAL\n- **Detection**: LCP image/font not preloaded\n- **CWV**: LCP\n\n```html\n\u003clink rel=\"preload\" as=\"image\" href=\"/hero.webp\" fetchpriority=\"high\" /\u003e\n```\n\n### L5: Client-Side Data Fetching for Main Content\n\n- **Severity**: CRITICAL\n- **Detection**: `useEffect.*fetch|useEffect.*axios|ngOnInit.*subscribe`\n- **CWV**: LCP\n\n```tsx\n// BAD — content appears after JS execution + API call\n'use client';\nfunction Page() {\n  const [data, setData] = useState(null);\n  useEffect(() =\u003e { fetch('/api/data').then(r =\u003e r.json()).then(setData); }, []);\n  return \u003cdiv\u003e{data?.title}\u003c/div\u003e;\n}\n\n// GOOD — Server Component fetches data before HTML is sent\nasync function Page() {\n  const data = await fetch('https://api.example.com/data').then(r =\u003e r.json());\n  return \u003cdiv\u003e{data.title}\u003c/div\u003e;\n}\n```\n\n### L6: Excessive Redirect Chains\n\n- **Severity**: IMPORTANT\n- **Detection**: Multiple sequential redirects (HTTP 301/302 chains)\n- **CWV**: LCP\n\nEach redirect adds 200-300ms. Maximum one redirect.\n\n### L7: Missing fetchpriority on LCP Element\n\n- **Severity**: IMPORTANT\n- **Detection**: Above-fold hero image without `fetchpriority=\"high\"` or `priority` prop\n- **CWV**: LCP\n\n```tsx\n// Next.js\n\u003cImage src=\"/hero.webp\" alt=\"Hero\" width={1200} height={600} priority /\u003e\n\n// Angular\n\u003cimg ngSrc=\"/hero.webp\" alt=\"Hero\" width=\"1200\" height=\"600\" priority\u003e\n\n// Plain HTML\n\u003cimg src=\"/hero.webp\" alt=\"Hero\" width=\"1200\" height=\"600\" fetchpriority=\"high\" /\u003e\n```\n\n### L8: Third-Party Scripts in Head Without Async/Defer\n\n- **Severity**: IMPORTANT\n- **Detection**: `\u003cscript.*src=\"https://` without `async|defer`\n- **CWV**: LCP\n\nDefer non-essential scripts. Use facade pattern for chat widgets.\n\n### L9: Oversized Initial HTML (\u003e14KB)\n\n- **Severity**: SUGGESTION\n- **Detection**: Server-rendered HTML larger than 14KB\n- **CWV**: LCP\n\nReduce inline CSS/JS, remove whitespace, use streaming SSR with Suspense boundaries.\n\n### L10: Missing Compression\n\n- **Severity**: IMPORTANT\n- **Detection**: Server not returning `content-encoding: br` or `gzip`\n- **CWV**: LCP\n\nEnable Brotli (15-25% better than gzip) at CDN/server level.\n\n---\n\n## Rendering and Hydration Anti-Patterns (R1-R8)\n\n### R1: Entire Component Tree Marked \"use client\"\n\n- **Severity**: CRITICAL\n- **Detection**: `\"use client\"` at top-level layout or page component\n- **CWV**: LCP + INP\n\nPush `\"use client\"` down to leaf components that need interactivity.\n\n### R2: Missing Suspense Boundaries for Async Data\n\n- **Severity**: IMPORTANT\n- **Detection**: Server Components doing data fetching without `\u003cSuspense\u003e`\n- **CWV**: LCP\n\n```tsx\n// GOOD — stream shell immediately, fill in data progressively\nasync function Page() {\n  const user = await getUser();\n  return (\n    \u003cdiv\u003e\n      \u003cHeader user={user} /\u003e\n      \u003cSuspense fallback={\u003cPostsSkeleton /\u003e}\u003e\n        \u003cPosts /\u003e\n      \u003c/Suspense\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n### R3: Hydration Mismatch from Dynamic Client Content\n\n- **Severity**: IMPORTANT\n- **Detection**: `Date.now()|Math.random()|window\\.innerWidth` in SSR components\n- **CWV**: CLS\n\nUse `useEffect` for client-only values, or `suppressHydrationWarning` for known differences.\n\n### R4: Missing Streaming for Slow Data Sources\n\n- **Severity**: IMPORTANT\n- **Detection**: Page awaiting all data before sending HTML\n- **CWV**: LCP (TTFB)\n\nUse streaming SSR with Suspense boundaries. Shell streams immediately; slow data fills in progressively.\n\n### R5: Unstable References Causing Re-renders\n\n- **Severity**: IMPORTANT\n- **Detection**: `style=\\{\\{|onClick=\\{\\(\\) =\u003e` inline in JSX\n- **CWV**: INP\n\nReact 19+ with React Compiler enabled (separate babel/SWC build plugin): auto-memoized. Without Compiler: extract or memoize with `useMemo`/`useCallback`. Angular: OnPush. Vue: `computed()`.\n\n### R6: Missing Virtualization for Long Lists\n\n- **Severity**: IMPORTANT\n- **Detection**: `.map(` rendering \u003e100 items without virtual scrolling\n- **CWV**: INP\n\nUse TanStack Virtual, react-window, Angular CDK Virtual Scroll, or vue-virtual-scroller.\n\n### R7: SSR of Immediately-Hidden Content\n\n- **Severity**: SUGGESTION\n- **Detection**: Server-rendering `display: none` components\n- **CWV**: LCP (TTFB)\n\nUse client-side rendering for modals, drawers, dropdowns. Angular: `@defer`. React: `React.lazy`.\n\n### R8: Missing `key` Prop on List Items\n\n- **Severity**: IMPORTANT\n- **Detection**: `.map(` without `key=` prop\n- **CWV**: INP\n\n```tsx\n// GOOD — stable unique key\n{items.map(item =\u003e \u003cRow key={item.id} data={item} /\u003e)}\n```\n\nNever use array index as key if list can reorder.\n\n---\n\n## JavaScript Runtime and INP Anti-Patterns (J1-J8)\n\n### J1: Long Synchronous Task in Event Handler\n\n- **Severity**: CRITICAL\n- **Detection**: Event handlers with heavy computation (\u003e50ms)\n- **CWV**: INP\n\n```typescript\n// GOOD — yield to browser\nasync function handleClick() {\n  setLoading(true);\n  await (globalThis.scheduler?.yield?.() ?? new Promise(r =\u003e setTimeout(r, 0)));\n  const result = expensiveComputation(data);\n  setResult(result);\n}\n```\n\nMove heavy work to Web Worker for best results.\n\n\u003e **Note:** `scheduler.yield()` is supported in Chrome 129+, Firefox 129+, but NOT Safari as of April 2026. Fallback: `await (globalThis.scheduler?.yield?.() ?? new Promise(r =\u003e setTimeout(r, 0)))`.\n\n### J2: Layout Thrashing\n\n- **Severity**: CRITICAL\n- **Detection**: `offsetHeight|offsetWidth|getBoundingClientRect|clientHeight` in loops\n- **CWV**: INP\n\n```typescript\n// GOOD — batch reads then batch writes\nconst heights = elements.map(el =\u003e el.offsetHeight);\nelements.forEach((el, i) =\u003e { el.style.height = `${heights[i] + 10}px`; });\n```\n\n### J3: setInterval/setTimeout Without Cleanup\n\n- **Severity**: IMPORTANT\n- **Detection**: `setInterval|setTimeout` without cleanup\n- **Impact**: Memory\n\n```tsx\nuseEffect(() =\u003e {\n  const id = setInterval(() =\u003e fetchData(), 5000);\n  return () =\u003e clearInterval(id);\n}, []);\n```\n\n### J4: addEventListener Without removeEventListener\n\n- **Severity**: IMPORTANT\n- **Detection**: `addEventListener` without cleanup\n- **Impact**: Memory\n\n```tsx\nuseEffect(() =\u003e {\n  const controller = new AbortController();\n  window.addEventListener('resize', handleResize, { signal: controller.signal });\n  return () =\u003e controller.abort();\n}, []);\n```\n\n### J5: Detached DOM Node References\n\n- **Severity**: SUGGESTION\n- **Detection**: Variables holding references to removed DOM elements\n- **Impact**: Memory\n\nSet references to `null` when elements are removed.\n\n### J6: Synchronous XHR\n\n- **Severity**: CRITICAL\n- **Detection**: `XMLHttpRequest` with synchronous flag\n- **CWV**: INP\n\nUse `fetch()` (always async).\n\n### J7: Heavy Computation on Main Thread\n\n- **Severity**: IMPORTANT\n- **Detection**: CPU-intensive operations in component code\n- **CWV**: INP\n\nMove to Web Worker or break into chunks with `scheduler.yield()`.\n\n### J8: Missing Effect Cleanup\n\n- **Severity**: IMPORTANT\n- **Detection**: `useEffect` without return cleanup; `subscribe` without unsubscribe\n- **Impact**: Memory\n\nReact: return cleanup from `useEffect`. Angular: `takeUntilDestroyed()`. Vue: `onUnmounted`.\n\n---\n\n## CSS Performance Anti-Patterns (C1-C7)\n\n### C1: Animation Using Layout-Triggering Properties\n\n- **Severity**: CRITICAL\n- **Detection**: `animation:|transition:` with `top|left|width|height|margin|padding`\n- **CWV**: INP\n\n```css\n/* BAD — main thread, \u003c60fps */\n.card { transition: width 0.3s, height 0.3s; }\n\n/* GOOD — GPU compositor, 60fps */\n.card { transition: transform 0.3s, opacity 0.3s; }\n.card:hover { transform: scale(1.05); }\n```\n\n### C2: Missing content-visibility for Off-Screen Sections\n\n- **Severity**: SUGGESTION\n- **Detection**: Long pages without `content-visibility: auto`\n- **CWV**: INP\n\n```css\n.below-fold-section {\n  content-visibility: auto;\n  contain-intrinsic-size: auto 500px;\n}\n```\n\n### C3: will-change Applied Permanently\n\n- **Severity**: SUGGESTION\n- **Detection**: `will-change:` in base CSS (not `:hover|:focus`)\n- **Impact**: Memory\n\nApply on interaction only or let browser optimize automatically.\n\n### C4: Large Unused CSS\n\n- **Severity**: IMPORTANT\n- **Detection**: CSS where \u003e50% of rules are unused\n- **CWV**: LCP\n\nUse PurgeCSS, Tailwind purge, or critters. Code-split CSS per route.\n\n### C5: Universal Selector in Hot Paths\n\n- **Severity**: SUGGESTION\n- **Detection**: `\\* \\{` in CSS\n- **CWV**: INP\n\n```css\n/* GOOD — zero-specificity reset */\n:where(*, *::before, *::after) { box-sizing: border-box; }\n```\n\n### C6: Missing CSS Containment\n\n- **Severity**: SUGGESTION\n- **Detection**: Complex components without `contain` property\n- **CWV**: INP\n\n```css\n.sidebar { contain: layout style paint; }\n```\n\n### C7: Route Transitions Without View Transitions API\n\n- **Severity**: SUGGESTION\n- **Detection**: SPA route changes without View Transitions API\n- **CWV**: CLS (perceived)\n\n```javascript\n// Use View Transitions for smooth route changes (with feature check)\nif (document.startViewTransition) {\n  document.startViewTransition(() =\u003e {\n    // update DOM / navigate\n  });\n} else {\n  // fallback: update DOM directly\n}\n```\n\nSame-document transitions supported in all major browsers. Cross-document supported in Chrome/Edge 126+, Safari 18.5+. Always feature-check before calling — unsupported browsers will throw without the guard.\n\n---\n\n## Images, Media and Fonts Anti-Patterns (I1-I8)\n\n### I1: Images Without Dimensions\n\n- **Severity**: CRITICAL\n- **Detection**: `\u003cimg` without `width=` and `height=`\n- **CWV**: CLS\n\nAlways set `width` and `height` on images, or use `aspect-ratio` in CSS.\n\n### I2: Lazy Loading Above-Fold Images\n\n- **Severity**: CRITICAL\n- **Detection**: `loading=\"lazy\"` on hero/banner images\n- **CWV**: LCP\n\n```html\n\u003c!-- GOOD — eager load with high priority --\u003e\n\u003cimg src=\"/hero.webp\" alt=\"Hero\" fetchpriority=\"high\" /\u003e\n```\n\n### I3: Legacy Format Only (JPEG/PNG)\n\n- **Severity**: IMPORTANT\n- **Detection**: Images without WebP/AVIF alternatives\n- **CWV**: LCP\n\n```html\n\u003cpicture\u003e\n  \u003csource srcset=\"/hero.avif\" type=\"image/avif\" /\u003e\n  \u003csource srcset=\"/hero.webp\" type=\"image/webp\" /\u003e\n  \u003cimg src=\"/hero.jpg\" alt=\"Hero\" width=\"1200\" height=\"600\" /\u003e\n\u003c/picture\u003e\n```\n\n### I4: Missing Responsive srcset/sizes\n\n- **Severity**: IMPORTANT\n- **Detection**: `\u003cimg` without `srcset`\n- **CWV**: LCP\n\n```html\n\u003cimg src=\"/hero-800.jpg\" alt=\"Hero\"\n     srcset=\"/hero-400.jpg 400w, /hero-800.jpg 800w, /hero-1200.jpg 1200w\"\n     sizes=\"(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px\" /\u003e\n```\n\n### I5: Font Without font-display\n\n- **Severity**: IMPORTANT\n- **Detection**: `@font-face` without `font-display`\n- **CWV**: CLS\n\n```css\n@font-face {\n  font-family: 'CustomFont';\n  src: url('/fonts/custom.woff2') format('woff2');\n  font-display: swap; /* or \"optional\" for best CLS */\n}\n```\n\n### I6: Critical Font Not Preloaded\n\n- **Severity**: IMPORTANT\n- **Detection**: Custom font without `\u003clink rel=\"preload\"\u003e`\n- **CWV**: LCP + CLS\n\n```html\n\u003clink rel=\"preload\" href=\"/fonts/main.woff2\" as=\"font\" type=\"font/woff2\" crossorigin /\u003e\n```\n\n### I7: Full Font Loaded When Subset Suffices\n\n- **Severity**: SUGGESTION\n- **Detection**: Font files \u003e 50KB WOFF2\n- **CWV**: LCP\n\nUse `unicode-range`, subset with glyphhanger, or `next/font` (auto-subsets Google Fonts).\n\n### I8: Unoptimized SVGs\n\n- **Severity**: SUGGESTION\n- **Detection**: SVGs with editor metadata\n- **CWV**: LCP (minor)\n\n```bash\nnpx svgo input.svg -o output.svg\n```\n\n---\n\n## Bundle and Tree Shaking Anti-Patterns (B1-B6)\n\n### B1: Barrel File Importing Entire Module\n\n- **Severity**: IMPORTANT\n- **Detection**: `from '\\.\\/(?:.*\\/index|components)'`\n- **CWV**: INP\n\n```typescript\n// BAD\nimport { Button } from './components';\n\n// GOOD — direct import\nimport { Button } from './components/Button';\n```\n\n### B2: CommonJS require() Preventing Tree Shaking\n\n- **Severity**: IMPORTANT\n- **Detection**: `require(` in frontend code\n- **CWV**: INP\n\nUse ESM `import/export`. Replace `require` with `import`.\n\n### B3: Large Dependency for Small Utility\n\n- **Severity**: IMPORTANT\n- **Detection**: `from \"moment\"|from \"lodash\"` (full imports)\n- **CWV**: INP\n\n```typescript\n// GOOD — tree-shakeable alternatives\nimport { format } from 'date-fns';\nimport { pick } from 'lodash-es';\n\n// BEST — native JS\nconst formatted = new Intl.DateTimeFormat('en').format(date);\n```\n\n### B4: Missing Dynamic Import for Route Splitting\n\n- **Severity**: CRITICAL\n- **Detection**: All route components imported statically\n- **CWV**: INP\n\n```tsx\n// Next.js: automatic with file-based routing\n// React:\nconst Page = React.lazy(() =\u003e import('./pages/Page'));\n// Angular:\n{ path: 'settings', loadComponent: () =\u003e import('./pages/settings.component') }\n// Vue:\nconst Page = defineAsyncComponent(() =\u003e import('./pages/Page.vue'));\n```\n\n### B5: Missing sideEffects in package.json\n\n- **Severity**: SUGGESTION\n- **Detection**: Library package.json without `\"sideEffects\"` field\n- **CWV**: INP\n\n```json\n{ \"sideEffects\": false }\n```\n\n### B6: Duplicate Dependencies\n\n- **Severity**: SUGGESTION\n- **Detection**: Same library at multiple versions\n- **CWV**: INP\n\n```bash\nnpm dedupe\n```\n\n---\n\n## Framework-Specific: Next.js (NX1-NX6)\n\n### NX1: Not Using next/image\n\n- **Severity**: IMPORTANT\n- **Detection**: `\u003cimg ` in `.tsx` instead of `\u003cImage\u003e`\n- **CWV**: LCP + CLS\n\n```tsx\nimport Image from 'next/image';\n\u003cImage src=\"/hero.jpg\" alt=\"Hero\" width={1200} height={600} priority /\u003e\n```\n\n### NX2: Not Using Cache Components for Partial Prerendering\n\n- **Severity**: IMPORTANT\n- **Detection**: Pages without `\"use cache\"` directive in Next.js 16+ projects\n- **CWV**: LCP\n\n```typescript\n// BAD — entire page is dynamic\nexport default async function Page() {\n  const data = await fetchData(); // blocks full page render\n  return \u003cdiv\u003e{data.title}\u003c/div\u003e;\n}\n\n// GOOD — enable Partial Prerendering with \"use cache\"\n// next.config.ts: { cacheComponents: true }\n\"use cache\";\nexport default async function Page() {\n  const data = await fetchData(); // static shell renders instantly, dynamic holes stream\n  return \u003cdiv\u003e{data.title}\u003c/div\u003e;\n}\n```\n\nEnable in `next.config.ts` with `cacheComponents: true`. Use `\"use cache\"` at file, component, or function level. Static shell loads instantly; dynamic content streams via Suspense boundaries.\n\n### NX3: Unnecessary \"use client\" on Server-Renderable Component\n\n- **Severity**: IMPORTANT\n- **Detection**: `\"use client\"` on components without hooks or browser APIs\n- **CWV**: INP\n\nRemove `\"use client\"` from components that only render static content.\n\n### NX4: Data Fetching in useEffect Instead of Server-Side\n\n- **Severity**: CRITICAL\n- **Detection**: `useEffect` + `fetch` in Next.js App Router pages\n- **CWV**: LCP\n\nFetch data in Server Components directly (async function body).\n\n### NX5: Missing next/font\n\n- **Severity**: IMPORTANT\n- **Detection**: `fonts.googleapis|fonts.gstatic` in CSS/HTML\n- **CWV**: CLS + LCP\n\n```tsx\nimport { Inter } from 'next/font/google';\nconst inter = Inter({ subsets: ['latin'] });\n```\n\n### NX6: Missing \"use cache\" for Cacheable Server Functions\n\n- **Severity**: IMPORTANT\n- **Detection**: Async server functions without `\"use cache\"` in Next.js 16+ with `cacheComponents: true`\n- **CWV**: LCP\n\n```typescript\n// BAD — data fetched on every request\nasync function getProducts() {\n  return await db.products.findMany();\n}\n\n// GOOD — cached with revalidation\n\"use cache\";\nimport { cacheLife } from 'next/cache';\nasync function getProducts() {\n  cacheLife('hours');\n  return await db.products.findMany();\n}\n```\n\n`\"use cache\"` replaces the old `unstable_cache` and `fetch` cache options. Use `cacheLife()` and `cacheTag()` for fine-grained control.\n\n---\n\n## Framework-Specific: Angular (NG1-NG6)\n\n### NG1: Default Change Detection on Presentational Components\n\n- **Severity**: IMPORTANT\n- **Detection**: Components without `ChangeDetectionStrategy.OnPush` (Angular \u003c19) or without signals (Angular 19+)\n- **CWV**: INP\n\n```typescript\n// Angular \u003c19: Use OnPush\n@Component({\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  ...\n})\n\n// Angular 19+: Prefer zoneless with signals\n// app.config.ts: provideZonelessChangeDetection()\n@Component({ ... })\nexport class ProductCard {\n  product = input.required\u003cProduct\u003e(); // signal input\n  price = computed(() =\u003e this.product().price * 1.19); // derived signal\n}\n```\n\nAngular 19+: prefer zoneless change detection with signals. OnPush is unnecessary when using signal-based reactivity. Angular 20+ has stable zoneless support.\n\n### NG2: Not Using NgOptimizedImage\n\n- **Severity**: IMPORTANT\n- **Detection**: `\u003cimg` without `ngSrc` in `.component.html`\n- **CWV**: LCP + CLS\n\n```html\n\u003cimg ngSrc=\"/hero.jpg\" alt=\"Hero\" width=\"1200\" height=\"600\" priority /\u003e\n```\n\n### NG3: Missing @defer for Below-Fold Content\n\n- **Severity**: SUGGESTION\n- **Detection**: Heavy below-fold components loaded eagerly (Angular 17+)\n- **CWV**: INP\n\n```html\n@defer (on viewport) {\n  \u003capp-heavy-chart [data]=\"chartData\" /\u003e\n} @placeholder {\n  \u003cdiv class=\"chart-skeleton\"\u003e\u003c/div\u003e\n}\n```\n\n### NG4: Not Using Signals for Reactive State\n\n- **Severity**: SUGGESTION\n- **Detection**: Class properties without signals in Angular 19+\n- **CWV**: INP\n\nUse `signal()` for reactive state, `computed()` for derived values. Signal APIs (`signal()`, `computed()`, `effect()`) are stable since Angular 20.\n\n### NG5: Full Hydration Without Incremental Hydration\n\n- **Severity**: IMPORTANT\n- **Detection**: SSR app without `withIncrementalHydration()` in Angular 19+\n- **CWV**: LCP, INP\n\n```typescript\n// BAD — full hydration blocks interactivity\nprovideClientHydration()\n\n// GOOD — incremental hydration with triggers\nprovideClientHydration(withIncrementalHydration())\n```\n\nUse `@defer` triggers (`on viewport`, `on interaction`) to hydrate components on demand. Reduces TTI by deferring non-critical component hydration.\n\n### NG6: Still Using zone.js in Angular 20+ Projects\n\n- **Severity**: SUGGESTION\n- **Detection**: `zone.js` in polyfills array, no `provideZonelessChangeDetection()` in Angular 20+\n- **CWV**: INP\n\n```typescript\n// app.config.ts\nexport const appConfig = {\n  providers: [\n    provideZonelessChangeDetection(), // removes ~15-30KB from bundle\n    // ...\n  ]\n};\n```\n\nZoneless change detection with signals reduces bundle size and improves runtime performance. Stable since Angular 20.\n\n---\n\n## Framework-Specific: React (RX1-RX4)\n\n### RX1: Missing React Compiler Adoption\n\n- **Severity**: SUGGESTION\n- **Detection**: Manual `useMemo|useCallback` in React 19+ project\n- **CWV**: INP\n\nEnable React Compiler (v19+) for auto-memoization. Remove manual wrappers.\n\n### RX2: Missing useTransition for Expensive Updates\n\n- **Severity**: IMPORTANT\n- **Detection**: State updates causing expensive re-renders without `useTransition`\n- **CWV**: INP\n\n```tsx\nconst [isPending, startTransition] = useTransition();\nfunction handleFilter(value) {\n  startTransition(() =\u003e setFilter(value));\n}\n```\n\n### RX3: Missing useDeferredValue for Expensive Rendering\n\n- **Severity**: IMPORTANT\n- **Detection**: Expensive rendering from rapidly-changing input\n- **CWV**: INP\n\n```tsx\nconst deferredQuery = useDeferredValue(query);\nconst results = expensiveFilter(items, deferredQuery);\n```\n\n### RX4: Missing React.lazy for Route Splitting\n\n- **Severity**: IMPORTANT\n- **Detection**: Route components imported statically\n- **CWV**: INP\n\n```tsx\nconst Settings = React.lazy(() =\u003e import('./pages/Settings'));\n```\n\n---\n\n## Framework-Specific: Vue (VU1-VU4)\n\n### VU1: reactive() on Large Data Structures\n\n- **Severity**: IMPORTANT\n- **Detection**: `reactive(` on large arrays or deep objects\n- **CWV**: INP\n\nUse `shallowRef()` or `shallowReactive()` for large data.\n\n### VU2: Missing v-memo on Expensive List Renders\n\n- **Severity**: SUGGESTION\n- **Detection**: Large lists without `v-memo`\n- **CWV**: INP\n\n```vue\n\u003cdiv v-for=\"item in items\" :key=\"item.id\" v-memo=\"[item.id, item.updatedAt]\"\u003e\n  \u003cExpensiveItem :data=\"item\" /\u003e\n\u003c/div\u003e\n```\n\n### VU3: Missing defineAsyncComponent\n\n- **Severity**: IMPORTANT\n- **Detection**: Heavy components imported statically\n- **CWV**: INP\n\n```typescript\nconst HeavyChart = defineAsyncComponent(() =\u003e import('./HeavyChart.vue'));\n```\n\n### VU4: Not Using Vapor Mode for Performance-Critical Components\n\n- **Severity**: SUGGESTION\n- **Detection**: Performance-critical components using virtual DOM in Vue 3.6+\n- **CWV**: INP\n\nVue 3.6+ Vapor Mode compiles templates to direct DOM operations, bypassing the virtual DOM. Use for performance-critical subtrees. Can be mixed with standard components.\n\n---\n\n## Resource Hints Quick Reference\n\n| Hint | Purpose | When to Use |\n|------|---------|-------------|\n| `preconnect` | DNS + TCP + TLS early | Critical third-party origins (API, CDN, fonts) |\n| `preload` | Fetch immediately, high priority | LCP image, critical font |\n| `prefetch` | Low priority for future navigation | Next-page assets |\n| `dns-prefetch` | DNS resolution only | Non-critical third-party origins |\n| `modulepreload` | Preload + parse ES module | Critical JS modules |\n| `\u003cscript type=\"speculationrules\"\u003e` | Prefetch/prerender next navigation | Likely next pages (Chrome 121+, progressive enhancement) |\n\n---\n\n## Image Optimization Quick Reference\n\n| Aspect | Recommendation |\n|--------|---------------|\n| Format | WebP (25-34% smaller), AVIF (50% smaller) |\n| LCP image | `fetchpriority=\"high\"` or framework `priority` prop |\n| Below-fold | `loading=\"lazy\"` |\n| Dimensions | Always set `width` + `height` |\n| Responsive | `srcset` + `sizes` or framework Image component |\n| Compression | Quality 75-85 for photos |\n\n---\n\n## Font Loading Quick Reference\n\n| Strategy | Best For | CLS Impact |\n|----------|---------|-----------|\n| `font-display: swap` | Body text | Slight FOUT, minimal CLS |\n| `font-display: optional` | All fonts (best CLS) | No FOUT, no CLS |\n| `next/font` | Next.js projects | Zero CLS |\n| Variable fonts | Multiple weights | Single file for all weights |\n\nRules: preload 1-2 critical fonts only, use WOFF2, subset to needed characters, self-host when possible.\n\n---\n\n## Performance Checklist (CWV)\n\n### LCP (\u003c 2.5s)\n- [ ] LCP image has `fetchpriority=\"high\"` or `priority` prop\n- [ ] LCP image preloaded if not in HTML source\n- [ ] No `loading=\"lazy\"` on above-fold images\n- [ ] Critical CSS inlined or extracted\n- [ ] No render-blocking scripts (use `defer` or `async`)\n- [ ] Preconnect to critical third-party origins\n- [ ] Main content server-rendered (not client-side fetched)\n- [ ] Images in modern format (WebP/AVIF) with responsive `srcset`\n- [ ] Compression enabled (Brotli preferred)\n- [ ] Fonts preloaded with `font-display: swap` or `optional`\n\n### INP (\u003c 200ms)\n- [ ] Event handlers complete in \u003c 50ms\n- [ ] Long tasks broken into smaller chunks\n- [ ] Route-based code splitting implemented\n- [ ] Heavy computation moved to Web Workers\n- [ ] Lists with \u003e 100 items virtualized\n- [ ] No barrel file imports (direct component imports)\n- [ ] ESM imports used (not CommonJS `require`)\n- [ ] `\"use client\"` only on components that need interactivity\n- [ ] Layout-triggering CSS properties not animated\n- [ ] Effect cleanup implemented (no leaking listeners/timers)\n\n### CLS (\u003c 0.1)\n- [ ] All images have `width` and `height` attributes\n- [ ] Fonts use `font-display: swap` or `optional`\n- [ ] No content injected above existing content dynamically\n- [ ] Ads/embeds have reserved space\n- [ ] No hydration mismatches\n- [ ] `content-visibility: auto` has `contain-intrinsic-size`\n","description":"Comprehensive web performance standards based on Core Web Vitals (LCP, INP, CLS), with 50+ anti-patterns, detection regex, framework-specific fixes for modern web frameworks, and modern API guidance.","import":{"commit_sha":"541b7819d8c3545c6df122491af4fa1eae415779","imported_at":"2026-05-18T20:05:35Z","license_text":"MIT License\n\nCopyright GitHub, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","owner":"github","repo":"github/awesome-copilot","source_url":"https://github.com/github/awesome-copilot/blob/541b7819d8c3545c6df122491af4fa1eae415779/instructions/performance-optimization.instructions.md"},"manifest":{}},"content_hash":[52,33,154,109,207,102,30,241,17,89,119,37,144,170,195,226,159,138,132,65,166,98,198,8,104,105,132,236,188,206,63,82],"trust_level":"unsigned","yanked":false}
