mcpField
P
performance

performance

github.com/addyosmani开发3.0k41.7k 次安装9 周前更新
代码仓库

关于 performance

面向开发者的性能优化技能,使用真实用户数据确定优先级,并通过浏览器跟踪诊断原因。它强调修改前后测量,并将静态检查结果标为假设。

主要功能

  1. 01现场与实验室性能基线
  2. 02Core Web Vitals 优先级判断
  3. 03基于跟踪的瓶颈诊断
  4. 04加载与运行时优化指导
  5. 05修改前后测量流程

适用场景

  1. 01查找 Core Web Vitals 不佳的原因
  2. 02对照基线验证性能改动
  3. 03在没有可运行页面时审查项目

MCP 相关

在可用 Chrome DevTools MCP 时,流程使用 `performance_start_trace` 和 `performance_analyze_insight` 记录并分析性能跟踪。

安装

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

$npx skills add https://github.com/addyosmani/web-quality-skills --skill performance

安装后在 Claude Code 中输入/performance确认已加载。

附带文件references/MEASUREMENT.mdreferences/RUM.md

详细介绍

性能优化

基于真实用户信号确定优化优先级,并使用浏览器跟踪记录进行诊断,以证据为依据优化性能。重点关注加载速度、运行时响应能力和资源交付。

工作原理

  1. 如果页面可以运行,请先阅读测量工作流,在修改前建立线上与实验室测量基线。
  2. 优先处理真实用户核心网页指标表现不佳的问题。使用 DevTools 性能跟踪记录及其针对性洞察查找原因。
  3. 仅检查和修改与已测量瓶颈相关的代码或资源。
  4. 重新运行条件等效的实验室测量,并报告优化前后的数值、测量条件和不确定性。线上验证仍需等待,直至收集到足够的新用户数据。

如果页面无法运行,请进行静态检查,但将发现称为假设,而非已测得的回归。为每个高影响假设提供可用于验证的命令或浏览器工作流。

优先使用能够记录性能跟踪记录并提供针对性洞察的浏览器工具。使用 Chrome DevTools MCP 时,请调用 performance_start_trace 和 performance_analyze_insight;不要通过 lighthouse_audit 进行性能分析,因为它涵盖的是 Lighthouse 中非性能类的类别。

初始性能预算

预算应反映产品的目标设备、网络、页面类型和用户旅程。以下数值是适用于典型内容页或电商页面的初始指导值,并非通用的通过/不通过标准。如果项目已有预算,请予以保留。

资源预算理由
页面总大小< 1.5 MB限制受限目标网络上的传输时间和数据成本;应使用有代表性的页面进行校准
JavaScript(压缩后)< 300 KB控制解析和执行成本
CSS(压缩后)< 100 KB限制阻塞渲染的工作
图片(首屏区域)< 500 KB保障可能成为 LCP 资源的图片
字体< 100 KB限制关键字体传输量
第三方资源< 200 KB限制产品控制范围之外的代码

关键渲染路径

服务器响应
  • TTFB < 800ms。 首字节时间应尽可能短。使用 CDN、缓存和高效的后端。
  • 启用压缩。 对文本资源使用 Gzip 或 Brotli。优先使用 Brotli(体积小 15–20%)。
  • 使用 HTTP/2 或 HTTP/3。 多路复用可减少连接开销。
  • 边缘缓存。 尽可能在 CDN 边缘缓存 HTML。
  • 针对已测量的文档延迟,考虑使用 Early Hints(HTTP 103)。 如果跟踪记录显示 HTML 生成缓慢,而关键子资源稳定,则在同一请求的正常最终响应之前,发送包含 Link 标头的临时 103 响应。请使用 HTTP/2 或更高版本。CDN 可以根据较早的 200 响应中的 Link 标头合成 103 响应;来源站点或边缘处理程序也可以直接发送。对于不支持的客户端,服务器会继续发送最终响应,但请确认当前浏览器和基础设施的支持情况。提示仅限于已确认关键的预加载或预连接:不准确的提示会浪费带宽。Cloudflare 报告称,在一项人工构建的图片密集型测试中,LCP 提升了 20–30%;应将其视为供应商案例研究,而非预期收益,并测量实际结果。请参阅 MDN 的 103 实现示例和 Cloudflare 的研究。
