![64173-c965cdb758d4919c.png](https://i-blog.csdnimg.cn/blog_migrate/9ea184677cbbb7e91fe32a7030c26a02.png)
目前项目采用 Nuxt SSR 来完成服务端渲染 ,为满足 SEO 需求,将非首屏内容也进行了请求和服务端直出,导致首屏时间变长(非首屏的资源请求和组件的渲染都会带来额外开销)。对于海量的用户来说,少量的爬虫访问需求反而影响了正常用户的访问,导致 SEO 和用户体验提升存在很大的矛盾。
为了解决这个问题,我们设计和实践了自适应 SSR 方案,来同时满足这两种场景的需求。今天会分享这个方案的技术细节、设计思路以及在实施该方案过程中遇到的一些相关的子问题的实践踩坑经验,欢迎大家一起交流。
分享大纲
- 问题来源和背景
- 问题解决思路
- 自适应 SSR 方案介绍
- 采用自适应 SSR 优化前后数据
- Vue SSR client side hydration 踩坑实践
- 使用 SVG 生成骨架屏踩坑实践
问题来源和背景
目前项目采用 Nuxt SSR 来完成服务端渲染,为满足 SEO 需求,将非首屏资源也进行了请求和服务端直出,导致首屏时间变长(非首屏的资源请求和组件的渲染都会带来额外开销)
优化前的加载流程图
![64173-2980af0cc2352516.png](https://i-blog.csdnimg.cn/blog_migrate/05b3c2fe948719eddd88d899d17c7b26.png)
目前我们的 Nuxt 项目采用 fetch 来实现 SSR 数据预取,fetch 中会处理所有关键和非关键请求
Nuxt 生命周期图
![64173-083ad34e9dbe9065.png](https://i-blog.csdnimg.cn/blog_migrate/1a45b1af3a519fea005ebffa74ddb7c2.png)
对于海量的用户来说,少量的爬虫访问需求反而影响了正常用户的访问,导致 SEO 和用户体验提升存在很大的矛盾。
为了解决这个问题,我们希望能区分不同的场景进行不同的直出,SEO 场景全部直出,其他场景只直出最小化的首屏,非关键请求放在前端异步拉取
解决思路
计划通过统一的方式来控制数据加载,将数据加载由专门的插件来控制,插件会根据条件来选择性的加载数据,同时懒加载一部分数据
- 判断是 SEO 情况,fetch 阶段执行所有的数据加载逻辑
- 非 SEO 场景,fetch 阶段只执行最小的数据加载逻辑,等到页面首屏直出后,通过一些方式来懒加载另一部分数据
优化后的项目影评页加载流程图
![64173-e7355199377a9207.png](https://i-blog.csdnimg.cn/blog_migrate/3ec23afdc69da81243c228a664304875.png)
自适应 SSR 方案介绍
Gitlab CI Pipeline
![64173-6b3f2f299e020fcf.png](https://i-blog.csdnimg.cn/blog_migrate/228e417f12dbaf00e6876c8330d21ad5.png)
自研 Nuxt Fetch Pipeline
借鉴 Gitlab CI 持续集成的概念和流程,将数据请求设计为不同的阶段 (Stage ),每个阶段执行不同的异步任务(Job),所有的阶段组成了数据请求的管线(Pipeline)
预置的 Stage
- seoFetch : 面向 SEO 渲染需要的 job 集合,一般要求是全部数据请求都需要,尽可能多的服务端渲染内容
- minFetch:首屏渲染需要的最小的 job 集合
- mounted: 首屏加载完之后,在 mounted 阶段异步执行的 job 集合
- idle: 空闲时刻才执行的 job 集合
每一个页面的都有一个 Nuxt Fetch Pipeline 的实例来控制,Nuxt Fetch Pipeline 需要配置相应的 job 和 stage,然后会自适应判断请求的类型,针对性的处理异步数据拉取:
- 如果是 SEO 场景,则只会执行 seoFetch 这个 stage 的 job 集合
- 如果是真实用户访问,则会在服务端先执行 minFetch 这个 stage 的 job 集合,然后立即返回,客户端可以看到首屏内容及骨架屏,然后在首屏加载完之后,会在 mounted 阶段异步执行 mounted stage 的 job 集合,另外一些优先级更低的 job,则会在 idle stage 也就是空闲的时候才执行。
Nuxt Fetch Pipeline 使用示例
page 页面 index.vue
import NuxtFetchPipeline, {
pipelineMixin,
adaptiveFetch,
} from '@/utils/nuxt-fetch-pipeline';
import pipelineConfig from './index.pipeline.config';
const nuxtFetchPipeline = new NuxtFetchPipeline(pipelineConfig);
export default {
mixins: [pipelineMixin(nuxtFetchPipeline)],
fetch(context) {
return adaptiveFetch(nuxtFetchPipeline, context);
},
};
配置文件 index.pipeline.config.js
export default {
stages: {
// 面向SEO渲染需要的 job 集合,一般要求是全部
seoFetch: {
type: 'parallel',
jobs: [
'task1'
]
},
// 首屏渲染需要的最小的 job 集合
minFetch: {
type: 'parallel',
jobs: [
]
},
// 首屏加载完之后,在 mounted 阶段异步执行的 job 集合
mount