core-web-vitals

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Core Web Vitals

Core Web Vitals

Performance optimization for Google's Core Web Vitals - LCP, INP, CLS with 2026 thresholds.
针对谷歌Core Web Vitals(LCP、INP、CLS)的性能优化方案,包含2026年最新阈值要求。

Core Web Vitals Thresholds (2026)

Core Web Vitals 阈值(2026版)

MetricGoodNeeds ImprovementPoor
LCP (Largest Contentful Paint)≤ 2.5s≤ 4.0s> 4.0s
INP (Interaction to Next Paint)≤ 200ms≤ 500ms> 500ms
CLS (Cumulative Layout Shift)≤ 0.1≤ 0.25> 0.25
Note: INP replaced FID (First Input Delay) in March 2024 as the official responsiveness metric.
指标优秀需要改进较差
LCP(Largest Contentful Paint)≤ 2.5s≤ 4.0s> 4.0s
INP(Interaction to Next Paint)≤ 200ms≤ 500ms> 500ms
CLS(Cumulative Layout Shift)≤ 0.1≤ 0.25> 0.25
注意:INP已于2024年3月取代FID(First Input Delay),成为官方响应性指标。

Upcoming 2026 Stricter Thresholds (Q4 2025 rollout)

即将到来的2026年更严格阈值(2025年第四季度推行)

MetricCurrent Good2026 Good
LCP≤ 2.5s≤ 2.0s
INP≤ 200ms≤ 150ms
CLS≤ 0.1≤ 0.08
Plan for stricter thresholds now to maintain search rankings.
指标当前优秀标准2026年优秀标准
LCP≤ 2.5s≤ 2.0s
INP≤ 200ms≤ 150ms
CLS≤ 0.1≤ 0.08
请提前为更严格的阈值做准备,以维持搜索排名。

LCP Optimization

LCP 优化方案

1. Identify LCP Element

1. 定位LCP元素

javascript
// Find LCP element in DevTools
new PerformanceObserver((entryList) => {
  const entries = entryList.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 });
javascript
// Find LCP element in DevTools
new PerformanceObserver((entryList) => {
  const entries = entryList.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 });

2. Optimize LCP Images

2. 优化LCP图片

tsx
// Priority loading for hero image
<img
  src="/hero.webp"
  alt="Hero"
  fetchpriority="high"
  loading="eager"
  decoding="async"
/>

// Next.js Image with priority
import Image from 'next/image';

<Image
  src="/hero.webp"
  alt="Hero"
  priority
  sizes="100vw"
  quality={85}
/>
tsx
// Priority loading for hero image
<img
  src="/hero.webp"
  alt="Hero"
  fetchpriority="high"
  loading="eager"
  decoding="async"
/>

// Next.js Image with priority
import Image from 'next/image';

<Image
  src="/hero.webp"
  alt="Hero"
  priority
  sizes="100vw"
  quality={85}
/>

3. Preload Critical Resources

3. 预加载关键资源

html
<!-- Preload LCP image -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />

<!-- Preload critical font -->
<link rel="preload" as="font" href="/fonts/inter.woff2" type="font/woff2" crossorigin />

<!-- Preconnect to critical origins -->
<link rel="preconnect" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://analytics.example.com" />
html
<!-- Preload LCP image -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />

<!-- Preload critical font -->
<link rel="preload" as="font" href="/fonts/inter.woff2" type="font/woff2" crossorigin />

<!-- Preconnect to critical origins -->
<link rel="preconnect" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://analytics.example.com" />

4. Server-Side Rendering

4. 服务端渲染(SSR)

typescript
// Next.js - ensure SSR for LCP content
export default async function Page() {
  const data = await fetchCriticalData();
  return <HeroSection data={data} />; // Rendered on server
}

// Avoid client-only LCP content
// BAD: LCP content loaded client-side
const [data, setData] = useState(null);
useEffect(() => { fetchData().then(setData); }, []);
typescript
// Next.js - ensure SSR for LCP content
export default async function Page() {
  const data = await fetchCriticalData();
  return <HeroSection data={data} />; // Rendered on server
}

