mcpField
A
audit-performance

audit-performance

github.com/kensaurus开发941 次安装1 周前更新
代码仓库

关于 audit-performance

面向开发者和编码 Agent 的性能审计技能,要求先采集线上与实验室数据,再定位具体瓶颈;修复后重新测量验证。

主要功能

  1. 01采集线上与实验室性能数据
  2. 02检查 Core Web Vitals 和 p95
  3. 03定位前端与数据库瓶颈
  4. 04针对性修复并重新测量
  5. 05使用 Sentry、Lighthouse 和 EXPLAIN

适用场景

  1. 01排查 LCP、INP 或 CLS 不佳
  2. 02追踪慢接口和 N+1 查询
  3. 03审查打包体积、图片与加载行为

安装

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

$npx skills add https://github.com/kensaurus/skills --skill audit-performance

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

附带文件references/loading-priority-2026.md

详细介绍

性能审计技能

自由度:混合 — 优化什么 [高自由度]; 先测量 Sentry/vitals 和 EXPLAIN [低自由度 — 严格执行]。

审计并修复例外。 先测量,再优化。JS 载荷 → audit-bundle-size。即时导航 → enhance-web-instant-nav。性能拐点 → test-load。超时/重试 → audit-resilience。 修复时:只更改已测量的瓶颈,并重新测量;将注意到的其他慢路径记录为后续事项,而不是修复项。直接在现有文件中修改,不要重写。仅在仓库已有性能测试的位置添加性能测试,或在任务要求时添加;临时测量结果不要放入仓库。

推理方法

  1. 观察 — 引用 LCP/INP/CLS、p95 或 EXPLAIN / N+1 循环(file:line)
  2. 解读 — 用户是在等待字节、查询,还是主线程工作?
  3. 分类 — vital-miss / N+1 / missing-index / bundle / image / re-render
  4. 严重程度 — 在线路径上的 LCP/INP 未达标,或 p95 远高于目标 = 严重

示例

观察: Sentry p95 /users = 2.3s;处理程序先执行 findAll,再逐个用户执行 Order.findByUserId(app/api/users/route.ts:31)。 解读: N+1;该页面的 TTFB/LCP 会受影响。 分类: N+1。 严重程度: 严重(p95 远超 200ms)。 发现: /users | N+1 | 预加载订单 | 然后重新测量

步骤 0:收集性能数据 [低自由度 — 严格执行]

优化前先测量。从多个来源收集数据。

生产数据(Sentry)

如果已启用 Sentry 性能监控,请获取真实生产指标:

sentry:search_events
{
 "organizationSlug": "<ORG_SLUG>",
 "projectSlug": "<PROJECT_SLUG>",
 "regionUrl": "<REGION_URL>",
 "query": "slowest transactions by p95 duration in last 7 days",
 "limit": 20
}
sentry:search_events
{
 "organizationSlug": "<ORG_SLUG>",
 "projectSlug": "<PROJECT_SLUG>",
 "regionUrl": "<REGION_URL>",
 "query": "web vitals LCP INP CLS performance scores in last 7 days",
 "limit": 20
}

检查与性能相关的问题:

sentry:search_issues
{
 "organizationSlug": "<ORG_SLUG>",
 "projectSlugOrId": "<PROJECT_SLUG>",
 "regionUrl": "<REGION_URL>",
 "query": "performance issues slow queries N+1 in last 30 days",
 "limit": 20
}
调研当前基准

获取当前性能目标:

firecrawl:firecrawl_search
{
 "query": "web vitals thresholds good score <current year>",
 "limit": 3,
 "sources": [{ "type": "web" }]
}
实际用户数据(事实依据) [低自由度 — 严格执行]

实验室数据(Lighthouse)用于诊断;实际用户数据决定是否通过。

  1. 在 CrUX / PageSpeed Insights 的实际用户数据标签页中,查看来源和按流量排名前 5 的 URL — 记录 p75 LCP / INP / CLS(优先移动设备)。
  2. 在应用中使用 web-vitals/attribution(而非基础构建):它会指出 LCP 元素、INP 交互目标和导致 CLS 位移的节点。
  3. 使用 budget.json 运行 Lighthouse CI(参见 references/loading-priority-2026.md §预算)。PR 回归时应判定失败,而不是依据绝对分数判定。

