mcpField
C
core-web-vitals

core-web-vitals

github.com/davila7开发33.0k114 次安装今天更新
代码仓库

关于 core-web-vitals

这项 Web 性能优化技能聚焦 LCP、INP 和 CLS,提供常见问题诊断与修复建议,适用于改善页面加载、响应速度和视觉稳定性的开发者。

主要功能

  1. 01LCP 优化指导
  2. 02INP 响应性诊断
  3. 03CLS 稳定性检查
  4. 04实验室与真实用户测量选项
  5. 05Next.js、React 和 Vue 快速修复

适用场景

  1. 01查找页面加载缓慢的原因
  2. 02诊断用户交互延迟
  3. 03减少意外布局偏移

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/davila7/claude-code-templates --skill core-web-vitals

安装后在 Claude Code 中输入/core-web-vitals确认已加载。

附带文件references/LCP.md

详细介绍

Core Web Vitals 优化

针对影响 Google 搜索排名和用户体验的三项 Core Web Vitals 指标进行优化。

三项指标

指标衡量内容良好需要改进较差
LCP加载≤ 2.5s2.5s – 4s> 4s
INP交互性≤ 200ms200ms – 500ms> 500ms
CLS视觉稳定性≤ 0.10.1 – 0.25> 0.25

Google 按第 75 百分位进行衡量——75% 的页面访问必须达到“良好”阈值。


LCP:最大内容绘制

LCP 衡量最大可见内容元素的渲染时间,通常是:

  • 主视觉图片或视频
  • 大段文本
  • 背景图片
  • <svg> 元素
常见的 LCP 问题

1. 服务器响应缓慢(TTFB > 800ms)

Fix: CDN, caching, optimized backend, edge rendering

2. 阻塞渲染的资源


<link rel="stylesheet" href="/all-styles.css">


<style>/* Critical above-fold CSS */</style>
<link rel="preload" href="/styles.css" as="style" 
      onload="this.onload=null;this.rel='stylesheet'">

3. 资源加载时间过长


<img src="/hero.jpg" alt="Hero">


<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
<img src="/hero.webp" alt="Hero" fetchpriority="high">

4. 客户端渲染延迟

// ❌ Content loads after JavaScript
useEffect(() => {
  fetch('/api/hero-text').then(r => r.json()).then(setHeroText);
}, []);

// ✅ Server-side or static rendering
// Use SSR, SSG, or streaming to send HTML with content
export async function getServerSideProps() {
  const heroText = await fetchHeroText();
  return { props: { heroText } };
}
LCP 优化清单
- [ ] TTFB < 800ms (use CDN, edge caching)
- [ ] LCP image preloaded with fetchpriority="high"
- [ ] LCP image optimized (WebP/AVIF, correct size)
- [ ] Critical CSS inlined (< 14KB)
- [ ] No render-blocking JavaScript in <head>
- [ ] Fonts don't block text rendering (font-display: swap)
- [ ] LCP element in initial HTML (not JS-rendered)
识别 LCP 元素
// Find your LCP element
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP element:', lastEntry.element);
  console.log('LCP time:', lastEntry.startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });

INP:交互到下一次绘制

INP 衡量页面访问期间所有交互(点击、轻触、按键)的响应能力。它报告最差的交互(对于流量较高的页面,按第 98 百分位计算)。

INP 分解

INP 总时长 = 输入延迟 + 处理时间 + 呈现延迟

阶段目标优化措施
输入延迟< 50ms减少主线程阻塞
处理< 100ms优化事件处理程序
呈现< 50ms尽量减少渲染工作
常见的 INP 问题

1. 长任务阻塞主线程

// ❌ Long synchronous task
function processLargeArray(items) {
  items.forEach(item => expensiveOperation(item));
}

// ✅ Break into chunks with yielding
async function processLargeArray(items) {
  const CHUNK_SIZE = 100;
  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE);
    chunk.forEach(item => expensiveOperation(item));
    
    // Yield to main thread
    await new Promise(r => setTimeout(r, 0));
    // Or use scheduler.yield() when available
  }
}

2. 事件处理程序开销过大

// ❌ All work in handler
button.addEventListener('click', () => {
  // Heavy computation
  const result = calculateComplexThing();
  // DOM updates
  updateUI(result);
  // Analytics
  trackEvent('click');
});

// ✅ Prioritize visual feedback
button.addEventListener('click', () => {
  // Immediate visual feedback
  button.classList.add('loading');
  
  // Defer non-critical work
  requestAnimationFrame(() => {
    const result = calculateComplexThing();
    updateUI(result);
  });
  
  // Use requestIdleCallback for analytics
  requestIdleCallback(() => trackEvent('click'));
});

3. 第三方脚本

// ❌ Eagerly loaded, blocks interactions
<script src="https://heavy-widget.com/widget.js"></script>

// ✅ Lazy loaded on interaction or visibility
const loadWidget = () => {
  import('https://heavy-widget.com/widget.js')
    .then(widget => widget.init());
};
button.addEventListener('click', loadWidget, { once: true });

4. 过度重新渲染(React/Vue)

// ❌ Re-renders entire tree
function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <Counter count={count} />
      <ExpensiveComponent /> {/* Re-renders on every count change */}
    </div>
  );
}

