core-web-vitals
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCore 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版)
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| 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年第四季度推行)
| Metric | Current Good | 2026 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
调试工具
| Tool | Use Case |
|---|---|
| Chrome DevTools Performance | Identify long tasks, layout shifts |
| Lighthouse | Lab data, recommendations |
| PageSpeed Insights | Field data + lab data |
| Web Vitals Extension | Real-time vitals overlay |
| Chrome UX Report | Real 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
关键决策
| Decision | Option A | Option B | Recommendation |
|---|---|---|---|
| LCP content rendering | Client-side | SSR/SSG | SSR/SSG - Critical content must be in initial HTML |
| Image format | JPEG/PNG | WebP/AVIF | WebP (AVIF for modern browsers) - 25-50% smaller |
| Font loading | swap | optional | optional for non-critical, swap with fallback metrics |
| INP optimization | Debounce | useTransition | useTransition - React 18+ native, better UX |
| Monitoring | Lab only | Lab + Field | Lab + Field - Real user data is ground truth |
| Performance budget | Soft warning | Hard fail | Hard fail in CI - Prevents regression |
| 决策项 | 选项A | 选项B | 推荐方案 |
|---|---|---|---|
| LCP内容渲染方式 | 客户端渲染 | SSR/SSG | SSR/SSG - 核心内容必须包含在初始HTML中 |
| 图片格式 | JPEG/PNG | WebP/AVIF | WebP(现代浏览器推荐AVIF)- 体积减小25-50% |
| 字体加载策略 | swap | optional | 非关键字体用optional,关键字体用swap并配合回退指标 |
| INP优化方式 | 防抖 | useTransition | useTransition - 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
相关技能
- - Comprehensive image optimization strategies
image-optimization - - Production monitoring and alerting
observability-monitoring - - SSR/RSC for LCP optimization
react-server-components-framework - - Modern frontend patterns
frontend-ui-developer - - Performance intersects with a11y (skip links, focus management)
accessibility-specialist
- - 全面的图片优化策略
image-optimization - - 生产环境监控与告警
observability-monitoring - - SSR/RSC用于LCP优化
react-server-components-framework - - 现代前端开发模式
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
- - 完整RUM实现指南
references/rum-setup.md - - 监控模板
scripts/performance-monitoring.ts - - 优化检查清单
checklists/cwv-checklist.md - - 真实场景优化案例
examples/cwv-examples.md
References
—
- - Complete RUM implementation
references/rum-setup.md - - Monitoring template
scripts/performance-monitoring.ts - - Optimization checklist
checklists/cwv-checklist.md - - Real-world optimization examples
examples/cwv-examples.md
—