普通视图

发现新文章,点击刷新页面。
昨天 — 2026年5月9日首页

耗时一个月,我把 Nuxt 首屏性能排障经验做成了一个 AI Skill

作者 东东同学
2026年5月9日 17:20

从「首屏白屏 30 秒」到「一句对话定位瓶颈」——把排障方法论编码为 AI Agent 可复用的技能包。

blog-cover.webp

背景:首屏慢得离谱,却无从下手

我们的项目是一个基于 Nuxt 3 的 SSR 应用,某天收到反馈:弱网环境下首屏白屏接近 30 秒,根组件的 onMountedwindow.load 之后很久才触发

第一反应当然是打开 Chrome DevTools 的 Performance 面板看火焰图。但问题来了:

  • Dev 模式下 Vite 的 transform 和 HMR runtime 会严重放大首屏体感,和线上差距巨大
  • preview 模式更接近生产,但每次要 build + preview 才能复测,迭代极慢
  • DevTools 的 Network 面板看单个请求还行,但要从几百个 chunk 和子资源里找出「谁拖慢了 onMounted」,完全靠肉眼排查
  • 每次排查都要手动数 TTFB、DCL、onMounted 之间的间隔,然后对照 Resource Timing 去猜瓶颈在哪一段

更痛苦的是,这些问题不是我一个人的问题 —— 团队里每个人遇到性能问题时都要重新走一遍这个排查流程。

核心痛点拆解

反复排查几轮后,我意识到问题其实可以拆成几个独立的维度:

1. 不知道瓶颈在服务端还是客户端

Nuxt SSR 的首屏链路分两段:

  • SSR 端(服务端):Nitro 中间件 → server 插件 → app.vue 服务端 setup → 页面 setup → 输出 HTML
  • CSR 端(浏览器):HTML 解析 → 客户端插件 → chunk 拉取与执行 → 路由 setup → 根 onMounted

TTFB 很短但整体慢?瓶颈在 CSR。TTFB 本身就长?瓶颈在 SSR。看似简单,但很多人第一步就判断错了方向。

2. 缺少采样边界

window.loadapp:mounted 看着差不多,但实际上「用户感觉页面可用了」的时机是 app.vueonMounted 全部执行完,而不是某个框架生命周期。没打 mark 的话,你甚至不知道这个「真正就绪」的时间点。

3. dev 和真实环境的巨大差异

dev 模式下 Vite 的 transform、HMR、sourcemap 带来巨大的额外开销。在 dev 下优化半天以为快了 2 秒,上 preview 发现只快了 200ms —— 因为 dev 的 2 秒瓶颈是 Vite 自身。

4. 排障知识无法传递

每次有新成员加入或开新项目,性能排障的方法论都要口头传授一遍。检查清单、脚本、经验全在脑子里,无法复用。

方案选型:为什么是 Skill 而不是文档或 CLI 工具?

既然要把排障流程固化下来,有几种选择:

方案 优点 缺点
写一篇 wiki 简单 没人看;每次要手动对着 wiki 操作
做一个 CLI 工具 可执行 只解决了采集,检查清单、阶段判断、结果解读还是靠人
做成 AI Agent Skill AI 引导执行全套流程 依赖 Agent 环境(Cursor/Claude Code)

最终选择了 Skill,理由:

  1. 排障不是一个纯自动化任务 — 它需要根据项目结构(monorepo?哪个子包?跑 dev 还是 preview?)做判断,这正是 AI Agent 擅长的
  2. Skill 包含「触发后检查清单」 — 不是跑一个命令就完了,而是严格保证每一步前提条件
  3. 输出格式标准化 — 每次 profile 结果以同样的 Markdown 结构呈现,方便团队对照历史数据
  4. 可分发、可安装 — 通过 npx skills add 一键安装,不依赖特定项目配置

Skill 的实现原理

整体架构

skills/nuxt-boot-timing/
├── SKILL.md                 # 技能入口:触发条件、执行流程、输出格式
├── references/
│   ├── boot-phases.md       # 冷启动阶段模型(SSR/CSR 各阶段拆解)
│   └── trigger-checklist.md # 触发后必做检查清单
└── scripts/
    ├── verify-env.mjs       # 环境校验(Node/Nuxt/Playwright 三重检查)
    ├── startup-resource-profile.mjs  # 核心:Playwright + CDP 采集脚本
    └── profile-cloudflare-startup.mjs # 一条龙:启动服务 + profile

阶段模型(SKILL.md 核心)

Skill 的核心是一张 SSR → CSR 阶段表,把 Nuxt 首屏链路拆成可独立度量的步骤:

SSR 端:

  1. Nitro / 服务端中间件
  2. Nuxt *.server 插件
  3. 根组件 app.vue 服务端 setup(含可能阻塞的顶层 await
  4. 页面 setup、@nuxt/content
  5. 输出 HTML

CSR 端:

  • 文档与入口脚本
  • 客户端插件链
  • 路由与壳、app:beforeMount
  • 根组件客户端 setup
  • 布局与首屏子树 chunk
  • 路由页 setup、根 onMounted、Suspense

有了这张表,每次排查就不再从零开始猜,而是对着阶段去定位。

采样机制:在根组件打入 mark

脚本要求必须在 app.vue 中插入一段代码:

// app.vue
onMounted(() => {
  // 放在最后一个 onMounted 里
  window.__TEENPATTI_APP_ROOT_MOUNTED__ = true
  performance.mark('teenpatti-app-root-mounted')
})

这个 mark 是整个 profile 的采样边界

  • 脚本启动 Playwright,打开页面
  • 轮询等待 window.__TEENPATTI_APP_ROOT_MOUNTED__ === true
  • page.evaluate 中采集 Navigation Timing + 所有 startTime ≤ mark.startTime 的 Resource Timing

这样做的好处:不等价于 window.load。在弱网 + 顶层 await 的场景下,onMounted 可能远晚于 load 事件。

环境校验脚本:三道防线

verify-env.mjs 在跑任何 profile 前做三层检查:

  1. Node 版本 — 必须 ≥ 18
  2. Nuxt 项目 — 从 cwd 向上遍历查找声明了 nuxtpackage.json(兼容 monorepo)
  3. Playwright 状态 — 从项目依赖、用户全局安装、本机可执行能力三个维度汇报

任何一项不满足,给出明确的修复指引,而不是让用户面对一个神秘的报错。

如何使用

安装

# 全局安装(推荐)
npx skills add vghub-official/nuxt-boot-perf-skills --skill nuxt-boot-timing -g -y

# 或在 Cursor 中手动复制
mkdir -p ~/.cursor/skills
cp -R skills/nuxt-boot-timing ~/.cursor/skills/nuxt-boot-timing

在 AI Agent 中使用(核心体验)

安装 Skill 后,你不需要手动做任何接入操作。在 Cursor 或 Claude Code 中直接对 Agent 说一句:

"首屏加载很慢,帮我排查一下"

Agent 会自动触发 nuxt-boot-timing 技能,按检查清单自动执行以下操作:

skill-workflow.webp

整个过程用户不需要手动操作脚本路径、不需要自己改 package.json,只需要在 Agent 引导下在终端执行 profile 命令。

报告格式长这样:

### 环境与前提
- 应用位置:<repo-root>/apps/web
- 运行方式:pnpm build && pnpm preview
- 约束:禁缓存 + DevTools Slow 4G

### 阶段判定
TTFB 仅 120ms,但 DCL → onMounted 间隔达 21s,瓶颈在 CSR 侧 chunk 拉取与执行。

### 证据摘要
| waitingMs | type | xfer    | url |
|-----------|------|---------|-----|
| 18500     | script | 2.3 MiB | /_nuxt/app.vue-abc123.js |
| 12400     | script | 1.1 MiB | /_nuxt/GameModal-def456.js |

### 建议下一步
1. 检查 app.vue 顶层 await 是否可改为 lazy
2. GameModal 改为动态导入,不阻塞首屏
3. preview 模式下复测确认

可配置的环境变量

这个 Skill 的脚本暴露了丰富的环境变量,覆盖各种排障场景:

变量 作用 默认值
STARTUP_PROFILE_URL 目标页面 URL http://localhost:3000/
STARTUP_PROFILE_USE_CACHE 是否使用 HTTP 缓存 禁用
STARTUP_PROFILE_NO_THROTTLE 不限速(本机带宽) 限速
STARTUP_PROFILE_THROTTLE_PRESET 限速预设 devtools-slow-4g
STARTUP_PROFILE_JSON 输出 JSON 便于 CI/jq 关闭
STARTUP_PROFILE_TOP 各排行榜条数 10
STARTUP_PROFILE_SETTLE_MS mounted 后额外等待 0

设计决策:为什么这样设计?

1. 脚本必须复制,不能现场生成

SKILL.md 明确规定:profile 脚本必须从技能目录直接复制,禁止 Agent 临时手写。这是为了防止 Agent「即兴发挥」写出不规范的脚本,导致采集口径漂移。

2. verify-env 不复制到业务仓库

校验脚本在技能目录保留单一副本,只在需要时通过绝对路径调用。因为它是排障工具的一部分,不是业务代码。

3. 不自动安装 Playwright 浏览器二进制

SKILL.md 要求 Agent 提示用户自行安装,而不是代劳。因为 npx playwright install chromium 会下载 ~150MB 的浏览器二进制,不应该在用户不知情的情况下执行。

4. Monorepo 兼容

脚本从 cwd 向上遍历找 package.json 中声明 nuxt 的目录,而不是写死路径。这在 monorepo(如 apps/webpackages/frontend)场景下至关重要。

实际效果

接入这个 Skill 后,团队的性能排障流程从:

"我看看 DevTools...这个 chunk 好像很大...TTFB 多少来着...等下我跑个 lighthouse..."

变成了:

Agent: "首屏瓶颈在 CSR 侧,根 onMounted 在 DCL 之后 21 秒。Top 3 拖慢项:app.vue chunk (2.3MiB, waiting 18.5s)、GameModal chunk (1.1MiB, waiting 12.4s)、@nuxt/content 文档数据 (waiting 8.2s)。建议:顶层 await 改 lazy、GameModal 异步加载。"

每次排查的时间从 30 分钟降到 3 分钟,而且复盘时有结构化的数据可对照。

开源地址

Skill 源码已开源在 GitHub:

github.com/vghub-offic…

安装只需一行:

npx skills add vghub-official/nuxt-boot-perf-skills --skill nuxt-boot-timing -g -y

如果你也在被 Nuxt 首屏性能问题困扰,欢迎试试,也欢迎提 PR 一起完善。


标签:Nuxt、性能优化、AI Agent、Skill、SSR、Playwright

❌
❌