// Avoid client-only LCP content
// BAD: LCP content loaded client-side
const [data, setData] = useState(null);
useEffect(() => { fetchData().then(setData); }, []);

INP Optimization

INP 优化方案

1. Break Up Long Tasks

1. 拆分长任务

typescript
// BAD: Long synchronous task (blocks main thread)
function processLargeArray(items: Item[]) {
  items.forEach(processItem); // Blocks for entire duration
}

// GOOD: Yield to main thread
async function processLargeArray(items: Item[]) {
  for (const item of items) {
    processItem(item);
    // Yield every 50ms to allow paint
    if (performance.now() % 50 < 1) {
      await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
    }
  }
}
typescript
// BAD: Long synchronous task (blocks main thread)
function processLargeArray(items: Item[]) {
  items.forEach(processItem); // Blocks for entire duration
}

// GOOD: Yield to main thread
async function processLargeArray(items: Item[]) {
  for (const item of items) {
    processItem(item);
    // Yield every 50ms to allow paint
    if (performance.now() % 50 < 1) {
      await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
    }
  }
}

2. Use Transitions for Non-Urgent Updates

2. 为非紧急更新使用Transition

typescript
import { useTransition, useDeferredValue } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    // Urgent: Update input immediately
    setQuery(e.target.value);

    // Non-urgent: Defer expensive filter
    startTransition(() => {
      setFilteredResults(filterResults(e.target.value));
    });
  };

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <ResultsList results={filteredResults} />
    </>
  );
}
typescript
import { useTransition, useDeferredValue } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    // Urgent: Update input immediately
    setQuery(e.target.value);

    // Non-urgent: Defer expensive filter
    startTransition(() => {
      setFilteredResults(filterResults(e.target.value));
    });
  };

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <ResultsList results={filteredResults} />
    </>
  );
}

3. Optimize Event Handlers

3. 优化事件处理器

typescript
// BAD: Heavy computation in click handler
<button onClick={() => {
  const result = heavyComputation(); // Blocks paint
  setResult(result);
}}>Calculate</button>

// GOOD: Defer heavy work
<button onClick={() => {
  setLoading(true);
  requestIdleCallback(() => {
    const result = heavyComputation();
    setResult(result);
    setLoading(false);
  });
}}>Calculate</button>
typescript
// BAD: Heavy computation in click handler
<button onClick={() => {
  const result = heavyComputation(); // Blocks paint
  setResult(result);
}}>Calculate</button>

// GOOD: Defer heavy work
<button onClick={() => {
  setLoading(true);
  requestIdleCallback(() => {
    const result = heavyComputation();
    setResult(result);
    setLoading(false);
  });
}}>Calculate</button>

CLS Optimization

CLS 优化方案

1. Reserve Space for Dynamic Content

1. 为动态内容预留空间

css
/* Reserve space for images */
.image-container {
  aspect-ratio: 16 / 9;
  width: 100%;
}

/* Reserve space for ads */
.ad-slot {
  min-height: 250px;
}
css
/* Reserve space for images */
.image-container {
  aspect-ratio: 16 / 9;
  width: 100%;
}

/* Reserve space for ads */
.ad-slot {
  min-height: 250px;
}

2. Explicit Dimensions

2. 显式设置尺寸

tsx
// Always set width and height
<img src="/photo.jpg" width={800} height={600} alt="Photo" />

// Next.js Image handles this automatically
<Image src="/photo.jpg" width={800} height={600} alt="Photo" />

// For responsive images
<Image src="/photo.jpg" fill sizes="(max-width: 768px) 100vw, 50vw" />
tsx
// Always set width and height
<img src="/photo.jpg" width={800} height={600} alt="Photo" />

// Next.js Image handles this automatically
<Image src="/photo.jpg" width={800} height={600} alt="Photo" />