修改代码前,先将这三项全部记录在报告的“生产指标”部分。

验证流程 [低自由度 — 严格执行]
# Field (source of truth): CrUX / PSI field tab → p75 LCP, INP, CLS
# Lab, repeatable, budgeted:
npx @lhci/cli autorun --config=lighthouserc.json
# Early Hints present?
curl -sv https://example.com/ 2>&1 | grep -E "< HTTP/.* 103"
# Speculation: DevTools → Application → Speculative loads
# bfcache:     DevTools → Application → Back/forward cache → Test
# LCP element: web-vitals/attribution → onLCP(({attribution}) => attribution.element)

性能目标

核心网页指标
指标良好需要改进差
LCP(最大内容绘制)<2.5s2.5-4s>4s
INP(下次绘制交互)<200ms200-500ms>500ms
CLS(累积布局偏移)<0.10.1-0.25>0.25
其他关键指标
  • TTFB:实际用户数据 p75 <800ms(Google 阈值);边缘节点目标 <200ms
  • FCP:<1.8s
  • 长任务:关键交互路径上没有超过 50ms 的任务(INP 驱动因素)
  • LCP 资源加载延迟:<100ms(从 TTFB 到开始请求 LCP 图片的间隔 — 通常表示“图片无法被发现 / 未预加载”)
  • bfcache:每个内容页面都符合使用条件(DevTools → Application → Back/forward cache)

前端性能审计 [高自由度]

包体积分析
npm run build -- --analyze # framework-specific
npx source-map-explorer 'dist/**/*.js'

检查清单:

  • 整体包体积 <200KB(gzip 压缩后)
  • 单个代码块均不超过 100KB(gzip 压缩后)
  • Tree shaking 正常工作(包中没有未使用的导出)
  • 重型库采用延迟加载(图表、编辑器、地图)
  • 不使用 moment.js(改用 date-fns 或 dayjs)
  • 不完整导入 lodash(改用 lodash-es 或按需导入)
代码拆分
  • 按路由拆分(每个页面加载自己的代码块)
  • 对重型组件进行组件级拆分(lazy() / dynamic())
  • 延迟加载首屏以下内容
图片优化
  • 使用 WebP/AVIF 格式(照片不使用 PNG/JPEG)
  • 图片尺寸合适(不要向移动设备提供 4K 图片)
  • 首屏以下的图片设置 loading="lazy"
  • 使用响应式 srcSet 适配不同屏幕尺寸
  • 使用图片 CDN(Cloudinary、imgix、Vercel Image Optimization)
加载优先级与推测加载 [应用方式的灵活度较高,LCP 规则的灵活度较低]

深入语法:references/loading-priority-2026.md。即时导航实现 → enhance-web-instant-nav。

LCP 元素(先通过 web-vitals/attribution 或 DevTools → Performance → LCP 找到它)

  • LCP 图片不能设置 loading="lazy"——对 LCP 图片使用懒加载会自动判定为严重问题
  • LCP 图片设置 fetchpriority="high"(Next.js:<Image priority />,还会注入预加载)
  • 可从 HTML 中发现 LCP 图片——不能是 CSS background-image、不能由客户端 JS 注入,也不能位于 hydration 边界之后
  • 当 LCP 图片是响应式图片且框架尚未对其预加载时,使用 <link rel="preload" as="image" imagesrcset imagesizes>
  • 非关键图片:loading="lazy" decoding="async";<picture> 按 AVIF → WebP → JPEG 回退

字体

  • 设置 font-display: swap(品牌文本以外的文本可设为 optional);设置回退字体度量值(size-adjust、ascent-override),确保切换不会造成 CLS——next/font 会自动处理,请确认实际使用了它
  • 仅预加载首屏所需字重;按实际使用的 Unicode 范围进行子集化;自行托管