// ✅ Memoized expensive components
const MemoizedExpensive = React.memo(ExpensiveComponent);

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <Counter count={count} />
      <MemoizedExpensive />
    </div>
  );
}
INP 优化清单
- [ ] No tasks > 50ms on main thread
- [ ] Event handlers complete quickly (< 100ms)
- [ ] Visual feedback provided immediately
- [ ] Heavy work deferred with requestIdleCallback
- [ ] Third-party scripts don't block interactions
- [ ] Debounced input handlers where appropriate
- [ ] Web Workers for CPU-intensive operations
INP 调试
// Identify slow interactions
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 200) {
      console.warn('Slow interaction:', {
        type: entry.name,
        duration: entry.duration,
        processingStart: entry.processingStart,
        processingEnd: entry.processingEnd,
        target: entry.target
      });
    }
  }
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });

CLS:累积布局偏移

CLS 衡量意外的布局偏移。当可见元素在没有用户交互的情况下,在不同帧之间改变位置时,就会发生偏移。

CLS 公式: 影响比例 × 距离比例

CLS 的常见成因

1. 图片未设置尺寸


<img src="photo.jpg" alt="Photo">


<img src="photo.jpg" alt="Photo" width="800" height="600">


<img src="photo.jpg" alt="Photo" style="aspect-ratio: 4/3; width: 100%;">

2. 广告、嵌入内容和 iframe


<iframe src="https://ad-network.com/ad"></iframe>


<div style="min-height: 250px;">
  <iframe src="https://ad-network.com/ad" height="250"></iframe>
</div>


<div style="aspect-ratio: 16/9;">
  <iframe src="https://youtube.com/embed/..." 
          style="width: 100%; height: 100%;"></iframe>
</div>

3. 动态插入的内容

// ❌ Inserts content above viewport
notifications.prepend(newNotification);

// ✅ Insert below viewport or use transform
const insertBelow = viewport.bottom < newNotification.top;
if (insertBelow) {
  notifications.prepend(newNotification);
} else {
  // Animate in without shifting
  newNotification.style.transform = 'translateY(-100%)';
  notifications.prepend(newNotification);
  requestAnimationFrame(() => {
    newNotification.style.transform = '';
  });
}

4. Web 字体导致 FOUT

/* ❌ Font swap shifts text */
@font-face {
  font-family: 'Custom';
  src: url('custom.woff2') format('woff2');
}

/* ✅ Optional font (no shift if slow) */
@font-face {
  font-family: 'Custom';
  src: url('custom.woff2') format('woff2');
  font-display: optional;
}

/* ✅ Or match fallback metrics */
@font-face {
  font-family: 'Custom';
  src: url('custom.woff2') format('woff2');
  font-display: swap;
  size-adjust: 105%; /* Match fallback size */
  ascent-override: 95%;
  descent-override: 20%;
}

5. 触发布局的动画

/* ❌ Animates layout properties */
.animate {
  transition: height 0.3s, width 0.3s;
}

/* ✅ Use transform instead */
.animate {
  transition: transform 0.3s;
}
.animate.expanded {
  transform: scale(1.2);
}
CLS 优化清单
- [ ] All images have width/height or aspect-ratio
- [ ] All videos/embeds have reserved space
- [ ] Ads have min-height containers
- [ ] Fonts use font-display: optional or matched metrics
- [ ] Dynamic content inserted below viewport
- [ ] Animations use transform/opacity only
- [ ] No content injected above existing content
CLS 调试
// Track layout shifts
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      console.log('Layout shift:', entry.value);
      entry.sources?.forEach(source => {
        console.log('  Shifted element:', source.node);
        console.log('  Previous rect:', source.previousRect);
        console.log('  Current rect:', source.currentRect);
      });
    }
  }
}).observe({ type: 'layout-shift', buffered: true });

测量工具

实验室测试
  • Chrome DevTools → Performance 面板、Lighthouse
  • WebPageTest → 详细瀑布图、胶片带
  • Lighthouse CLI → npx lighthouse <url>
现场数据(真实用户)
  • Chrome User Experience Report (CrUX) → BigQuery 或 API
  • Search Console → Core Web Vitals 报告
  • web-vitals library → 发送至你的分析工具
import {onLCP, onINP, onCLS} from 'web-vitals';

function sendToAnalytics({name, value, rating}) {
  gtag('event', name, {
    event_category: 'Web Vitals',
    value: Math.round(name === 'CLS' ? value * 1000 : value),
    event_label: rating
  });
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

框架快速修复

Next.js
// LCP: Use next/image with priority
import Image from 'next/image';
<Image src="/hero.jpg" priority fill alt="Hero" />

// INP: Use dynamic imports
const HeavyComponent = dynamic(() => import('./Heavy'), { ssr: false });

// CLS: Image component handles dimensions automatically
React
// LCP: Preload in head
<link rel="preload" href="/hero.jpg" as="image" fetchpriority="high" />

// INP: Memoize and useTransition
const [isPending, startTransition] = useTransition();
startTransition(() => setExpensiveState(newValue));

// CLS: Always specify dimensions in img tags
Vue/Nuxt

<NuxtImg src="/hero.jpg" preload loading="eager" />


<component :is="() => import('./Heavy.vue')" />


<img :style="{ aspectRatio: '16/9' }" />

参考资料

评论

支持 Markdown