// For responsive images
<Image src="/photo.jpg" fill sizes="(max-width: 768px) 100vw, 50vw" />

3. Avoid Layout-Shifting Fonts

3. 避免导致布局偏移的字体加载

css
/* Use font-display: optional for non-critical fonts */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: optional; /* Prevents flash of unstyled text */
}

/* Or use size-adjust for fallback */
@font-face {
  font-family: 'Fallback';
  src: local('Arial');
  size-adjust: 105%;
  ascent-override: 95%;
}
css
/* Use font-display: optional for non-critical fonts */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: optional; /* Prevents flash of unstyled text */
}

/* Or use size-adjust for fallback */
@font-face {
  font-family: 'Fallback';
  src: local('Arial');
  size-adjust: 105%;
  ascent-override: 95%;
}

4. Animations That Don't Cause Layout Shift

4. 使用不会导致布局偏移的动画

css
/* BAD: Changes layout properties */
.expanding {
  height: 0;
  transition: height 0.3s;
}
.expanding.open {
  height: 200px; /* Causes layout shift */
}

/* GOOD: Use transform */
.expanding {
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.3s;
}
.expanding.open {
  transform: scaleY(1);
}
css
/* BAD: Changes layout properties */
.expanding {
  height: 0;
  transition: height 0.3s;
}
.expanding.open {
  height: 200px; /* Causes layout shift */
}

/* GOOD: Use transform */
.expanding {
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.3s;
}
.expanding.open {
  transform: scaleY(1);
}

Real User Monitoring (RUM)

真实用户监控(RUM)

typescript
// web-vitals library
import { onLCP, onINP, onCLS } from 'web-vitals';

