跳到主要内容

性能指标

注意首屏加载的时间,是用户首次进入的页面,而不一定是首页

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,表示图片是高优先级的,浏览器需要尽早加载
html
<img
src="/hero-cover.webp"
width="1200"
height="675"
fetchpriority="high"
loading="eager"
alt="封面"
/>
  • 图片格式使用webp
  • 首屏只保留必要字体和必要 CSS
    • 对字体文件压缩
    • 自定义字体加 font-display: swap
  • 第三方脚本进行异步加载 async
    • 比如三方广告、facebook登录这些sdk

字体文件加载优化​

  • 根据当前语言,动态加载对应字体 CSS
ts
<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
ts
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 继续接管

ts
<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 图本质上是给外部爬虫抓取的公开资源。它不需要登录态,也不该依赖复杂鉴权,所以最重要的是“公网可访问、地址稳定”
  • 业务内容图、封面图、运营图、用户上传图放 OSS + CDN

放assets和public选择

  • 放public/
    • 它需要一个固定、公开、稳定的地址
    • 放在pulic的资源图片对seo会更加友好,更稳定的抓取、语义清晰,不会被构建工具导致文件名影响语义
    • 如favicon.ico、og图、占位图
  • 放 assets
    • 不要求固定公开 URL
    • 适合前端工程化资源处理

TTFB优化​

  • CDN加速优化
  • 缓存
  • 接口拆分