[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6BY6QFMBFYV55FD52DN45CT":3,"skill-same:01M6BY6QFMBFYV55FD52DN45CT":69},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":32,"linkOnly":33,"markdownContent":34,"markdownBody":35,"frontMatter":36,"refName":40,"commitSha":41,"skillDirectory":42,"forksCount":14,"repositoryHtmlUrl":43,"repositoryHomepageUrl":44,"ownerHtmlUrl":45,"ownerBlogUrl":46,"ownerDescription":47,"updatedAt":48,"install":49,"resources":62},"01M6BY6QFMBFYV55FD52DN45CT","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\".","wolfstar-project\u002Fwolfstar.rocks","wolfstar-project","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F109528280?v=4","01M6BXZW9J3Y1DWPNSBBWC2BVY","wolfstar","01M6BXZWDRD3C71GVE58BWXW1D",0,"MIT","https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Fcore-web-vitals\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31],"web","core","core web vitals","search","Optimization","improve","page","quality","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**5. Make navigations instant with the Speculation Rules API**\n\nFor most sites, the LCP a user actually experiences is dominated by *the next page they navigate to*, not the one they landed on. Telling the browser to prerender likely-next pages on hover collapses that LCP to ~0ms.\n\n```html\n\u003Cscript type=\"speculationrules\">\n{\n  \"prerender\": [{\n    \"where\": { \"href_matches\": \"\u002F*\" },\n    \"eagerness\": \"moderate\"\n  }]\n}\n\u003C\u002Fscript>\n```\n\n`eagerness` settings (cheapest → most aggressive): `conservative` (start on pointerdown), `moderate` (start after ~200ms hover), `eager` (start as soon as the link is in the viewport), `immediate` (start on page load). Start with `moderate` — it captures most navigations without prerendering pages users never visit.\n\nCaveats:\n- **Bandwidth\u002FCPU cost.** Each prerender is roughly a full page load. Scope `where` carefully (`href_matches` patterns, exclude logout\u002Fcheckout) and avoid `immediate` outside small sites.\n- **Side effects fire early.** Analytics, ads, and any code that runs on load will fire when the prerender starts, not when the user navigates. Gate side effects on the [`prerenderingchange` event](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fweb-platform\u002Fprerender-pages#detect_when_a_page_is_prerendered_or_used_for_a_full_navigation) or `document.prerendering`.\n- **Chromium-only.** Safari and Firefox ignore the script — it's a progressive enhancement, never a regression.\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- [ ] Speculation Rules added for likely-next navigations (moderate eagerness)\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 and yield to the scheduler. scheduler.yield() is the\n\u002F\u002F    recommended modern API — its continuation is queued at a boosted\n\u002F\u002F    priority so the rest of your work resumes ahead of unrelated tasks,\n\u002F\u002F    while still letting the browser handle pending input first.\nasync function processLargeArray(items) {\n  const CHUNK_SIZE = 100;\n  for (let i = 0; i \u003C items.length; i += CHUNK_SIZE) {\n    items.slice(i, i + CHUNK_SIZE).forEach(expensiveOperation);\n\n    if ('scheduler' in window && 'yield' in scheduler) {\n      await scheduler.yield();\n    } else {\n      \u002F\u002F Fallback for browsers without scheduler.yield (Safari, older Firefox).\n      \u002F\u002F setTimeout(0) yields but loses priority — your continuation may run\n      \u002F\u002F after unrelated tasks the browser picked up in between.\n      await new Promise(r => setTimeout(r, 0));\n    }\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, then yield before doing the heavy work\nbutton.addEventListener('click', async () => {\n  \u002F\u002F 1. Immediate visual feedback (cheap DOM update)\n  button.classList.add('loading');\n\n  \u002F\u002F 2. Yield so the browser can paint the loading state before we block\n  if ('scheduler' in window && 'yield' in scheduler) {\n    await scheduler.yield();\n  }\n\n  \u002F\u002F 3. Now do the heavy work — the user already saw the click register\n  const result = calculateComplexThing();\n  updateUI(result);\n\n  \u002F\u002F 4. Lowest-priority work last, when the main thread is idle\n  if ('requestIdleCallback' in window) {\n    requestIdleCallback(() => trackEvent('click'));\n  } else {\n    setTimeout(() => trackEvent('click'), 0);\n  }\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. durationThreshold: 40 matches what the\n\u002F\u002F web-vitals library uses — 16 (one frame) fires on nearly every interaction\n\u002F\u002F and drowns the console; 40 surfaces interactions that are starting to feel\n\u002F\u002F sluggish without spamming.\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: 40 });\n```\n\nFor field debugging across real users, prefer the `web-vitals\u002Fattribution` build of the [web-vitals library](https:\u002F\u002Fgithub.com\u002FGoogleChrome\u002Fweb-vitals) — `onINP()` from that build attaches a `LoAF` (Long Animation Frame) breakdown identifying the longest script and the input\u002Fprocessing\u002Fpresentation phase that ate the budget.\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**5. Make navigations instant with the Speculation Rules API**\n\nFor most sites, the LCP a user actually experiences is dominated by *the next page they navigate to*, not the one they landed on. Telling the browser to prerender likely-next pages on hover collapses that LCP to ~0ms.\n\n```html\n\u003Cscript type=\"speculationrules\">\n{\n  \"prerender\": [{\n    \"where\": { \"href_matches\": \"\u002F*\" },\n    \"eagerness\": \"moderate\"\n  }]\n}\n\u003C\u002Fscript>\n```\n\n`eagerness` settings (cheapest → most aggressive): `conservative` (start on pointerdown), `moderate` (start after ~200ms hover), `eager` (start as soon as the link is in the viewport), `immediate` (start on page load). Start with `moderate` — it captures most navigations without prerendering pages users never visit.\n\nCaveats:\n- **Bandwidth\u002FCPU cost.** Each prerender is roughly a full page load. Scope `where` carefully (`href_matches` patterns, exclude logout\u002Fcheckout) and avoid `immediate` outside small sites.\n- **Side effects fire early.** Analytics, ads, and any code that runs on load will fire when the prerender starts, not when the user navigates. Gate side effects on the [`prerenderingchange` event](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fweb-platform\u002Fprerender-pages#detect_when_a_page_is_prerendered_or_used_for_a_full_navigation) or `document.prerendering`.\n- **Chromium-only.** Safari and Firefox ignore the script — it's a progressive enhancement, never a regression.\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- [ ] Speculation Rules added for likely-next navigations (moderate eagerness)\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 and yield to the scheduler. scheduler.yield() is the\n\u002F\u002F    recommended modern API — its continuation is queued at a boosted\n\u002F\u002F    priority so the rest of your work resumes ahead of unrelated tasks,\n\u002F\u002F    while still letting the browser handle pending input first.\nasync function processLargeArray(items) {\n  const CHUNK_SIZE = 100;\n  for (let i = 0; i \u003C items.length; i += CHUNK_SIZE) {\n    items.slice(i, i + CHUNK_SIZE).forEach(expensiveOperation);\n\n    if ('scheduler' in window && 'yield' in scheduler) {\n      await scheduler.yield();\n    } else {\n      \u002F\u002F Fallback for browsers without scheduler.yield (Safari, older Firefox).\n      \u002F\u002F setTimeout(0) yields but loses priority — your continuation may run\n      \u002F\u002F after unrelated tasks the browser picked up in between.\n      await new Promise(r => setTimeout(r, 0));\n    }\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, then yield before doing the heavy work\nbutton.addEventListener('click', async () => {\n  \u002F\u002F 1. Immediate visual feedback (cheap DOM update)\n  button.classList.add('loading');\n\n  \u002F\u002F 2. Yield so the browser can paint the loading state before we block\n  if ('scheduler' in window && 'yield' in scheduler) {\n    await scheduler.yield();\n  }\n\n  \u002F\u002F 3. Now do the heavy work — the user already saw the click register\n  const result = calculateComplexThing();\n  updateUI(result);\n\n  \u002F\u002F 4. Lowest-priority work last, when the main thread is idle\n  if ('requestIdleCallback' in window) {\n    requestIdleCallback(() => trackEvent('click'));\n  } else {\n    setTimeout(() => trackEvent('click'), 0);\n  }\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. durationThreshold: 40 matches what the\n\u002F\u002F web-vitals library uses — 16 (one frame) fires on nearly every interaction\n\u002F\u002F and drowns the console; 40 surfaces interactions that are starting to feel\n\u002F\u002F sluggish without spamming.\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: 40 });\n```\n\nFor field debugging across real users, prefer the `web-vitals\u002Fattribution` build of the [web-vitals library](https:\u002F\u002Fgithub.com\u002FGoogleChrome\u002Fweb-vitals) — `onINP()` from that build attaches a `LoAF` (Long Animation Frame) breakdown identifying the longest script and the input\u002Fprocessing\u002Fpresentation phase that ate the budget.\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":15,"metadata":37,"description":7},{"author":38,"version":39},"web-quality-skills","1.0","main","218944972a37a59fccc9ee1474e524ddc83048aa",".skills\u002Fcore-web-vitals","https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks","https:\u002F\u002Fwolfstar.rocks","https:\u002F\u002Fgithub.com\u002Fwolfstar-project","https:\u002F\u002Fwww.wolfstar.rocks","All projects for bot WolfStar","2026-11-12 03:33:55",{"source":8,"skillName":5,"commands":50,"prompt":60,"zipAvailable":61},[51,54,57],{"tool":52,"command":53},"npx","npx skills add wolfstar-project\u002Fwolfstar.rocks --skill core-web-vitals",{"tool":55,"command":56},"bunx","bunx skills add wolfstar-project\u002Fwolfstar.rocks --skill core-web-vitals",{"tool":58,"command":59},"pnpm","pnpm dlx skills add wolfstar-project\u002Fwolfstar.rocks --skill core-web-vitals","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Fcore-web-vitals\u002FSKILL.md",true,[63],{"path":64,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":67,"sourceHtmlUrl":68},"references\u002FLCP.md","REFERENCE","参考文档",4431,"https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Fcore-web-vitals\u002Freferences\u002FLCP.md",[70,90,114,134,148,159],{"skillNo":71,"name":72,"nameZh":6,"description":73,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":74,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":85,"publishedAt":32},"01M6BY9H7CDDKSGQ7G6SG7H7VW","writing-plans","Use when you have a spec or requirements for a multi-step task, before touching code","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Fwriting-plans\u002FSKILL.md",[77,80],{"categoryNo":78,"categoryName":79,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":81,"categoryName":82,"taxonomyType":83,"taxonomyTypeDesc":84},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[86,87,88,89],"writing","writing plans","code","spec",{"skillNo":91,"name":92,"nameZh":6,"description":93,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":102,"publishedAt":32},"01M6BY9C1NMAWJYQV2AG9Z50JD","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\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Fweb-quality-audit\u002FSKILL.md",[96,99],{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000019","营销推广",{"categoryNo":100,"categoryName":101,"taxonomyType":83,"taxonomyTypeDesc":84},"01JBSEEDCAT000000000000035","市场营销",[103,104,24,31,105,106,107,108,109,110,111,112,113,30],"audit","web quality audit","performance","accessibility","run","review","best practices","website","seo","Lighthouse","site",{"skillNo":115,"name":116,"nameZh":6,"description":117,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":74,"licenseFileUrl":6,"sourceHtmlUrl":118,"categories":119,"keywords":126,"publishedAt":32},"01M6BY99BPGY7PGAJNJBSA43VC","web-design-guidelines","Review UI code for Web Interface Guidelines compliance. Use when asked to \"review my UI\", \"check accessibility\", \"audit design\", \"review UX\", or \"check my site against best practices\".","https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Fweb-design-guidelines\u002FSKILL.md",[120,123],{"categoryNo":121,"categoryName":122,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":124,"categoryName":125,"taxonomyType":83,"taxonomyTypeDesc":84},"01JBSEEDCAT000000000000032","设计师",[127,24,128,129,108,113,106,88,109,130,131,103,132,133],"guidelines","design","web design guidelines","ux","file","ui","compliance",{"skillNo":135,"name":136,"nameZh":6,"description":137,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":74,"licenseFileUrl":6,"sourceHtmlUrl":138,"categories":139,"keywords":144,"publishedAt":32},"01M6BY96B8TTMXB77QSZ4RG44C","test-driven-development","Use when implementing any feature or bugfix, before writing implementation code","https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Ftest-driven-development\u002FSKILL.md",[140,143],{"categoryNo":141,"categoryName":142,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":78,"categoryName":79,"taxonomyType":21,"taxonomyTypeDesc":22},[145,146,147,86,88],"development","test driven development","test",{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":74,"licenseFileUrl":6,"sourceHtmlUrl":152,"categories":153,"keywords":154,"publishedAt":32},"01M6BY9415MPKFMGTNR996N2YX","tdd","Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions \"red-green-refactor\", or wants integration tests.","https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Ftdd\u002FSKILL.md",[],[150,147,155,145,156,157,158],"integration tests","integration","refactor","first",{"skillNo":160,"name":161,"nameZh":6,"description":162,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":74,"licenseFileUrl":6,"sourceHtmlUrl":163,"categories":164,"keywords":166,"publishedAt":32},"01M6BY91BK1ER9EV193FCVHY88","systematic-debugging","Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes","https:\u002F\u002Fgithub.com\u002Fwolfstar-project\u002Fwolfstar.rocks\u002Fblob\u002Fmain\u002F.skills\u002Fsystematic-debugging\u002FSKILL.md",[165],{"categoryNo":141,"categoryName":142,"taxonomyType":21,"taxonomyTypeDesc":22},[167,168,147],"systematic debugging","debugging"]