function sendToAnalytics(metric: Metric) {
  fetch('/api/vitals', {
    method: 'POST',
    body: JSON.stringify({
      name: metric.name,
      value: metric.value,
      rating: metric.rating,
      navigationType: metric.navigationType,
    }),
    keepalive: true, // Send even if page unloads
  });
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
typescript
// web-vitals library
import { onLCP, onINP, onCLS } from 'web-vitals';

function sendToAnalytics(metric: Metric) {
  fetch('/api/vitals', {
    method: 'POST',
    body: JSON.stringify({
      name: metric.name,
      value: metric.value,
      rating: metric.rating,
      navigationType: metric.navigationType,
    }),
    keepalive: true, // Send even if page unloads
  });
}

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

Performance Budgets

性能预算

json
// lighthouse-budget.json
{
  "resourceSizes": [
    { "resourceType": "script", "budget": 150 },
    { "resourceType": "image", "budget": 300 },
    { "resourceType": "total", "budget": 500 }
  ],
  "timings": [
    { "metric": "largest-contentful-paint", "budget": 2500 },
    { "metric": "cumulative-layout-shift", "budget": 0.1 }
  ]
}
typescript
// webpack-budget.config.js
module.exports = {
  performance: {
    maxAssetSize: 150000, // 150kb
    maxEntrypointSize: 250000, // 250kb
    hints: 'error', // Fail build if exceeded
  },
};
json
// lighthouse-budget.json
{
  "resourceSizes": [
    { "resourceType": "script", "budget": 150 },
    { "resourceType": "image", "budget": 300 },
    { "resourceType": "total", "budget": 500 }
  ],
  "timings": [
    { "metric": "largest-contentful-paint", "budget": 2500 },
    { "metric": "cumulative-layout-shift", "budget": 0.1 }
  ]
}
typescript
// webpack-budget.config.js
module.exports = {
  performance: {
    maxAssetSize: 150000, // 150kb
    maxEntrypointSize: 250000, // 250kb
    hints: 'error', // Fail build if exceeded
  },
};

Debugging Tools

调试工具

ToolUse Case
Chrome DevTools PerformanceIdentify long tasks, layout shifts
LighthouseLab data, recommendations
PageSpeed InsightsField data + lab data
Web Vitals ExtensionReal-time vitals overlay
Chrome UX ReportReal user data by origin
工具使用场景
Chrome DevTools Performance定位长任务、布局偏移
Lighthouse实验室数据、优化建议
PageSpeed Insights真实用户数据+实验室数据
Web Vitals Extension实时性能指标悬浮窗
Chrome UX Report按域名统计的真实用户数据

Quick Reference

快速参考

typescript
// ✅ LCP: Preload and prioritize hero image
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
<Image src="/hero.webp" priority fill sizes="100vw" />

// ✅ INP: Use transitions for expensive updates
const [isPending, startTransition] = useTransition();
const deferredQuery = useDeferredValue(query);

// ✅ CLS: Always set dimensions, reserve space
<img src="/photo.jpg" width={800} height={600} alt="Photo" />
<div className="min-h-[250px]">{/* Reserved space */}</div>

// ✅ RUM: Send metrics reliably
navigator.sendBeacon('/api/vitals', JSON.stringify(metric));

// ✅ Font loading: Prevent FOUT/FOIT
@font-face {
  font-display: optional; // or swap with size-adjust
}

// ❌ NEVER: Client-side fetch for LCP content
useEffect(() => { fetchHeroData().then(setData); }, []);

// ❌ NEVER: Missing dimensions on images
<img src="/photo.jpg" alt="Photo" /> // Causes CLS

// ❌ NEVER: Heavy computation in event handlers
onClick={() => { heavyComputation(); setResult(result); }}
typescript
// ✅ LCP: Preload and prioritize hero image
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
<Image src="/hero.webp" priority fill sizes="100vw" />

// ✅ INP: Use transitions for expensive updates
const [isPending, startTransition] = useTransition();
const deferredQuery = useDeferredValue(query);

// ✅ CLS: Always set dimensions, reserve space
<img src="/photo.jpg" width={800} height={600} alt="Photo" />
<div className="min-h-[250px]">{/* Reserved space */}</div>

// ✅ RUM: Send metrics reliably
navigator.sendBeacon('/api/vitals', JSON.stringify(metric));

// ✅ Font loading: Prevent FOUT/FOIT
@font-face {
  font-display: optional; // or swap with size-adjust
}

// ❌ NEVER: Client-side fetch for LCP content
useEffect(() => { fetchHeroData().then(setData); }, []);

// ❌ NEVER: Missing dimensions on images
<img src="/photo.jpg" alt="Photo" /> // Causes CLS

// ❌ NEVER: Heavy computation in event handlers
onClick={() => { heavyComputation(); setResult(result); }}

Key Decisions

关键决策

DecisionOption AOption BRecommendation
LCP content renderingClient-sideSSR/SSGSSR/SSG - Critical content must be in initial HTML
Image formatJPEG/PNGWebP/AVIFWebP (AVIF for modern browsers) - 25-50% smaller
Font loadingswapoptionaloptional for non-critical, swap with fallback metrics
INP optimizationDebounceuseTransitionuseTransition - React 18+ native, better UX
MonitoringLab onlyLab + FieldLab + Field - Real user data is ground truth
Performance budgetSoft warningHard failHard fail in CI - Prevents regression
决策项选项A选项B推荐方案
LCP内容渲染方式客户端渲染SSR/SSGSSR/SSG - 核心内容必须包含在初始HTML中
图片格式JPEG/PNGWebP/AVIFWebP(现代浏览器推荐AVIF)- 体积减小25-50%
字体加载策略swapoptional非关键字体用optional,关键字体用swap并配合回退指标
INP优化方式防抖useTransitionuseTransition - React 18+原生方案,用户体验更优
监控方式仅实验室数据实验室+真实用户数据实验室+真实用户数据 - 真实用户数据是判断标准
性能预算 enforcement软警告强制失败CI中强制失败 - 防止性能退化

Anti-Patterns (FORBIDDEN)

反模式(禁止操作)

typescript
// ❌ FORBIDDEN: LCP element rendered client-side
function Hero() {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetchHeroContent().then(setData);  // LCP waits for JS + fetch!
  }, []);
  return data ? <HeroImage src={data.image} /> : <Skeleton />;
}