资源加载

预连接到所需来源:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>

预加载关键资源:

仅预加载那些延迟发现会在跟踪记录中显现出来的资源。每次预加载都会占用带宽,不必要的高优先级请求可能会延迟 LCP。


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


<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>

使用 Speculation Rules API 预渲染可能的后续导航:

<script type="speculationrules">
{
  "prerender": [{
    "where": { "href_matches": "/*" },
    "eagerness": "moderate"
  }]
}
</script>

moderate 等待比 eager 模式更强的意图信号。衡量预测命中率、传输字节数和服务器成本;错误的预渲染大致相当于一次未使用的导航。有关权衡以及分析所需的 prerenderingchange 门控,请参阅 core-web-vitals → LCP。

延迟加载非关键 CSS:


<style>/* Above-fold styles */</style>


<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
JavaScript 优化

延迟加载非必需脚本:


<script src="/critical.js"></script>


<script defer src="/app.js"></script>


<script async src="/analytics.js"></script>


<script type="module" src="/app.mjs"></script>

代码拆分模式:

// Route-based splitting
const Dashboard = lazy(() => import('./Dashboard'));

// Component-based splitting
const HeavyChart = lazy(() => import('./HeavyChart'));

// Feature-based splitting
if (user.isPremium) {
  const PremiumFeatures = await import('./PremiumFeatures');
}

Tree shaking 最佳实践:

// ❌ Imports entire library
import _ from 'lodash';
_.debounce(fn, 300);

// ✅ Imports only what's needed
import debounce from 'lodash/debounce';
debounce(fn, 300);

图片优化

格式选择
格式用例浏览器支持
AVIF照片,压缩效果最佳92%+
WebP照片,良好的后备格式97%+
PNG带透明效果的图形通用
SVG图标、徽标、插图通用
响应式图片
<picture>
  
  <source 
    type="image/avif"
    srcset="hero-400.avif 400w,
            hero-800.avif 800w,
            hero-1200.avif 1200w"
    sizes="(max-width: 600px) 100vw, 50vw">
  
  
  <source 
    type="image/webp"
    srcset="hero-400.webp 400w,
            hero-800.webp 800w,
            hero-1200.webp 1200w"
    sizes="(max-width: 600px) 100vw, 50vw">
  
  
  <img 
    src="hero-800.jpg"
    srcset="hero-400.jpg 400w,
            hero-800.jpg 800w,
            hero-1200.jpg 1200w"
    sizes="(max-width: 600px) 100vw, 50vw"
    width="1200" 
    height="600"
    alt="Hero image"
    loading="lazy"
    decoding="async">
</picture>
LCP 图片优先级

<img 
  src="hero.webp" 
  fetchpriority="high"
  loading="eager"
  decoding="sync"
  alt="Hero">


<img 
  src="product.webp" 
  loading="lazy"
  decoding="async"
  alt="Product">

字体优化

加载策略
/* System font stack as fallback */
body {
  font-family: 'Custom Font', -apple-system, BlinkMacSystemFont, 
               'Segoe UI', Roboto, sans-serif;
}

/* Prevent invisible text */
@font-face {
  font-family: 'Custom Font';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap; /* or optional for non-critical */
  font-weight: 400;
  font-style: normal;
  unicode-range: U+0000-00FF; /* Subset to Latin */
}
预加载关键字体
<link rel="preload" href="/fonts/heading.woff2" as="font" type="font/woff2" crossorigin>
可变字体
/* One file instead of multiple weights */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Variable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}

缓存策略

Cache-Control 标头
# HTML (short or no cache)
Cache-Control: no-cache, must-revalidate

# Static assets with hash (immutable)
Cache-Control: public, max-age=31536000, immutable

# Static assets without hash
Cache-Control: public, max-age=86400, stale-while-revalidate=604800

# API responses
Cache-Control: private, max-age=0, must-revalidate
Service Worker 缓存
// Cache-first for static assets
self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image' ||
      event.request.destination === 'style' ||
      event.request.destination === 'script') {
    event.respondWith(
      caches.match(event.request).then((cached) => {
        return cached || fetch(event.request).then((response) => {
          const clone = response.clone();
          caches.open('static-v1').then((cache) => cache.put(event.request, clone));
          return response;
        });
      })
    );
  }
});

