在 Vercel 下优化博客的访问速度

发布时间:2023-09-04

统计字数:2539 字

阅读时间:13 min read

优化目的

今天主要优化的是博客首页和文章详情页的加载速度,首先有几个大前提条件

  1. 博客的代码托管在 Github,部署使用 Vercel 进行部署
  2. 博客的域名是在国外域名商进行购买,未在国内备案,所以无法使用国内服务器或者CDN

那么国内连接 Vercel 的速度,以我广东佛山为例,ping 的延迟为 150ms 左右,也就是最快的加载速度也要 150ms,但是目前博客无论是首页还是文章的详情页的加载速度都大大高于这一延迟,那么就需要对此进行优化。

渲染模式

首先 Nuxt 有下面这几大渲染模式:

  1. 客户端渲染(CSR)
  2. 服务端渲染(SSR)
  3. 混合渲染

客户端渲染:我们在开发后台管理平台的时候,页面首先会给出固定的页面框架模板,然后客户端请求接口获取数据,浏览器再把数据填充进去,从而获得完整的网页,这就是客户端渲染。

服务端渲染:在我们请求页面的时候,服务器会请求数据,将数据填充到 HTML 中,最终直接返回给我们一个完整的页面。

混合渲染:Nuxt3 中的渲染模式,不算是新的渲染模式,其实就是通过设置路由规则,来灵活决定使用哪种渲染模式。

静态站点生成(SSG):网站在构建的时候就直接生成静态的 HTML 文件。

下面是这几大渲染模式的优点和缺点

名称优点缺点
客户端渲染1.更好的交互性能,用户无需进行页面刷新即可与页面交互
2.对于复杂的交互和动态效果的支持较好
1.首屏渲染速度较慢
2.对于 SEO 的支持较弱,因为部分搜索引擎爬虫无法执行 JavaScript 代码
服务端渲染1.更快的首屏渲染速度
2.更好的 SEO 优化,因为搜索引擎可以直接看到渲染好的页面 HTML
3.对于客户端的 JavaScript 代码的依赖较小
1.对于服务端的压力较大
2.对于复杂的交互和动态效果的支持相对较弱
静态站点生成1.极快的页面加载速度
2.对于 SEO 的支持非常好
3.可以在静态页面中实现动态数据的渲染
1.对于频繁更新数据的网站不太适合
2.对于复杂的交互和动态效果的支持有限

基于以上渲染模式,各大托管网站 Vercel和Netlify 都有推出他们的优化渲染模式,例如 IWR 和 SWR等等。

选择方案

我们的网站是属于个人博客网站,网站的总页面顶多上天也不会超过百页,页面的组成由静态 Markdown 组成,而且我们还对SEO有强烈的需求,所以最先否定 CSR 也就是客户端渲染。那么就剩下 SSR 和 SSG 两个选择了。

如果说网站只做静态文档的展示,不和用户做交互的话,那么 SSG 足矣,可是我还想保留跟用户交互的需求,所以 SSR 渲染模式是咱们的优选。

使用 Nuxt 来做 SSR 渲染是极其方便和简单的,通过 useAsyncData 就可以很简单的做到。

ts
const { data } = await useAsyncData("article", () => {
  return queryContent("/_articles").where({ slug: route.params.slug }).findOne();
});

这个代码的作用是在服务端异步获取本地 Markdown 文章的数据,也就是最终返回给我们的页面中是包含文章信息的。

我们查看网页是 CSR 渲染还是 SSR 渲染,可以用一个操作来检查,我们直接右击网页,查看源代码,如果是 CSR 渲染的话,源代码上是不会带有网页的数据,反之 SSR 渲染的话,则会带有,这也是为啥 SSR 会比 CSR 的 SEO 要好的原因,因为 CSR 的话爬虫是获取不到网页数据的。

image-20230903232557450

但是尽管是服务端渲染,由于 Vercel 的连接速度还是太慢了,导致获取文章的时候速度不高。

首页加载速度 2s 左右,这还不是首次加载。

首页加载速度

其中文章详情页也是如此

image-20230903180217374

我们查看一下耗时是花在哪里了,等待服务端响应1.10s,下载内容353ms,可以看到其实主要瓶颈还是在等待服务端响应上。

image-20230903180234643

那么问题就变成了如何解决客户端连接 Vercel 服务端的速度过慢的问题了。

使用 ISR

一开始是想使用 instant.page 来解决这个问题的,instant.page 是什么? 有什么用?

在桌面上