// ❌ FORBIDDEN: Images without dimensions
<img src="/photo.jpg" alt="Photo" />  // Browser can't reserve space
// ✅ CORRECT: Always provide width/height
<img src="/photo.jpg" width={800} height={600} alt="Photo" />

// ❌ FORBIDDEN: Lazy loading LCP image
<img src="/hero.webp" loading="lazy" />  // Delays LCP!
// ✅ CORRECT: Eager load with high priority
<img src="/hero.webp" fetchpriority="high" loading="eager" />

// ❌ FORBIDDEN: Blocking main thread in handlers
<button onClick={() => {
  const result = expensiveOperation();  // Blocks INP!
  setResult(result);
}}>Calculate</button>
// ✅ CORRECT: Defer heavy work
<button onClick={() => {
  startTransition(() => {
    const result = expensiveOperation();
    setResult(result);
  });
}}>Calculate</button>

// ❌ FORBIDDEN: Layout-shifting animations
.sidebar {
  width: 0;
  transition: width 0.3s;  // Causes layout shift!
}
// ✅ CORRECT: Use transform
.sidebar {
  transform: translateX(-100%);
  transition: transform 0.3s;
}

// ❌ FORBIDDEN: Inserting content above viewport
function Banner() {
  const [show, setShow] = useState(false);
  useEffect(() => {
    setTimeout(() => setShow(true), 1000);  // CLS!
  }, []);
  return show ? <div className="fixed top-0">Banner</div> : null;
}

// ❌ FORBIDDEN: Font flash without fallback
@font-face {
  font-family: 'Custom';
  src: url('/custom.woff2');
  font-display: block;  // Shows nothing until font loads
}

// ❌ FORBIDDEN: Only measuring in lab environment
// Lab data != real user experience
// Always combine Lighthouse with RUM (web-vitals library)

// ❌ FORBIDDEN: Third-party scripts blocking render
<script src="https://slow-analytics.com/script.js"></script>
// ✅ CORRECT: Defer or async non-critical scripts
<script src="https://analytics.com/script.js" defer></script>
typescript
// ❌ FORBIDDEN: LCP element rendered client-side
function Hero() {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetchHeroContent().then(setData);  // LCP需等待JS加载+请求完成!
  }, []);
  return data ? <HeroImage src={data.image} /> : <Skeleton />;
}

// ❌ FORBIDDEN: Images without dimensions
<img src="/photo.jpg" alt="Photo" />  // 浏览器无法预留空间
// ✅ CORRECT: Always provide width/height
<img src="/photo.jpg" width={800} height={600} alt="Photo" />

// ❌ FORBIDDEN: Lazy loading LCP image
<img src="/hero.webp" loading="lazy" />  // 会延迟LCP!
// ✅ CORRECT: Eager load with high priority
<img src="/hero.webp" fetchpriority="high" loading="eager" />

// ❌ FORBIDDEN: Blocking main thread in handlers
<button onClick={() => {
  const result = expensiveOperation();  // 会阻塞INP!
  setResult(result);
}}>Calculate</button>
// ✅ CORRECT: Defer heavy work
<button onClick={() => {
  startTransition(() => {
    const result = expensiveOperation();
    setResult(result);
  });
}}>Calculate</button>

// ❌ FORBIDDEN: Layout-shifting animations
.sidebar {
  width: 0;
  transition: width 0.3s;  // 会导致布局偏移!
}
// ✅ CORRECT: Use transform
.sidebar {
  transform: translateX(-100%);
  transition: transform 0.3s;
}

// ❌ FORBIDDEN: Inserting content above viewport
function Banner() {
  const [show, setShow] = useState(false);
  useEffect(() => {
    setTimeout(() => setShow(true), 1000);  // 会导致CLS!
  }, []);
  return show ? <div className="fixed top-0">Banner</div> : null;
}

