前端性能优化不能只靠感觉,必须用数据说话。FCP(First Contentful Paint,首次内容绘制)、LCP(Largest Contentful Paint,最大内容绘制)和 TTFB(Time To First Byte,首字节时间)是 Web Vitals 体系中衡量加载体验最核心的三个指标,它们分别对应了网络响应、首次渲染和主要内容呈现三个阶段。对于 Vue 3 这样的单页应用来说,由于路由切换不会触发完整的页面加载,采集这些指标会遇到不少特殊问题,本文将完整讲解采集与分析的思路。

一、三大指标的含义与判定标准
TTFB 指的是从用户发起请求到收到服务器返回第一个字节的时间,它综合反映了 DNS 解析、TCP 连接、TLS 握手以及服务端处理的耗时。一般来说 TTFB 低于 800 毫秒算良好,超过 1800 毫秒就需要警惕,常见原因是服务端响应慢或重定向链路过长。
FCP 指的是浏览器渲染出第一块内容(文本、图片、非空白 canvas 等)的时间点,反映用户什么时候从白屏变为看到东西。良好的标准是 1.8 秒以内。在 Vue 3 中,由于应用是靠 JS 挂载到 #app 容器上,如果打包体积过大或脚本阻塞,FCP 会被明显推迟。
LCP 反映的是视口内最大的内容元素(通常是首屏大图或标题文本)完成渲染的时间,良好标准是 2.5 秒以内。LCP 的特殊之处在于它是一个持续更新的指标,浏览器会记录所有候选元素中渲染完成最晚的那个,直到用户发生首次交互为止。
二、在 Vue 3 中采集三大指标
最推荐的方案是使用浏览器原生的 PerformanceObserver 接口,无需引入第三方库即可拿到高精度的指标数据。下面是一个可以直接放进 Vue 3 项目入口文件的采集模块:
import { performance } from 'node:perf_hooks' // 浏览器环境直接用全局 performance
// 采集 TTFB
function getTTFB() {
const nav = performance.getEntriesByType('navigation')[0]
if (nav) {
const ttfb = nav.responseStart - nav.requestStart
report({ metric: 'TTFB', value: ttfb })
}
}
// 采集 FCP
function observeFCP() {
new PerformanceObserver((list) => {
const entries = list.getEntries()
const fcp = entries.find(e => e.name === 'first-contentful-paint')
if (fcp) report({ metric: 'FCP', value: fcp.startTime })
}).observe({ type: 'paint', buffered: true })
}
// 采集 LCP
function observeLCP() {
let lastValue = 0
const po = new PerformanceObserver((list) => {
const entry = list.getEntries().pop()
lastValue = entry.startTime
})
po.observe({ type: 'largest-contentful-paint', buffered: true })
// 用户首次交互后停止更新,此时上报最终值
['keydown', 'click', 'scroll'].forEach(evt => {
window.addEventListener(evt, () => {
po.disconnect()
report({ metric: 'LCP', value: lastValue })
}, { once: true, passive: true })
})
}
getTTFB()
observeFCP()
observeLCP()需要注意 buffered: true 这个选项,它允许读取在观察者注册之前已经产生的条目,这对 Vue 3 尤其重要,因为应用初始化代码可能在指标产生之后才执行。如果目标浏览器较旧,也可以直接使用 Google 官方的 web-vitals 库,它封装了上述逻辑并处理了各类浏览器兼容问题。
上报时要考虑使用 navigator.sendBeacon 或 fetch 的 keepalive 选项,避免页面卸载时请求被浏览器取消。同时在数据中附带路由地址、设备类型、网络类型等维度信息,便于后续分组分析。
三、单页应用场景下的归因与优化
Vue 3 是单页应用,路由切换不会产生新的 navigation 条目,因此 TTFB 和 FCP 通常只在首次进入时有效。针对路由切换后的渲染性能,需要换一种采集方式:监听 onMounted 钩子配合路由开始时间来计算组件级耗时:
import { useRouter } from 'vue-router'
router.beforeEach((to, from, next) => {
to.meta.start = performance.now()
next()
})
// 在路由视图的根组件中
onMounted(() => {
const start = route.meta.start
if (start) {
report({
metric: 'route-render',
route: route.path,
value: performance.now() - start
})
}
})拿到数据之后,分析比采集更重要。如果 TTFB 偏高,优先排查服务端接口耗时、CDN 配置和重定向次数;如果 FCP 偏高,通常是打包产物阻塞渲染,可以通过路由懒加载、代码分割、给脚本加 defer、内联关键 CSS 来改善;如果 LCP 偏高,常见原因是首屏大图没有优化,应当使用 <img> 的 loading="eager" 加 fetchpriority="high"、提前预加载关键图片,或者将服务端渲染(SSR)纳入方案,让首屏内容由 HTML 直接输出。
此外,LCP 的 PerformanceObserver 条目对象上带有 element 属性,可以直接定位是哪个元素成为了最大内容元素,把它一并上报,能让排查效率大幅提升。结合 Vue 3 的 defineAsyncComponent 和 Suspense 特性,还可以进一步细分首屏渲染中每个异步块的时间占比,形成完整的性能画像。
四、构建持续监控的数据看板
一次性的采集意义有限,持续监控才能发现性能劣化的趋势。上报到的数据建议按 PV 的 75 分位数(P75)来统计,而不是简单求平均,因为平均值容易被少量极端慢的样本拉偏,而 Google 的评分标准正是基于 P75 制定的。
数据看板建议按以下维度切分:浏览器版本、设备档次(可通过 navigator.hardwareConcurrency 和内存大小粗略判断)、地域与运营商、页面路由。通过对比不同维度的指标差异,往往能定位到优化收益最大的方向,例如发现低端安卓机的 LCP 普遍超标,就应优先压缩首屏图片和 JS 体积。
最后不要忘记把性能监控接入 CI 流程,在每次发版前用 Lighthouse 或 Puppeteer 跑一遍关键页面,将 FCP、LCP、TTFB 的基线数据与线上真实数据对照,形成开发环境预判加线上验证的闭环。这样一套体系搭建完成后,Vue 3 项目的性能状况就不再是黑盒,每一次优化都能用数据验证效果。
Vue 3性能监控Web Vitals修改时间:2026-08-31 01:32:42