性能指标
注意首屏加载的时间,是用户首次进入的页面,而不一定是首页
FCP和LCP优化手段其实都是差不多
-
TTFB(服务器首字节时间):加载第一个字节所需时间 (衡量请求资源到响应第一个字节)
- 目标通常是 不超过 0.8 秒
-
FCP(首次内容绘制):用户第一次看到内容的时间。越小越好。
- 目标通常是 不超过 1.8s
-
LCP(最大内容绘制):主要内容出现时间。
- 目标通常是 不超过 2.5s
- 顶级网站:0.8s – 1.5s
-
CLS(布局偏移):页面抖动程度。
- 目标通常是 不超过 0.1s
-
TBT(总阻塞时间): 从页面首次开始渲染内容到用户可交互之间,被阻塞时间的总和
- 目标通常是 不超过 200ms
1. TTFB(服务器首字节时间)
- 分析手段:DevTools Network 看主文档 Waiting (TTFB);看是否命中 CDN;看后端 APM 慢查询和接口耗时分布。
- 常见原因:后端计算慢、数据库慢、未缓存、跨区域回源。
- 优化方案:页面/接口缓存(CDN+边缘缓存)、数据库索引和查询优化、SSR 结果缓存、减少中间层串行调用。
- Network -> Timing -> Waiting for server response -> 这是TTFB 核心值
分析
-
需要勾选 Disable cache,避免本地缓存干扰
-
Network -> Timing 面板
-
重点字段:Waiting for server response,这就是 TTFB 的核心等待段。
2. FCP(首次内容绘制)
- 分析手段:Lighthouse FCP + Performance 面板首屏渲染时间线;检查首屏 CSS/字体/JS 阻塞。
- 常见原因:关键 CSS 被阻塞、字体加载晚、首屏 JS 太重。
- 优化方案:关键 CSS 内联、非关键 CSS 延迟、font-display: swap、首屏脚本拆分和延迟加载。
3. LCP(最大内容绘制)
- 分析手段:Lighthouse LCP 元素定位;Performance 里找 LCP candidate;检查 LCP 图片请求起始时机。
- 常见原因:首屏大图大小、未预加载、服务端慢、渲染被 JS 阻塞、CDN 命中率
- 优化方案:LCP 资源预加载(preload/fetchpriority=high)、图片压缩与 WebP/AVIF、SSR 直出首屏内容、减少首屏 JS 执行。
1、注意LCP最大内容绘制,获取的是 当前视口内最大的内容元素,LCP 只会取一个元素
2、如果首屏上有大图和小图,那么LCP只会取大图作为LCP最大内容元素
4. TBT(总阻塞时间)
- 分析手段:Performance 看 Long Tasks(>50ms);Coverage 看未使用 JS;主线程任务分解。
- 常见原因:大包 JS、第三方脚本多、同步执行重计算。(主线程阻塞时间)
- 优化方案:代码分割、按路由懒加载、移除无用依赖、Web Worker 转移重计算、第三方脚本延后。
5. CLS(累计布局偏移)
- 分析手段:Performance 的 Layout Shift 记录;定位发生偏移的 DOM 元素。
- 常见原因:图片/广告位无尺寸、异步内容插入顶部、字体切换导致跳动。
- 优化方案:为媒体和广告容器预留宽高、避免顶部插入、字体度量兼容与预加载关键字体。
FCP优化/LCP优化
- 首屏图片不要懒加载
- 不要设置
loading="lazy",这会直接把 LCP 拖慢。LCP 图片应该优先加载,而不是懒加载。官方建议让浏览器尽早发现并请求 LCP 资源 - 使用fetchpriority设置为high,表示图片是高优先级的,浏览器需要尽早加载
- 不要设置
<img
src="/hero-cover.webp"
width="1200"
height="675"
fetchpriority="high"
loading="eager"
alt="封面"
/>
- 图片格式使用webp
- 首屏只保留必要字体和必要 CSS
- 对字体文件压缩
- 自定义字体加
font-display: swap
- 第三方脚本进行异步加载 async
- 比如三方广告、facebook登录这些sdk
字体文件加载优化
- 根据当前语言,动态加载对应字体 CSS
<script setup lang="ts">
const { locale } = useI18n()
const fontCssHref = computed(() => {
switch (locale.value) {
case 'ar':
return '/fonts/ar.css'
case 'ja':
return '/fonts/ja.css'
case 'en':
default:
return '/fonts/en.css'
}
})
useHead({
htmlAttrs: {
lang: computed(() => locale.value)
},
link: [
{
key: 'locale-font-css',
rel: 'stylesheet',
href: fontCssHref
}
]
})
</script>
拆分chunk包
nuxt通过nuxt analyze来分析生成报告
对构建产物进行 chunk 包体积分析,降低首屏 JS 体积与主线程执行成本
vooks、@css-render、vdirs、seemly本质上是naive-ui的依赖生态- 配置manualChunks手动拆包,目的是把“重依赖”从业务路由 chunk 里剥离,减少首页首屏 JS 竞争
分析是否有效
- Network 看首屏 JS 体积,可以看到拆出来的每一个chunk
- Performance 看主线程脚本耗时
- 比较
Evaluate Script、Compile Script总时长,优化后应下降
- 比较
- Lighthouse 核心指标,如FCP、LCP
vite: {
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (!id.includes('node_modules')) {
return
}
if (id.includes('xgplayer')) {
return 'vendor-xgplayer'
}
if (id.includes('lodash-es')) {
return 'vendor-lodash'
}
if (id.includes('dayjs')) {
return 'vendor-dayjs'
}
if (
id.includes('naive-ui')
|| id.includes('@css-render')
|| id.includes('vooks')
|| id.includes('vdirs')
|| id.includes('seemly')
) {
return 'vendor-naive'
}
if (id.includes('@vueuse')) {
return 'vendor-vueuse'
}
if (id.includes('swiper')) {
return 'vendor-swiper'
}
},
},
},
}
css优化
- 减少无用的css
- UI 库全量引入问题优化
- 优先砍掉首屏没用到的样式
- css和js分离拆分,利用css缓存复用
- 压缩css文件
css加载慢问题
CSS 只有在浏览器拿到 HTML,并解析到对应的 <link rel="stylesheet"> 后才会发请求
- 检查是否渲染阻塞
- 文件太碎,可能导致不是“下载慢”,而是“排队慢”
- 检查是否命中缓存
图片优化
图片尺寸优化
- 对于CDN的图片链接,图片尺寸以及压缩参数进行二次修改
播放页封面优化
xgplayer西瓜视频播放器
- 不要把首屏 LCP 交给
xg-poster - xgplayer 初始化后才插入/更新的
方案:
- SSR/首屏 HTML 里直接输出
<img> - 给这张图加
fetchpriority="high" - 不加
loading="lazy" - xgplayer 初始化后,等
loadeddata/canplay再把这张图隐藏 xg-poster做兜底
首屏先渲染一张真实
<img>,负责 LCP同时给 xgplayer 继续传
poster当播放器触发
loadeddata或canplay后,把外层<img>隐藏后续如果播放器需要内部 poster 行为,
xg-poster继续接管
<script setup lang="ts">
import Player, { Events } from 'xgplayer'
const poster = 'https://file-cdn.flextv.cc/section_cover/50/2f/502f42....jpg'
const videoUrl = '你的视频地址'
const playerEl = ref<HTMLElement | null>(null)
const showCover = ref(true)
useHead({
link: [
{ rel: 'preconnect', href: 'https://file-cdn.flextv.cc' },
{ rel: 'preload', as: 'image', href: poster, fetchpriority: 'high' }
]
})
onMounted(() => {
const player = new Player({
el: playerEl.value!,
url: videoUrl,
poster, // 可保留;但首屏 LCP 不再依赖它
})
player.on(Events.LOADED_DATA, () => {
showCover.value = false
})
// 也可以换成 CANPLAY
// player.on(Events.CANPLAY, () => {
// showCover.value = false
// })
})
</script>
<template>
<div class="player-shell">
<img
v-if="showCover"
class="player-cover"
:src="poster"
alt=""
fetchpriority="high"
/>
<div ref="playerEl" class="player-core"></div>
</div>
</template>
图片预加载/懒加载/优先级加载
- 首屏图片使用:
- fetchpriority=high,提高首屏图片优先级加载
- 前几项 eager,其他lazy (eager立即加载,lazy懒加载)
- 不在可视区域范围的图片,进行懒加载
存放本地图片优化
本地 项目里不要把“大量业务图片”直接放仓库
- 注意使用webp,提高压缩率,降低图片体积
方案:
- 可以手动压缩+转 webp 再存 OSS,
- 原图上 OSS + 参数化输出 webp + 尺寸裁剪 + CDN缓存 (推荐)
场景
- 项目内只放少量稳定的小资源:logo、icon、默认图、装饰图
- 适合放在项目存放的:favicon、OG 图、默认头像、占位图
- OG 图通常是“站点级、低频变更、需要稳定 URL”的资源,这类文件很适合放在 Nuxt 的
public/目录里。Nuxt 官方也直接把og-image.png作为public/的典型示例 - OG 图本质上是给外部爬虫抓取的公开资源。它不需要登录态,也不该依赖复杂鉴权,所以最重要的是“公网可访问、地址稳定”
- OG 图通常是“站点级、低频变更、需要稳定 URL”的资源,这类文件很适合放在 Nuxt 的
- 业务内容图、封面图、运营图、用户上传图放 OSS + CDN
放assets和public选择
- 放
public/- 它需要一个固定、公开、稳定的地址
- 放在pulic的资源图片对seo会更加友好,更稳定的抓取、语义清晰,不会被构建工具导致文件名影响语义
- 如favicon.ico、og图、占位图
- 放
assets- 不要求固定公开 URL
- 适合前端工程化资源处理
TTFB优化
- CDN加速优化
- 缓存
- 接口拆分