// ❌ FORBIDDEN: Font flash without fallback
@font-face {
  font-family: 'Custom';
  src: url('/custom.woff2');
  font-display: block;  // 字体加载完成前不显示内容
}

// ❌ FORBIDDEN: Only measuring in lab environment
// 实验室数据 != 真实用户体验
// 务必结合Lighthouse与RUM(web-vitals库)

// ❌ FORBIDDEN: Third-party scripts blocking render
<script src="https://slow-analytics.com/script.js"></script>
// ✅ CORRECT: Defer or async non-critical scripts
<script src="https://analytics.com/script.js" defer></script>

Related Skills

相关技能

  • image-optimization
    - Comprehensive image optimization strategies
  • observability-monitoring
    - Production monitoring and alerting
  • react-server-components-framework
    - SSR/RSC for LCP optimization
  • frontend-ui-developer
    - Modern frontend patterns
  • accessibility-specialist
    - Performance intersects with a11y (skip links, focus management)
  • image-optimization
    - 全面的图片优化策略
  • observability-monitoring
    - 生产环境监控与告警
  • react-server-components-framework
    - SSR/RSC用于LCP优化
  • frontend-ui-developer
    - 现代前端开发模式
  • accessibility-specialist
    - 性能优化与无障碍设计的交集(跳转链接、焦点管理)

Capability Details

能力细节

lcp-optimization

lcp-optimization

Keywords: LCP, largest-contentful-paint, hero, preload, priority, SSR, TTFB Solves: Slow initial render, delayed hero content, poor Time to First Byte
关键词: LCP, largest-contentful-paint, hero, preload, priority, SSR, TTFB 解决问题: 初始渲染缓慢、首屏内容延迟、TTFB(首字节时间)过长

inp-optimization

inp-optimization

Keywords: INP, interaction, responsiveness, long-task, transition, yield, scheduler Solves: Slow button responses, janky scrolling, blocked main thread
关键词: INP, interaction, responsiveness, long-task, transition, yield, scheduler 解决问题: 按钮响应缓慢、滚动卡顿、主线程阻塞

cls-prevention

cls-prevention

Keywords: CLS, layout-shift, dimensions, aspect-ratio, font-display, skeleton Solves: Content jumping, image pop-in, font flash, ad insertion shifts
关键词: CLS, layout-shift, dimensions, aspect-ratio, font-display, skeleton 解决问题: 内容跳动、图片突然加载、字体闪烁、广告插入导致的布局偏移

rum-monitoring

rum-monitoring

Keywords: RUM, web-vitals, field-data, analytics, sendBeacon, percentile Solves: Understanding real user experience, identifying regressions, alerting
关键词: RUM, web-vitals, field-data, analytics, sendBeacon, percentile 解决问题: 理解真实用户体验、定位性能退化、告警触发

performance-budgets

performance-budgets

Keywords: budget, webpack, lighthouse-ci, bundle-size, threshold, regression Solves: Preventing performance degradation, enforcing standards, CI integration
关键词: budget, webpack, lighthouse-ci, bundle-size, threshold, regression 解决问题: 防止性能退化、执行标准规范、CI集成

2026-thresholds

参考资料

Keywords: 2026, stricter, LCP-2.0s, INP-150ms, CLS-0.08, future-proof Solves: Preparing for Google's stricter thresholds before they become ranking factors
  • references/rum-setup.md
    - 完整RUM实现指南
  • scripts/performance-monitoring.ts
    - 监控模板
  • checklists/cwv-checklist.md
    - 优化检查清单
  • examples/cwv-examples.md
    - 真实场景优化案例

References

  • references/rum-setup.md
    - Complete RUM implementation
  • scripts/performance-monitoring.ts
    - Monitoring template
  • checklists/cwv-checklist.md
    - Optimization checklist
  • examples/cwv-examples.md
    - Real-world optimization examples