关于 audit-performance
面向开发者和编码 Agent 的性能审计技能,要求先采集线上与实验室数据,再定位具体瓶颈;修复后重新测量验证。
主要功能
- 01采集线上与实验室性能数据
- 02检查 Core Web Vitals 和 p95
- 03定位前端与数据库瓶颈
- 04针对性修复并重新测量
- 05使用 Sentry、Lighthouse 和 EXPLAIN
适用场景
- 01排查 LCP、INP 或 CLS 不佳
- 02追踪慢接口和 N+1 查询
- 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。 修复时:只更改已测量的瓶颈,并重新测量;将注意到的其他慢路径记录为后续事项,而不是修复项。直接在现有文件中修改,不要重写。仅在仓库已有性能测试的位置添加性能测试,或在任务要求时添加;临时测量结果不要放入仓库。
推理方法
- 观察 — 引用 LCP/INP/CLS、p95 或
EXPLAIN/ N+1 循环(file:line) - 解读 — 用户是在等待字节、查询,还是主线程工作?
- 分类 — vital-miss / N+1 / missing-index / bundle / image / re-render
- 严重程度 — 在线路径上的 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)用于诊断;实际用户数据决定是否通过。
- 在 CrUX / PageSpeed Insights 的实际用户数据标签页中,查看来源和按流量排名前 5 的 URL — 记录 p75 LCP / INP / CLS(优先移动设备)。
- 在应用中使用
web-vitals/attribution(而非基础构建):它会指出 LCP 元素、INP 交互目标和导致 CLS 位移的节点。 - 使用
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.5s | 2.5-4s | >4s |
| INP(下次绘制交互) | <200ms | 200-500ms | >500ms |
| CLS(累积布局偏移) | <0.1 | 0.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 响应 |
| CDN | Vercel/CloudFront/Cloudflare | 1h-1d | 静态页面、图片 |
| API | Redis/Memcached | 5m-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 | 中 |
| 布局偏移 | 为图片/嵌入内容设置明确的宽度和高度 | 中 |
应用修复前的自我审查 [低自由度 — 不要跳过]
- 有证据 — 有测得的 Web Vital、p95 或
EXPLAIN,而非“看起来很慢” - 可复现 — 同一路径仍超过目标表中的阈值
- 严重程度合理 — Critical = Web Vital 较差或线上路径的 p95 较高
- 责任归属正确 — JS 负载 →
audit-bundle-size;性能拐点 →test-load;超时 →audit-resilience - 不产生虚假的安全感 — 没有测得的重新渲染时,不使用
memo/useMemo;修复后再次测量 - 明确标出 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]