运行时性能

避免布局抖动
// ❌ Forces multiple reflows
elements.forEach(el => {
  const height = el.offsetHeight; // Read
  el.style.height = height + 10 + 'px'; // Write
});

// ✅ Batch reads, then batch writes
const heights = elements.map(el => el.offsetHeight); // All reads
elements.forEach((el, i) => {
  el.style.height = heights[i] + 10 + 'px'; // All writes
});
对开销大的操作进行防抖
function debounce(fn, delay) {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn(...args), delay);
  };
}

// Debounce scroll/resize handlers
window.addEventListener('scroll', debounce(handleScroll, 100));
使用 requestAnimationFrame
// ❌ May cause jank
setInterval(animate, 16);

// ✅ Synced with display refresh
function animate() {
  // Animation logic
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
虚拟化长列表
// For lists > 100 items, render only visible items
// Use libraries like react-window, vue-virtual-scroller, or native CSS:
.virtual-list {
  content-visibility: auto;
  contain-intrinsic-size: 0 50px; /* Estimated item height */
}
使用 View Transitions 实现流畅导航

View Transitions API 让浏览器使用单个 GPU 合成快照,在两个 DOM 状态之间交叉淡入淡出(或自定义动画)——不会重复渲染,不会造成布局抖动,并且快照不会计入 CLS。

同文档(SPA 风格)——2026 年基线:

// Wrap the DOM mutation that swaps the view
function navigate(newView) {
  if (!document.startViewTransition) return swapDOM(newView);
  document.startViewTransition(() => swapDOM(newView));
}

跨文档(MPA 风格)——Chromium 稳定版支持,其他浏览器渐进增强:

/* On both source and destination pages */
@view-transition { navigation: auto; }

这就是完整的集成——现在同源导航会自动淡入淡出。若要让特定元素参与共享元素过渡(例如缩略图展开为主图),请为它们设置相同的 view-transition-name:

.product-thumb[data-id="42"], .product-hero { view-transition-name: product-42; }

将其与上文的 Speculation Rules 配合使用,即可实现即时且带动画的导航。

第三方脚本

加载策略
// ❌ Blocks main thread
<script src="https://analytics.example.com/script.js"></script>

// ✅ Async loading
<script async src="https://analytics.example.com/script.js"></script>

// ✅ Delay until interaction
<script>
document.addEventListener('DOMContentLoaded', () => {
  const observer = new IntersectionObserver((entries) => {
    if (entries[0].isIntersecting) {
      const script = document.createElement('script');
      script.src = 'https://widget.example.com/embed.js';
      document.body.appendChild(script);
      observer.disconnect();
    }
  });
  observer.observe(document.querySelector('#widget-container'));
});
</script>
外观代理模式

<div class="youtube-facade" 
     data-video-id="abc123" 
     onclick="loadYouTube(this)">
  <img src="/thumbnails/abc123.jpg" alt="Video title">
  <button aria-label="Play video">▶</button>
</div>

测量

只要 URL 可运行,就使用测量工作流。该工作流定义了 Chrome DevTools MCP 路由、CrUX 和备用数据源、可重复的实验室条件,以及精简的证据格式。

指标类型说明
p75 的 LCP、INP、CLS实际用户数据反映用户体验结果的 Core Web Vitals;用于通过/失败优先级排序
跟踪记录中的 LCP、CLS实验室针对单次导航的可重复诊断值
TBT实验室主线程阻塞诊断指标,也是 INP 的粗略代理指标,并非实际用户 INP
FCP、Speed Index实验室加载诊断指标,并非 Core Web Vitals

原始 PerformanceObserver 代码片段可用于当前浏览器会话,但本身并非真实用户数据。当用户需要生产环境遥测时,请阅读第一方 RUM 参考文档,并优先使用 web-vitals,而不是手工编写指标实现。

参考资料

有关 Core Web Vitals 专项优化,请参阅 Core Web Vitals。

评论

支持 Markdown