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

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

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

我们的项目是一个基于 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 的实现原理

整体架构

bash 复制代码
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 中插入一段代码:

ts 复制代码
// 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 状态 --- 从项目依赖、用户全局安装、本机可执行能力三个维度汇报

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

如何使用

安装

bash 复制代码
# 全局安装(推荐)
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 技能,按检查清单自动执行以下操作:

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

报告格式长这样:

markdown 复制代码
### 环境与前提
- 应用位置:<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...

安装只需一行:

bash 复制代码
npx skills add vghub-official/nuxt-boot-perf-skills --skill nuxt-boot-timing -g -y

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


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

相关推荐
mldong2 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排3 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
linqiw3 小时前
Embabel 上手:Java Agent 是怎么干活的
agent
passerby60613 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰5 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
大模型真好玩5 小时前
DeepSeek Harness 入门很简单(三)——DeepSeek Harness接入工具、MCP、Skill
人工智能·agent·deepseek
mayaairi6 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js