[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DYWE9DW93PPJB68H85Y9P4":3,"skill-same:01M6DYWE9DW93PPJB68H85Y9P4":66},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":39,"commitSha":40,"skillDirectory":41,"forksCount":42,"repositoryHtmlUrl":43,"repositoryHomepageUrl":6,"ownerHtmlUrl":44,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":45,"install":46,"resources":59},"01M6DYWE9DW93PPJB68H85Y9P4","core-web-vitals",null,"Optimize Core Web Vitals (LCP, INP, CLS) for better page experience and search ranking. Use when asked to \"improve Core Web Vitals\", \"fix LCP\", \"reduce CLS\", \"optimize INP\", \"page experience optimization\", or \"fix layout shifts\".","kennyolofsson23-netizen\u002Fclaude-code-config","kennyolofsson23-netizen","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F262391667?v=4","01M6DYPCT5DBDV0C11CXD8G7RX","01M6DYPD3KYJ7P6A8H7RKJ6KGG",0,"MIT","https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Fcore-web-vitals\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30],"web","core","core web vitals","search","Optimization","improve","page","quality","fix","use","optimize","layout","ranking","2026-11-10 10:12:30",false,"---\nname: core-web-vitals\ndescription: Optimize Core Web Vitals (LCP, INP, CLS) for better page experience and search ranking. Use when asked to \"improve Core Web Vitals\", \"fix LCP\", \"reduce CLS\", \"optimize INP\", \"page experience optimization\", or \"fix layout shifts\".\nlicense: MIT\nmetadata:\n  author: web-quality-skills\n  version: \"1.0\"\n---\n\n# Core Web Vitals optimization\n\nTargeted optimization for the three Core Web Vitals metrics that affect Google Search ranking and user experience.\n\n## The three metrics\n\n| Metric | Measures | Good | Needs work | Poor |\n|--------|----------|------|------------|------|\n| **LCP** | Loading | ≤ 2.5s | 2.5s – 4s | > 4s |\n| **INP** | Interactivity | ≤ 200ms | 200ms – 500ms | > 500ms |\n| **CLS** | Visual Stability | ≤ 0.1 | 0.1 – 0.25 | > 0.25 |\n\nGoogle measures at the **75th percentile** — 75% of page visits must meet \"Good\" thresholds.\n\n---\n\n## LCP: Largest Contentful Paint\n\nLCP measures when the largest visible content element renders. Usually this is:\n- Hero image or video\n- Large text block\n- Background image\n- `\u003Csvg>` element\n\n### Common LCP issues\n\n**1. Slow server response (TTFB > 800ms)**\n```\nFix: CDN, caching, optimized backend, edge rendering\n```\n\n**2. Render-blocking resources**\n```html\n\u003C!-- ❌ Blocks rendering -->\n\u003Clink rel=\"stylesheet\" href=\"\u002Fall-styles.css\">\n\n\u003C!-- ✅ Critical CSS inlined, rest deferred -->\n\u003Cstyle>\u002F* Critical above-fold CSS *\u002F\u003C\u002Fstyle>\n\u003Clink rel=\"preload\" href=\"\u002Fstyles.css\" as=\"style\" \n      onload=\"this.onload=null;this.rel='stylesheet'\">\n```\n\n**3. Slow resource load times**\n```html\n\u003C!-- ❌ No hints, discovered late -->\n\u003Cimg src=\"\u002Fhero.jpg\" alt=\"Hero\">\n\n\u003C!-- ✅ Preloaded with high priority -->\n\u003Clink rel=\"preload\" href=\"\u002Fhero.webp\" as=\"image\" fetchpriority=\"high\">\n\u003Cimg src=\"\u002Fhero.webp\" alt=\"Hero\" fetchpriority=\"high\">\n```\n\n**4. Client-side rendering delays**\n```javascript\n\u002F\u002F ❌ Content loads after JavaScript\nuseEffect(() => {\n  fetch('\u002Fapi\u002Fhero-text').then(r => r.json()).then(setHeroText);\n}, []);\n\n\u002F\u002F ✅ Server-side or static rendering\n\u002F\u002F Use SSR, SSG, or streaming to send HTML with content\nexport async function getServerSideProps() {\n  const heroText = await fetchHeroText();\n  return { props: { heroText } };\n}\n```\n\n### LCP optimization checklist\n\n```markdown\n- [ ] TTFB \u003C 800ms (use CDN, edge caching)\n- [ ] LCP image preloaded with fetchpriority=\"high\"\n- [ ] LCP image optimized (WebP\u002FAVIF, correct size)\n- [ ] Critical CSS inlined (\u003C 14KB)\n- [ ] No render-blocking JavaScript in \u003Chead>\n- [ ] Fonts don't block text rendering (font-display: swap)\n- [ ] LCP element in initial HTML (not JS-rendered)\n```\n\n### LCP element identification\n```javascript\n\u002F\u002F Find your LCP element\nnew PerformanceObserver((list) => {\n  const entries = list.getEntries();\n  const lastEntry = entries[entries.length - 1];\n  console.log('LCP element:', lastEntry.element);\n  console.log('LCP time:', lastEntry.startTime);\n}).observe({ type: 'largest-contentful-paint', buffered: true });\n```\n\n---\n\n## INP: Interaction to Next Paint\n\nINP measures responsiveness across ALL interactions (clicks, taps, key presses) during a page visit. It reports the worst interaction (at 98th percentile for high-traffic pages).\n\n### INP breakdown\n\nTotal INP = **Input Delay** + **Processing Time** + **Presentation Delay**\n\n| Phase | Target | Optimization |\n|-------|--------|--------------|\n| Input Delay | \u003C 50ms | Reduce main thread blocking |\n| Processing | \u003C 100ms | Optimize event handlers |\n| Presentation | \u003C 50ms | Minimize rendering work |\n\n### Common INP issues\n\n**1. Long tasks blocking main thread**\n```javascript\n\u002F\u002F ❌ Long synchronous task\nfunction processLargeArray(items) {\n  items.forEach(item => expensiveOperation(item));\n}\n\n\u002F\u002F ✅ Break into chunks with yielding\nasync function processLargeArray(items) {\n  const CHUNK_SIZE = 100;\n  for (let i = 0; i \u003C items.length; i += CHUNK_SIZE) {\n    const chunk = items.slice(i, i + CHUNK_SIZE);\n    chunk.forEach(item => expensiveOperation(item));\n    \n    \u002F\u002F Yield to main thread\n    await new Promise(r => setTimeout(r, 0));\n    \u002F\u002F Or use scheduler.yield() when available\n  }\n}\n```\n\n**2. Heavy event handlers**\n```javascript\n\u002F\u002F ❌ All work in handler\nbutton.addEventListener('click', () => {\n  \u002F\u002F Heavy computation\n  const result = calculateComplexThing();\n  \u002F\u002F DOM updates\n  updateUI(result);\n  \u002F\u002F Analytics\n  trackEvent('click');\n});\n\n\u002F\u002F ✅ Prioritize visual feedback\nbutton.addEventListener('click', () => {\n  \u002F\u002F Immediate visual feedback\n  button.classList.add('loading');\n  \n  \u002F\u002F Defer non-critical work\n  requestAnimationFrame(() => {\n    const result = calculateComplexThing();\n    updateUI(result);\n  });\n  \n  \u002F\u002F Use requestIdleCallback for analytics\n  requestIdleCallback(() => trackEvent('click'));\n});\n```\n\n**3. Third-party scripts**\n```javascript\n\u002F\u002F ❌ Eagerly loaded, blocks interactions\n\u003Cscript src=\"https:\u002F\u002Fheavy-widget.com\u002Fwidget.js\">\u003C\u002Fscript>\n\n\u002F\u002F ✅ Lazy loaded on interaction or visibility\nconst loadWidget = () => {\n  import('https:\u002F\u002Fheavy-widget.com\u002Fwidget.js')\n    .then(widget => widget.init());\n};\nbutton.addEventListener('click', loadWidget, { once: true });\n```\n\n**4. Excessive re-renders (React\u002FVue)**\n```javascript\n\u002F\u002F ❌ Re-renders entire tree\nfunction App() {\n  const [count, setCount] = useState(0);\n  return (\n    \u003Cdiv>\n      \u003CCounter count={count} \u002F>\n      \u003CExpensiveComponent \u002F> {\u002F* Re-renders on every count change *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F ✅ Memoized expensive components\nconst MemoizedExpensive = React.memo(ExpensiveComponent);\n\nfunction App() {\n  const [count, setCount] = useState(0);\n  return (\n    \u003Cdiv>\n      \u003CCounter count={count} \u002F>\n      \u003CMemoizedExpensive \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### INP optimization checklist\n\n```markdown\n- [ ] No tasks > 50ms on main thread\n- [ ] Event handlers complete quickly (\u003C 100ms)\n- [ ] Visual feedback provided immediately\n- [ ] Heavy work deferred with requestIdleCallback\n- [ ] Third-party scripts don't block interactions\n- [ ] Debounced input handlers where appropriate\n- [ ] Web Workers for CPU-intensive operations\n```\n\n### INP debugging\n```javascript\n\u002F\u002F Identify slow interactions\nnew PerformanceObserver((list) => {\n  for (const entry of list.getEntries()) {\n    if (entry.duration > 200) {\n      console.warn('Slow interaction:', {\n        type: entry.name,\n        duration: entry.duration,\n        processingStart: entry.processingStart,\n        processingEnd: entry.processingEnd,\n        target: entry.target\n      });\n    }\n  }\n}).observe({ type: 'event', buffered: true, durationThreshold: 16 });\n```\n\n---\n\n## CLS: Cumulative Layout Shift\n\nCLS measures unexpected layout shifts. A shift occurs when a visible element changes position between frames without user interaction.\n\n**CLS Formula:** `impact fraction × distance fraction`\n\n### Common CLS causes\n\n**1. Images without dimensions**\n```html\n\u003C!-- ❌ Causes layout shift when loaded -->\n\u003Cimg src=\"photo.jpg\" alt=\"Photo\">\n\n\u003C!-- ✅ Space reserved -->\n\u003Cimg src=\"photo.jpg\" alt=\"Photo\" width=\"800\" height=\"600\">\n\n\u003C!-- ✅ Or use aspect-ratio -->\n\u003Cimg src=\"photo.jpg\" alt=\"Photo\" style=\"aspect-ratio: 4\u002F3; width: 100%;\">\n```\n\n**2. Ads, embeds, and iframes**\n```html\n\u003C!-- ❌ Unknown size until loaded -->\n\u003Ciframe src=\"https:\u002F\u002Fad-network.com\u002Fad\">\u003C\u002Fiframe>\n\n\u003C!-- ✅ Reserve space with min-height -->\n\u003Cdiv style=\"min-height: 250px;\">\n  \u003Ciframe src=\"https:\u002F\u002Fad-network.com\u002Fad\" height=\"250\">\u003C\u002Fiframe>\n\u003C\u002Fdiv>\n\n\u003C!-- ✅ Or use aspect-ratio container -->\n\u003Cdiv style=\"aspect-ratio: 16\u002F9;\">\n  \u003Ciframe src=\"https:\u002F\u002Fyoutube.com\u002Fembed\u002F...\" \n          style=\"width: 100%; height: 100%;\">\u003C\u002Fiframe>\n\u003C\u002Fdiv>\n```\n\n**3. Dynamically injected content**\n```javascript\n\u002F\u002F ❌ Inserts content above viewport\nnotifications.prepend(newNotification);\n\n\u002F\u002F ✅ Insert below viewport or use transform\nconst insertBelow = viewport.bottom \u003C newNotification.top;\nif (insertBelow) {\n  notifications.prepend(newNotification);\n} else {\n  \u002F\u002F Animate in without shifting\n  newNotification.style.transform = 'translateY(-100%)';\n  notifications.prepend(newNotification);\n  requestAnimationFrame(() => {\n    newNotification.style.transform = '';\n  });\n}\n```\n\n**4. Web fonts causing FOUT**\n```css\n\u002F* ❌ Font swap shifts text *\u002F\n@font-face {\n  font-family: 'Custom';\n  src: url('custom.woff2') format('woff2');\n}\n\n\u002F* ✅ Optional font (no shift if slow) *\u002F\n@font-face {\n  font-family: 'Custom';\n  src: url('custom.woff2') format('woff2');\n  font-display: optional;\n}\n\n\u002F* ✅ Or match fallback metrics *\u002F\n@font-face {\n  font-family: 'Custom';\n  src: url('custom.woff2') format('woff2');\n  font-display: swap;\n  size-adjust: 105%; \u002F* Match fallback size *\u002F\n  ascent-override: 95%;\n  descent-override: 20%;\n}\n```\n\n**5. Animations triggering layout**\n```css\n\u002F* ❌ Animates layout properties *\u002F\n.animate {\n  transition: height 0.3s, width 0.3s;\n}\n\n\u002F* ✅ Use transform instead *\u002F\n.animate {\n  transition: transform 0.3s;\n}\n.animate.expanded {\n  transform: scale(1.2);\n}\n```\n\n### CLS optimization checklist\n\n```markdown\n- [ ] All images have width\u002Fheight or aspect-ratio\n- [ ] All videos\u002Fembeds have reserved space\n- [ ] Ads have min-height containers\n- [ ] Fonts use font-display: optional or matched metrics\n- [ ] Dynamic content inserted below viewport\n- [ ] Animations use transform\u002Fopacity only\n- [ ] No content injected above existing content\n```\n\n### CLS debugging\n```javascript\n\u002F\u002F Track layout shifts\nnew PerformanceObserver((list) => {\n  for (const entry of list.getEntries()) {\n    if (!entry.hadRecentInput) {\n      console.log('Layout shift:', entry.value);\n      entry.sources?.forEach(source => {\n        console.log('  Shifted element:', source.node);\n        console.log('  Previous rect:', source.previousRect);\n        console.log('  Current rect:', source.currentRect);\n      });\n    }\n  }\n}).observe({ type: 'layout-shift', buffered: true });\n```\n\n---\n\n## Measurement tools\n\n### Lab testing\n- **Chrome DevTools** → Performance panel, Lighthouse\n- **WebPageTest** → Detailed waterfall, filmstrip\n- **Lighthouse CLI** → `npx lighthouse \u003Curl>`\n\n### Field data (real users)\n- **Chrome User Experience Report (CrUX)** → BigQuery or API\n- **Search Console** → Core Web Vitals report\n- **web-vitals library** → Send to your analytics\n\n```javascript\nimport {onLCP, onINP, onCLS} from 'web-vitals';\n\nfunction sendToAnalytics({name, value, rating}) {\n  gtag('event', name, {\n    event_category: 'Web Vitals',\n    value: Math.round(name === 'CLS' ? value * 1000 : value),\n    event_label: rating\n  });\n}\n\nonLCP(sendToAnalytics);\nonINP(sendToAnalytics);\nonCLS(sendToAnalytics);\n```\n\n---\n\n## Framework quick fixes\n\n### Next.js\n```jsx\n\u002F\u002F LCP: Use next\u002Fimage with priority\nimport Image from 'next\u002Fimage';\n\u003CImage src=\"\u002Fhero.jpg\" priority fill alt=\"Hero\" \u002F>\n\n\u002F\u002F INP: Use dynamic imports\nconst HeavyComponent = dynamic(() => import('.\u002FHeavy'), { ssr: false });\n\n\u002F\u002F CLS: Image component handles dimensions automatically\n```\n\n### React\n```jsx\n\u002F\u002F LCP: Preload in head\n\u003Clink rel=\"preload\" href=\"\u002Fhero.jpg\" as=\"image\" fetchpriority=\"high\" \u002F>\n\n\u002F\u002F INP: Memoize and useTransition\nconst [isPending, startTransition] = useTransition();\nstartTransition(() => setExpensiveState(newValue));\n\n\u002F\u002F CLS: Always specify dimensions in img tags\n```\n\n### Vue\u002FNuxt\n```vue\n\u003C!-- LCP: Use nuxt\u002Fimage with preload -->\n\u003CNuxtImg src=\"\u002Fhero.jpg\" preload loading=\"eager\" \u002F>\n\n\u003C!-- INP: Use async components -->\n\u003Ccomponent :is=\"() => import('.\u002FHeavy.vue')\" \u002F>\n\n\u003C!-- CLS: Use aspect-ratio CSS -->\n\u003Cimg :style=\"{ aspectRatio: '16\u002F9' }\" \u002F>\n```\n\n## References\n\n- [web.dev LCP](https:\u002F\u002Fweb.dev\u002Farticles\u002Flcp)\n- [web.dev INP](https:\u002F\u002Fweb.dev\u002Farticles\u002Finp)\n- [web.dev CLS](https:\u002F\u002Fweb.dev\u002Farticles\u002Fcls)\n- [Performance skill](..\u002Fperformance\u002FSKILL.md)\n","# Core Web Vitals optimization\n\nTargeted optimization for the three Core Web Vitals metrics that affect Google Search ranking and user experience.\n\n## The three metrics\n\n| Metric | Measures | Good | Needs work | Poor |\n|--------|----------|------|------------|------|\n| **LCP** | Loading | ≤ 2.5s | 2.5s – 4s | > 4s |\n| **INP** | Interactivity | ≤ 200ms | 200ms – 500ms | > 500ms |\n| **CLS** | Visual Stability | ≤ 0.1 | 0.1 – 0.25 | > 0.25 |\n\nGoogle measures at the **75th percentile** — 75% of page visits must meet \"Good\" thresholds.\n\n---\n\n## LCP: Largest Contentful Paint\n\nLCP measures when the largest visible content element renders. Usually this is:\n- Hero image or video\n- Large text block\n- Background image\n- `\u003Csvg>` element\n\n### Common LCP issues\n\n**1. Slow server response (TTFB > 800ms)**\n```\nFix: CDN, caching, optimized backend, edge rendering\n```\n\n**2. Render-blocking resources**\n```html\n\u003C!-- ❌ Blocks rendering -->\n\u003Clink rel=\"stylesheet\" href=\"\u002Fall-styles.css\">\n\n\u003C!-- ✅ Critical CSS inlined, rest deferred -->\n\u003Cstyle>\u002F* Critical above-fold CSS *\u002F\u003C\u002Fstyle>\n\u003Clink rel=\"preload\" href=\"\u002Fstyles.css\" as=\"style\" \n      onload=\"this.onload=null;this.rel='stylesheet'\">\n```\n\n**3. Slow resource load times**\n```html\n\u003C!-- ❌ No hints, discovered late -->\n\u003Cimg src=\"\u002Fhero.jpg\" alt=\"Hero\">\n\n\u003C!-- ✅ Preloaded with high priority -->\n\u003Clink rel=\"preload\" href=\"\u002Fhero.webp\" as=\"image\" fetchpriority=\"high\">\n\u003Cimg src=\"\u002Fhero.webp\" alt=\"Hero\" fetchpriority=\"high\">\n```\n\n**4. Client-side rendering delays**\n```javascript\n\u002F\u002F ❌ Content loads after JavaScript\nuseEffect(() => {\n  fetch('\u002Fapi\u002Fhero-text').then(r => r.json()).then(setHeroText);\n}, []);\n\n\u002F\u002F ✅ Server-side or static rendering\n\u002F\u002F Use SSR, SSG, or streaming to send HTML with content\nexport async function getServerSideProps() {\n  const heroText = await fetchHeroText();\n  return { props: { heroText } };\n}\n```\n\n### LCP optimization checklist\n\n```markdown\n- [ ] TTFB \u003C 800ms (use CDN, edge caching)\n- [ ] LCP image preloaded with fetchpriority=\"high\"\n- [ ] LCP image optimized (WebP\u002FAVIF, correct size)\n- [ ] Critical CSS inlined (\u003C 14KB)\n- [ ] No render-blocking JavaScript in \u003Chead>\n- [ ] Fonts don't block text rendering (font-display: swap)\n- [ ] LCP element in initial HTML (not JS-rendered)\n```\n\n### LCP element identification\n```javascript\n\u002F\u002F Find your LCP element\nnew PerformanceObserver((list) => {\n  const entries = list.getEntries();\n  const lastEntry = entries[entries.length - 1];\n  console.log('LCP element:', lastEntry.element);\n  console.log('LCP time:', lastEntry.startTime);\n}).observe({ type: 'largest-contentful-paint', buffered: true });\n```\n\n---\n\n## INP: Interaction to Next Paint\n\nINP measures responsiveness across ALL interactions (clicks, taps, key presses) during a page visit. It reports the worst interaction (at 98th percentile for high-traffic pages).\n\n### INP breakdown\n\nTotal INP = **Input Delay** + **Processing Time** + **Presentation Delay**\n\n| Phase | Target | Optimization |\n|-------|--------|--------------|\n| Input Delay | \u003C 50ms | Reduce main thread blocking |\n| Processing | \u003C 100ms | Optimize event handlers |\n| Presentation | \u003C 50ms | Minimize rendering work |\n\n### Common INP issues\n\n**1. Long tasks blocking main thread**\n```javascript\n\u002F\u002F ❌ Long synchronous task\nfunction processLargeArray(items) {\n  items.forEach(item => expensiveOperation(item));\n}\n\n\u002F\u002F ✅ Break into chunks with yielding\nasync function processLargeArray(items) {\n  const CHUNK_SIZE = 100;\n  for (let i = 0; i \u003C items.length; i += CHUNK_SIZE) {\n    const chunk = items.slice(i, i + CHUNK_SIZE);\n    chunk.forEach(item => expensiveOperation(item));\n    \n    \u002F\u002F Yield to main thread\n    await new Promise(r => setTimeout(r, 0));\n    \u002F\u002F Or use scheduler.yield() when available\n  }\n}\n```\n\n**2. Heavy event handlers**\n```javascript\n\u002F\u002F ❌ All work in handler\nbutton.addEventListener('click', () => {\n  \u002F\u002F Heavy computation\n  const result = calculateComplexThing();\n  \u002F\u002F DOM updates\n  updateUI(result);\n  \u002F\u002F Analytics\n  trackEvent('click');\n});\n\n\u002F\u002F ✅ Prioritize visual feedback\nbutton.addEventListener('click', () => {\n  \u002F\u002F Immediate visual feedback\n  button.classList.add('loading');\n  \n  \u002F\u002F Defer non-critical work\n  requestAnimationFrame(() => {\n    const result = calculateComplexThing();\n    updateUI(result);\n  });\n  \n  \u002F\u002F Use requestIdleCallback for analytics\n  requestIdleCallback(() => trackEvent('click'));\n});\n```\n\n**3. Third-party scripts**\n```javascript\n\u002F\u002F ❌ Eagerly loaded, blocks interactions\n\u003Cscript src=\"https:\u002F\u002Fheavy-widget.com\u002Fwidget.js\">\u003C\u002Fscript>\n\n\u002F\u002F ✅ Lazy loaded on interaction or visibility\nconst loadWidget = () => {\n  import('https:\u002F\u002Fheavy-widget.com\u002Fwidget.js')\n    .then(widget => widget.init());\n};\nbutton.addEventListener('click', loadWidget, { once: true });\n```\n\n**4. Excessive re-renders (React\u002FVue)**\n```javascript\n\u002F\u002F ❌ Re-renders entire tree\nfunction App() {\n  const [count, setCount] = useState(0);\n  return (\n    \u003Cdiv>\n      \u003CCounter count={count} \u002F>\n      \u003CExpensiveComponent \u002F> {\u002F* Re-renders on every count change *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F ✅ Memoized expensive components\nconst MemoizedExpensive = React.memo(ExpensiveComponent);\n\nfunction App() {\n  const [count, setCount] = useState(0);\n  return (\n    \u003Cdiv>\n      \u003CCounter count={count} \u002F>\n      \u003CMemoizedExpensive \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### INP optimization checklist\n\n```markdown\n- [ ] No tasks > 50ms on main thread\n- [ ] Event handlers complete quickly (\u003C 100ms)\n- [ ] Visual feedback provided immediately\n- [ ] Heavy work deferred with requestIdleCallback\n- [ ] Third-party scripts don't block interactions\n- [ ] Debounced input handlers where appropriate\n- [ ] Web Workers for CPU-intensive operations\n```\n\n### INP debugging\n```javascript\n\u002F\u002F Identify slow interactions\nnew PerformanceObserver((list) => {\n  for (const entry of list.getEntries()) {\n    if (entry.duration > 200) {\n      console.warn('Slow interaction:', {\n        type: entry.name,\n        duration: entry.duration,\n        processingStart: entry.processingStart,\n        processingEnd: entry.processingEnd,\n        target: entry.target\n      });\n    }\n  }\n}).observe({ type: 'event', buffered: true, durationThreshold: 16 });\n```\n\n---\n\n## CLS: Cumulative Layout Shift\n\nCLS measures unexpected layout shifts. A shift occurs when a visible element changes position between frames without user interaction.\n\n**CLS Formula:** `impact fraction × distance fraction`\n\n### Common CLS causes\n\n**1. Images without dimensions**\n```html\n\u003C!-- ❌ Causes layout shift when loaded -->\n\u003Cimg src=\"photo.jpg\" alt=\"Photo\">\n\n\u003C!-- ✅ Space reserved -->\n\u003Cimg src=\"photo.jpg\" alt=\"Photo\" width=\"800\" height=\"600\">\n\n\u003C!-- ✅ Or use aspect-ratio -->\n\u003Cimg src=\"photo.jpg\" alt=\"Photo\" style=\"aspect-ratio: 4\u002F3; width: 100%;\">\n```\n\n**2. Ads, embeds, and iframes**\n```html\n\u003C!-- ❌ Unknown size until loaded -->\n\u003Ciframe src=\"https:\u002F\u002Fad-network.com\u002Fad\">\u003C\u002Fiframe>\n\n\u003C!-- ✅ Reserve space with min-height -->\n\u003Cdiv style=\"min-height: 250px;\">\n  \u003Ciframe src=\"https:\u002F\u002Fad-network.com\u002Fad\" height=\"250\">\u003C\u002Fiframe>\n\u003C\u002Fdiv>\n\n\u003C!-- ✅ Or use aspect-ratio container -->\n\u003Cdiv style=\"aspect-ratio: 16\u002F9;\">\n  \u003Ciframe src=\"https:\u002F\u002Fyoutube.com\u002Fembed\u002F...\" \n          style=\"width: 100%; height: 100%;\">\u003C\u002Fiframe>\n\u003C\u002Fdiv>\n```\n\n**3. Dynamically injected content**\n```javascript\n\u002F\u002F ❌ Inserts content above viewport\nnotifications.prepend(newNotification);\n\n\u002F\u002F ✅ Insert below viewport or use transform\nconst insertBelow = viewport.bottom \u003C newNotification.top;\nif (insertBelow) {\n  notifications.prepend(newNotification);\n} else {\n  \u002F\u002F Animate in without shifting\n  newNotification.style.transform = 'translateY(-100%)';\n  notifications.prepend(newNotification);\n  requestAnimationFrame(() => {\n    newNotification.style.transform = '';\n  });\n}\n```\n\n**4. Web fonts causing FOUT**\n```css\n\u002F* ❌ Font swap shifts text *\u002F\n@font-face {\n  font-family: 'Custom';\n  src: url('custom.woff2') format('woff2');\n}\n\n\u002F* ✅ Optional font (no shift if slow) *\u002F\n@font-face {\n  font-family: 'Custom';\n  src: url('custom.woff2') format('woff2');\n  font-display: optional;\n}\n\n\u002F* ✅ Or match fallback metrics *\u002F\n@font-face {\n  font-family: 'Custom';\n  src: url('custom.woff2') format('woff2');\n  font-display: swap;\n  size-adjust: 105%; \u002F* Match fallback size *\u002F\n  ascent-override: 95%;\n  descent-override: 20%;\n}\n```\n\n**5. Animations triggering layout**\n```css\n\u002F* ❌ Animates layout properties *\u002F\n.animate {\n  transition: height 0.3s, width 0.3s;\n}\n\n\u002F* ✅ Use transform instead *\u002F\n.animate {\n  transition: transform 0.3s;\n}\n.animate.expanded {\n  transform: scale(1.2);\n}\n```\n\n### CLS optimization checklist\n\n```markdown\n- [ ] All images have width\u002Fheight or aspect-ratio\n- [ ] All videos\u002Fembeds have reserved space\n- [ ] Ads have min-height containers\n- [ ] Fonts use font-display: optional or matched metrics\n- [ ] Dynamic content inserted below viewport\n- [ ] Animations use transform\u002Fopacity only\n- [ ] No content injected above existing content\n```\n\n### CLS debugging\n```javascript\n\u002F\u002F Track layout shifts\nnew PerformanceObserver((list) => {\n  for (const entry of list.getEntries()) {\n    if (!entry.hadRecentInput) {\n      console.log('Layout shift:', entry.value);\n      entry.sources?.forEach(source => {\n        console.log('  Shifted element:', source.node);\n        console.log('  Previous rect:', source.previousRect);\n        console.log('  Current rect:', source.currentRect);\n      });\n    }\n  }\n}).observe({ type: 'layout-shift', buffered: true });\n```\n\n---\n\n## Measurement tools\n\n### Lab testing\n- **Chrome DevTools** → Performance panel, Lighthouse\n- **WebPageTest** → Detailed waterfall, filmstrip\n- **Lighthouse CLI** → `npx lighthouse \u003Curl>`\n\n### Field data (real users)\n- **Chrome User Experience Report (CrUX)** → BigQuery or API\n- **Search Console** → Core Web Vitals report\n- **web-vitals library** → Send to your analytics\n\n```javascript\nimport {onLCP, onINP, onCLS} from 'web-vitals';\n\nfunction sendToAnalytics({name, value, rating}) {\n  gtag('event', name, {\n    event_category: 'Web Vitals',\n    value: Math.round(name === 'CLS' ? value * 1000 : value),\n    event_label: rating\n  });\n}\n\nonLCP(sendToAnalytics);\nonINP(sendToAnalytics);\nonCLS(sendToAnalytics);\n```\n\n---\n\n## Framework quick fixes\n\n### Next.js\n```jsx\n\u002F\u002F LCP: Use next\u002Fimage with priority\nimport Image from 'next\u002Fimage';\n\u003CImage src=\"\u002Fhero.jpg\" priority fill alt=\"Hero\" \u002F>\n\n\u002F\u002F INP: Use dynamic imports\nconst HeavyComponent = dynamic(() => import('.\u002FHeavy'), { ssr: false });\n\n\u002F\u002F CLS: Image component handles dimensions automatically\n```\n\n### React\n```jsx\n\u002F\u002F LCP: Preload in head\n\u003Clink rel=\"preload\" href=\"\u002Fhero.jpg\" as=\"image\" fetchpriority=\"high\" \u002F>\n\n\u002F\u002F INP: Memoize and useTransition\nconst [isPending, startTransition] = useTransition();\nstartTransition(() => setExpensiveState(newValue));\n\n\u002F\u002F CLS: Always specify dimensions in img tags\n```\n\n### Vue\u002FNuxt\n```vue\n\u003C!-- LCP: Use nuxt\u002Fimage with preload -->\n\u003CNuxtImg src=\"\u002Fhero.jpg\" preload loading=\"eager\" \u002F>\n\n\u003C!-- INP: Use async components -->\n\u003Ccomponent :is=\"() => import('.\u002FHeavy.vue')\" \u002F>\n\n\u003C!-- CLS: Use aspect-ratio CSS -->\n\u003Cimg :style=\"{ aspectRatio: '16\u002F9' }\" \u002F>\n```\n\n## References\n\n- [web.dev LCP](https:\u002F\u002Fweb.dev\u002Farticles\u002Flcp)\n- [web.dev INP](https:\u002F\u002Fweb.dev\u002Farticles\u002Finp)\n- [web.dev CLS](https:\u002F\u002Fweb.dev\u002Farticles\u002Fcls)\n- [Performance skill](..\u002Fperformance\u002FSKILL.md)\n",{"name":5,"license":14,"metadata":36,"description":7},{"author":37,"version":38},"web-quality-skills","1.0","main","770ead1d2718b931bf0d81b757d75d0f33e43d90","skills\u002Fcore-web-vitals",2,"https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config","https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen","2026-09-05 20:20:54",{"source":8,"skillName":5,"commands":47,"prompt":57,"zipAvailable":58},[48,51,54],{"tool":49,"command":50},"npx","npx skills add kennyolofsson23-netizen\u002Fclaude-code-config --skill core-web-vitals",{"tool":52,"command":53},"bunx","bunx skills add kennyolofsson23-netizen\u002Fclaude-code-config --skill core-web-vitals",{"tool":55,"command":56},"pnpm","pnpm dlx skills add kennyolofsson23-netizen\u002Fclaude-code-config --skill core-web-vitals","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Fcore-web-vitals\u002FSKILL.md",true,[60],{"path":61,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":64,"sourceHtmlUrl":65},"references\u002FLCP.md","REFERENCE","参考文档",4431,"https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Fcore-web-vitals\u002Freferences\u002FLCP.md",[67,96,129,163,177,203],{"skillNo":68,"name":69,"nameZh":6,"description":70,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":83,"publishedAt":31},"01M6DZ7A62MZTGTBH4GYQPYHHM","web-quality-audit","Comprehensive web quality audit covering performance, accessibility, SEO, and best practices. Use when asked to \"audit my site\", \"review web quality\", \"run lighthouse audit\", \"check page quality\", or \"optimize my website\".","https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Fweb-quality-audit\u002FSKILL.md",[73,78],{"categoryNo":74,"categoryName":75,"taxonomyType":76,"taxonomyTypeDesc":77},"01JBSEEDCAT000000000000019","营销推广","SCENARIO","使用场景",{"categoryNo":79,"categoryName":80,"taxonomyType":81,"taxonomyTypeDesc":82},"01JBSEEDCAT000000000000035","市场营销","PROFESSION","适用职业",[18,84,85,25,86,87,88,89,90,91,92,93,94,24,95,27,28],"audit","web quality audit","review","performance","accessibility","run","best practices","website","seo","Lighthouse","site","check",{"skillNo":97,"name":98,"nameZh":6,"description":99,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":108,"publishedAt":31},"01M6DZ6385911YENGYA985W015","tailwind-v4-shadcn","Production-tested setup for Tailwind CSS v4 with shadcn\u002Fui, Vite, and React.\n\nUse when: initializing React projects with Tailwind v4, setting up shadcn\u002Fui,\nimplementing dark mode, debugging CSS variable issues, fixing theme switching,\nmigrating from Tailwind v3, or encountering color\u002Ftheming problems.\n\nCovers: @theme inline pattern, CSS variable architecture, dark mode with\nThemeProvider, component composition, vite.config setup, common v4 gotchas,\nand production-tested patterns.\n\nKeywords: Tailwind v4, shadcn\u002Fui, @tailwindcss\u002Fvite, @theme inline, dark mode,\nCSS variables, hsl() wrapper, components.json, React theming, theme switching,\ncolors not working, variables broken, theme not applying, @plugin directive,\ntypography plugin, forms plugin, prose class, @tailwindcss\u002Ftypography,\n@tailwindcss\u002Fforms","https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Ftailwind-v4-shadcn\u002FSKILL.md",[102,105],{"categoryNo":103,"categoryName":104,"taxonomyType":76,"taxonomyTypeDesc":77},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":106,"categoryName":107,"taxonomyType":76,"taxonomyTypeDesc":77},"01JBSEEDCAT000000000000015","视觉设计",[109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128],"shadcn","Tailwind","tailwind v4","tailwind v4 shadcn","css","tailwindcss","components","react","component","architecture","config","debugging","issues","Composition","projects","common","broken","up","switching","css variables",{"skillNo":130,"name":131,"nameZh":6,"description":132,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":133,"categories":134,"keywords":143,"publishedAt":31},"01M6DZ5Z3F0PTH87NM9ATQD3E3","social-content","When the user wants help creating, scheduling, or optimizing social media content for LinkedIn, Twitter\u002FX, Instagram, TikTok, Facebook, or other platforms. Also use when the user mentions 'LinkedIn post,' 'Twitter thread,' 'social media,' 'content calendar,' 'social scheduling,' 'engagement,' or 'viral content.' This skill covers content creation, repurposing, and platform-specific strategies.","https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Fsocial-content\u002FSKILL.md",[135,136,139,140],{"categoryNo":74,"categoryName":75,"taxonomyType":76,"taxonomyTypeDesc":77},{"categoryNo":137,"categoryName":138,"taxonomyType":76,"taxonomyTypeDesc":77},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":79,"categoryName":80,"taxonomyType":81,"taxonomyTypeDesc":82},{"categoryNo":141,"categoryName":142,"taxonomyType":81,"taxonomyTypeDesc":82},"01JBSEEDCAT000000000000036","内容创作者",[144,145,146,147,148,149,150,151,152,27,153,154,155,156,157,158,159,160,161,162],"content","social content","social","Media","marketing","twitter","linkedin","instagram","social media","calendar","scheduling","tiktok","content calendar","content creation","facebook","viral","strategies","platform","creation",{"skillNo":164,"name":92,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":170,"publishedAt":31},"01M6DZ5NDBE4821RDH11V7FQTK","Optimize for search engine visibility and ranking. Use when asked to \"improve SEO\", \"optimize for search\", \"fix meta tags\", \"add structured data\", \"sitemap optimization\", or \"search engine optimization\".","https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Fseo\u002FSKILL.md",[168,169],{"categoryNo":74,"categoryName":75,"taxonomyType":76,"taxonomyTypeDesc":77},{"categoryNo":79,"categoryName":80,"taxonomyType":81,"taxonomyTypeDesc":82},[92,171,18,21,22,172,173,174,23,175,25,26,27,28,176,30],"structured data","data","Meta","Meta Tags","engine","structured",{"skillNo":178,"name":179,"nameZh":6,"description":180,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":181,"categories":182,"keywords":188,"publishedAt":31},"01M6DZ39W3BS39MBVGBDA38DV9","vercel-react-best-practices","React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React\u002FNext.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.","https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Freact-best-practices\u002FSKILL.md",[183,184,185],{"categoryNo":103,"categoryName":104,"taxonomyType":76,"taxonomyTypeDesc":77},{"categoryNo":137,"categoryName":138,"taxonomyType":76,"taxonomyTypeDesc":77},{"categoryNo":186,"categoryName":187,"taxonomyType":81,"taxonomyTypeDesc":82},"01JBSEEDCAT000000000000030","开发工程师",[116,189,90,190,191,192,87,115,193,194,195,196,22,172,197,198,199,200,201,202],"vercel react best practices","react best practices","vercel","writing","react components","guidelines","tasks","code","Next.js","refactoring","patterns","next","pages","triggers",{"skillNo":204,"name":205,"nameZh":6,"description":206,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":207,"categories":208,"keywords":214,"publishedAt":31},"01M6DZ2C3HPF1JX3TD508SXNDR","supabase-postgres-best-practices","Postgres performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.","https:\u002F\u002Fgithub.com\u002Fkennyolofsson23-netizen\u002Fclaude-code-config\u002Fblob\u002Fmain\u002Fskills\u002Fpostgres-best-practices\u002FSKILL.md",[209,212,213],{"categoryNo":210,"categoryName":211,"taxonomyType":76,"taxonomyTypeDesc":77},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":137,"categoryName":138,"taxonomyType":76,"taxonomyTypeDesc":77},{"categoryNo":186,"categoryName":187,"taxonomyType":81,"taxonomyTypeDesc":82},[90,215,216,217,218,192,87,196,219,220,22,221,222,223,27,224,225,226,227,228],"supabase postgres best practices","supabase","postgres best practices","postgres","database","examples","analysis","correct","Rules","schema","query","across","critical","impact"]