发现博客首次访问加载很慢,该如何进行优化呢?

首先,我们使用开发者工具对网站页面进行检测,分析其中存在的问题:

  1. 打开浏览器开发者调试工具,选择更多工具中的 性能分析 Performance insights 或者 Lighthouse

预览截图

  1. 在标注3的下拉选项中选择禁用缓存来得到更真实的首次访问模拟,也可以设置弱网来进一步优化。

  2. 等检测完成后结合右侧的提示进行分析优化。

优化前

从下图的分析结果可以看出,首屏白屏时间在 4-6s 之间,FCP时间在5.38S,通过网络请求可以分析出主要阻塞在静态资源的加载。

针对这个问题,不难得出方案,要优化资源加载,将其迁移走CDN加速。

优化前

优化后

从下图的优化后分析结果可以看出,首屏白屏时间缩短到在 1-2s 之间,FCP时间在1.16S,效果还是比较明显。

优化后

如何优化

进入正题,为什么慢?

经典的类比问题:你开着跑车,却行驶在泥土路上,为什么慢?

简单来说,博客的性能服务器主机,一般都有带宽的上下行限制,当访问博客时,从服务器获取 HTML、CSS、IMG等资源,会达到它的瓶颈。

而最常见的方案是将网站整体资源进行CDN资源加速,来缓解低性能服务器主机带来的带宽等瓶颈。

首先是压缩这些静态资源。

然后通过将CSS、JS、IMG等资源分流到CDN进行加速,博客主机则只是进行基础 HTML 得响应,可以有效缓解这个问题。

压缩

安装hexo-all-minifier进行配置即可。

1
yarn add hexo-all-minifier --dev

按文档进行配置_config.yml,下面是参考,这里我没有选择进行JS合并、图片的压缩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
all_minifier: true

js_concator:
enable: false
bundle_path: '/js.js'
front: false
silent: false

html_minifier:
enable: true
ignore_error: false
silent: false

css_minifier:
enable: true
silent: false
exclude:
- '*.min.css'

js_minifier:
enable: true
mangle: true
silent: false
exclude:
- '*.min.js'

image_minifier:
enable: false
图片资源的优化

除了常规的压缩,还可以选择 webp 格式,或者对 jpeg 进行渐进式渲染,png 的交错模式来提升用户体验。

资源的缓存机制

设置合适的缓存机制,例如公共依赖的JS、CSS,以及图片资源,缓存可设置为 3 个月以上。

CDN分流

将静态资源上传到CDN服务器,并替换博客中使用这些资源的地址即可。

下面提供一个简单的替换脚本,可用于检查并替换 CSS、JS、IMG资源路径:

  • 安装cheerio

    1
    yarn add cheerio --dev
  • 添加下面的脚本到 scripts/filter/cdn.js 运行 yarn build 查看替换的 log 输出。

预览截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const cheerio = require('cheerio')
const logs = []
hexo.extend.filter.register('after_render:html', function (str) {

const isDev = hexo.env.cmd === 'server'

if (isDev) {
return str
}

const $ = cheerio.load(str, { decodeEntities: false })

const cdnPrefixAssets = hexo.theme.config.cdn?.prefix?.assets
const cdnPrefixImg = hexo.theme.config.cdn?.prefix?.img

if (cdnPrefixAssets || cdnPrefixImg) {
$('link[rel="stylesheet"], script[src], img[src]').each(function () {
const tag = $(this).is('link') ? 'link' : $(this).is('script') ? 'script' : 'img';
const replaceKey = tag === 'link' ? 'href' : 'src'
const url = $(this).attr(replaceKey);

if (url && (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('//') && !url.startsWith('data:'))) {
const newUrl = tag === 'img' ? (cdnPrefixImg + url) : (cdnPrefixAssets + url);
$(this).attr(replaceKey, newUrl);
if (!isDev) {
const log = `${tag}: ${url}`
if (!logs.includes(log)) {
console.log(`\x1b[34mTIP\x1b[0m cdn replace ${log}`, '')
}
logs.push(log)
}

}
})
}

return $.html()
})

提取页面首屏关键CSS进行拆分

目前主题的大部分样式集中在 main.css中,压缩后依旧有 200K+的大小。

这个问题目前还没有更进一步的处理,方案想到两种:

  1. 重构拆分CSS,按模块重新梳理按需加载
  2. 使用 critical 脚本形式提取分离样式文件

待实践更新

预解析

DNS预解析

DNS预解析是一种机制,通过在浏览器请求之前解析域名的DNS记录,可以加快用户访问页面的速度。当用户点击一个链接或输入URL时,浏览器需要解析URL中的域名,获取对应的IP地址,然后才能发起HTTP请求获取网页内容。这个DNS解析的过程需要时间,而DNS预解析可以在用户访问网页之前就提前解析域名,从而节省了解析时间。

1
<link rel="dns-prefetch" href="//cdn.hi-zhang.com">
资源预加载

资源预加载是一种优化技术,通过在页面加载过程中预先获取将来可能需要的资源,以减少后续请求的延迟。这可以包括CSS文件、JavaScript文件、字体、图像等。

下面是预加载、预取的例子,也可以进一步通过开源库来预加载当前页面上下游的相关链接。

1
2
3
4
<link rel="preload" href="styles.css" as="style">
<link rel="prefetch" href="script.js" as="script">
<link rel="preload" href="font.woff" as="font">
<link rel="preload" href="image.jpg" as="image">

Http 协议升级 h2

首先,怎么排查资源使用的协议?

Network面板中,勾选 Protocol 即可看到对应的协议。

下面是 http2 对于网站、博客比较明显的优点:

  1. 多路复用

http1 中,Chrome 限制单域名6个TCP连接并发,7-12顺位的资源则需要等待。
http2 中,可复用TCP连接,少了建立TCP连接的等待时间。

  1. 头部压缩

http每一次通信都会携带 Request headers 信息,包含 Cookies、浏览器UA等信息。
http2中,会进行压缩并减少冗余,仅发送差异的数据。

针对本网站博客而言,🤔️ 大概提升在 50ms 左右 ,主要是因为博客复杂度不高,上述的优化空间较小。

在 nginx 中开启 http2 只需要在 https 443 的 server 代码块里添加即可。

1
2
3
4
server {
listen 443 ssl http2;
server_name xxxx;
}