Early Hints (103)

  • 源站/CDN 发送 103 Early Hints,其中包含图片/字体 CDN 的 preconnect,以及 LCP 图片和关键 CSS 的 preload。验证:curl -sv https://site/ 2>&1 | grep -i "< HTTP/.* 103"
  • Node:res.writeEarlyHints({ link: [...] });否则在 CDN 配置

Speculation Rules(提升后续导航速度)

  • 存在 <script type="speculationrules">,其中为站内链接设置了积极度为 moderate 的 prefetch 文档规则
  • 仅对高概率访问的后续页面(分页、“下一篇文章”、产品详情)使用 prerender;积极度设为 moderate/conservative
  • 排除推测加载的内容:注销、购物车/结账、身份验证、任何具有 GET 副作用的内容,以及包含 ? 的 URL(除非设置了 expects_no_vary_search)
  • 存在 Sec-Purpose: prefetch;prerender 时,服务端忽略副作用;通过 document.prerendering + prerenderingchange 阻止分析/广告脚本运行
  • 完整实现 → enhance-web-instant-nav

bfcache

  • 不使用 unload 监听器(改用 pagehide);HTML 不设置 Cache-Control: no-store;在 pagehide 时没有打开的 WebSocket / IndexedDB 事务
  • 在 DevTools → Application → Back/forward cache → “Test back/forward cache” 中验证显示绿色

渲染策略(Next.js 16 / React 19)

  • 默认使用 Server Components;仅在交互组件叶节点使用 'use client'
  • 将较慢的数据包在 <Suspense> 中,以便外壳流式传输;使用静态外壳 + 动态空缺(cacheComponents / 'use cache'),而不是阻塞整个路由
  • 先启用 React Compiler(reactCompiler: true),再使用任何手动 memo/useMemo/useCallback——仅在 Profiler 证明 Compiler 遗漏时才手动记忆化

INP(响应能力)

  • 点击/按键路径上没有超过 50ms 的任务;使用 scheduler.yield()(回退方案为 setTimeout(0))或 startTransition 分割工作
  • 输入处理程序中没有同步布局读取(offsetHeight、getBoundingClientRect)
  • 在较长的屏幕外区块上使用 content-visibility: auto + contain-intrinsic-size
  • 第三方脚本:使用 async/defer、Next <Script strategy="lazyOnload">(或在安全情况下通过 Partytown 使用 "worker")、经同意后加载;嵌入内容使用外观替代方案(lite-youtube、静态地图图片 → 点击后加载 iframe)

CLS

  • 每个 <img>、<video>、<iframe> 和广告位都设置 width/height 或 aspect-ratio
  • 为延迟出现的 UI(横幅、提示消息、Cookie 栏)预留空间或将其覆盖显示——绝不推挤内容
React 性能(如适用)
  • 已启用 React Compiler;只有 Profiler 证据表明有必要时才手动使用 memo(参见 Loading Priority & Speculation)
  • 没有不必要的重新渲染(React DevTools Profiler)
  • 长列表已虚拟化(react-window、@tanstack/react-virtual)
  • JSX 属性中没有内联创建对象或数组
  • Context 提供程序的作用域足够窄(局部状态不会导致整个应用都被包裹)
CSS 性能
  • 没有布局抖动(先读取,再写入)
  • 动画使用 transform/opacity(由 GPU 加速)
  • 没有 @import 链(改用打包)
  • 首屏内容所需的关键 CSS 已内联
  • 字体已精简并预加载(<link rel="preload">)

后端性能审计 [高自由度]

数据库查询

N+1 检测:

// BAD: N+1
const users = await User.findAll();
for (const user of users) {
 const orders = await Order.findByUserId(user.id);
}

// GOOD: eager loading
const users = await User.findAll({ include: [Order] });

检查清单:

  • 已消除 N+1 查询(预加载、联接、批量加载)
  • 为频繁查询的列建立了索引(WHERE、JOIN、ORDER BY)
  • 只 SELECT 所需列(不使用 SELECT *)
  • 所有列表端点都已分页
  • 已配置连接池
  • 已启用慢查询日志

索引验证: [低自由度 — 按原样运行]

EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = '123';
API 响应优化
  • 已启用响应压缩(gzip/brotli)
  • 已设置适当的缓存标头(Cache-Control、ETag)
  • 没有过度获取(只返回客户端需要的内容)
  • 列表端点已分页
  • p95 响应时间 <200ms
缓存策略
层工具TTL用途
浏览器Cache-Control 标头不固定静态资源、API 响应
CDNVercel/CloudFront/Cloudflare1h-1d静态页面、图片
APIRedis/Memcached5m-1h开销较大的查询、计算数据
ORM查询缓存1m-5m重复的相同查询
网络优化
  • 静态资源使用 CDN
  • 已启用 HTTP/2 或 HTTP/3
  • 对关键来源预连接(<link rel="preconnect">)
  • 对第三方域名进行 DNS 预取
  • 尽可能批量发送 API 调用(GraphQL、DataLoader)

基于研究的优化 [高自由度]

针对具体的性能瓶颈,研究当前的解决方案:

firecrawl:firecrawl_search
{
 "query": "<framework> <specific bottleneck> performance optimization <current year>",
 "limit": 5,
 "sources": [{ "type": "web" }]
}

然后深入阅读最佳结果:

firecrawl:firecrawl_scrape
{
 "url": "<best-result-url>",
 "formats": ["markdown"],
 "onlyMainContent": true
}

通过 Context7 查阅框架官方文档:

context7:resolve-library-id
{
 "libraryName": "<framework>",
 "query": "performance optimization"
}

快速见效的优化(影响最大、工作量最小)

问题解决方案影响
LCP 图片使用了 lazy 加载/未设置优先级使用 fetchpriority="high" 并预加载;移除 LCP 图片上的 loading="lazy"高(LCP 缩短 −300 至 −800 ms)
第一个页面快、第二个页面慢使用 Speculation Rules prefetch(中等)高(下一次导航的 TTFB ≈ 0)
到处手动使用 memo启用 React Compiler高(INP;删除多余的 memo)
包体积过大拆分路由代码,延迟加载大型库高
图片加载慢使用 WebP、延迟加载和响应式图片高
没有缓存添加 Cache-Control 标头高
N+1 查询预加载/批量加载高
缺少索引添加数据库索引高
字体未优化精简字体 + 预加载 + font-display:swap中
没有压缩启用 gzip/brotli中
重新渲染开销大优先使用 React Compiler;只有 Profiler 证据表明有必要时才手动使用 memo中
布局偏移为图片/嵌入内容设置明确的宽度和高度中

应用修复前的自我审查 [低自由度 — 不要跳过]

  1. 有证据 — 有测得的 Web Vital、p95 或 EXPLAIN,而非“看起来很慢”
  2. 可复现 — 同一路径仍超过目标表中的阈值
  3. 严重程度合理 — Critical = Web Vital 较差或线上路径的 p95 较高
  4. 责任归属正确 — JS 负载 → audit-bundle-size;性能拐点 → test-load;超时 → audit-resilience
  5. 不产生虚假的安全感 — 没有测得的重新渲染时,不使用 memo/useMemo;修复后再次测量
  6. 明确标出 LCP — 在建议修复图片前,证明哪个元素是 LCP(没有归因数据 = 猜测,而非审计)

输出:性能审计报告

## Performance Audit: [Project Name]

### Production Metrics (from Sentry)
- LCP: [value] — [good/needs work/poor]
- INP: [value] — [good/needs work/poor]
- CLS: [value] — [good/needs work/poor]
- Slowest transactions: [list with p95 times]

### Critical Issues (fix immediately)
| # | Area | Issue | Impact | Fix |
|---|------|-------|--------|-----|
| 1 | DB | N+1 on /users endpoint | p95 = 2.3s | Eager load orders relation |

### Optimizations (prioritized)
| # | Area | Issue | Impact | Effort | Fix |
|---|------|-------|--------|--------|-----|
| 1 | FE | Bundle 450KB gzipped | High | Low | Code split routes |

### Already Optimized
- [list of performance areas that are well-implemented]

### Monitoring Recommendations
- [what to add to track performance over time]

### Research Sources
- [URL] — [what optimization pattern it provided]

评论

支持 Markdown