在用户单击链接之前,他们会将鼠标悬停在该链接上。当用户悬停 65 毫秒时,他们有二分之一的机会点击该链接,因此 instant.page 此时开始预加载,平均为页面预加载留下超过 300 毫秒的时间

另一种选择是在用户开始按下鼠标时加载页面而不进行预加载。这使得未使用的请求为零,同时仍然将页面加载平均提高了 80 毫秒。

您还可以在悬停时或链接可见时进行预加载,并在用户开始按下鼠标时触发点击,从而使您的页面成为世界上最快的页面。

在移动

用户在释放之前开始触摸显示屏,平均留出90 毫秒的时间来预加载页面

另一种选择是在链接可见时立即预加载链接。

通俗点就是,该组件利用在悬停连接时,使用预加载这个机制,对目标页面进行预加载,从而在真正打开的时候可以有效利用预读取缓存,不得不说该组件的思路很好,如果能用上的话也算是另寻蹊径了。

但是很可惜,该组件在 Nuxt 下无法正常使用,查阅了一下 Github 的 issuse 貌似是 Nuxt 的 NuxtLink组件已经自带了预加载,并且是在可视区域里面就直接预加载了,可是不知道为啥没有作用,这部分不是重点就不做深入探究了,既然这路行不通,那就走其他路。

然后我就在 Github,Google 上搜啊搜,一开始搜索的方向是如何让国内连接 Vercel 能够快一点,可是在查看了各种答案后放弃了,如果能做到这一点的话,那么国内也没必要做什么备案了,大多数人其实费劲麻烦备案还是想要得到那个速度而已。

既然不能让国内连接的快,那么能不能对 Vercel 做某些操作,让它的响应快一点。

很快我就搜出了一个比较神奇的东西,Github 仓库地址:https://github.com/danielroe/nuxt-vercel-isr

image-20230904001228813

结合 Vercel 官方的文档 https://vercel.com/docs/frameworks/nuxt#incremental-static-regeneration-isr

总而言之,在 Vercel 上使用 ISR 和 Nuxt 可以提供:

  • 通过我们的全球边缘网络获得更好的性能
  • 零停机时间推出到以前静态生成的页面
  • 全球内容300ms更新
  • 生成的页面会被缓存并持久保存到持久存储中

也就是我们可以通过设定指定的路由路径,通过这个 ISR ,可以让 Vercel 那边缓存我们的页面,一直持续到我们下一次部署,并且还能够使用到 Vercel 的边缘网络,虽然不太了解这个边缘网络的具体,但是可以知道的是,通过这样子设置,网站的速度应该可以提高。

那么在 nuxt.config.ts 文件上添加了以下几个路由

ts
routeRules: {
  "/": { prerender: true },
  "/articles/**": { isr: true },
  "/about": { isr: true },
},

部署上去后,以下是优化效果

优化后:

首页加载时间(非首次加载):

首页加载时间(非首次加载)

文章详情页加载速度(非首次加载):

文章详情页加载速度(非首次加载)

时间消耗:

时间消耗

可以看到在使用了 Vercel 的 ISR 后,博客首页以及相关的文章详情页加载速度得到了较大的提升,虽然说首次加载的时间仍然不够理想,但是优化效果到了,那目的也算是达成了。

优化字体文件

除了 Vercel 的响应速度优化外,博客使用到的字体文件也是需要优化的。

在之前博客使用的英文字体分别是 jetBrains-mono 以及 Fira-code,但是这两者都是从 jsdelivr 中引入,由于 jsdelivr 已经被国内的 DNS 污染了,所以导致加载速度也是很慢,特别是首次加载。在首次加载中,由于字体加载过慢,导致页面大部分时间都处于白屏状态,用户体验非常差。

所以优化字体文件的请求速度也是提高博客速度的关键之一,在这里我直接将字体文件上传到腾讯云COS上,通过国内服务商的对象存储来优化加载速度。

或许你会问,你放到 COS 上不怕别人刷流量吗?

我的回答是 怕,但是也不算太怕,首先我这个是个人技术博客,来我的博客都是懂技术的,不会做这么无聊的事情,然后就算是人比较多,但是字体文件浏览器会做一个缓存,后续也不会继续请求,所以其实耗费的流量是比较少的。

总结

今天通过初步了解各个渲染模式,然后再根据实际情况转变优化方向,最后使用 Vercel 的 ISR 优化了博客的访问速度,然后再通过对象存储优化博客的字体文件,最终得到优化的目的。

后续可能还会探索是否有其他更牛逼,更快的优化方式,毕竟 access speed 这个东西肯定是越快越好的。