去年,我把我的网站从 WordPress 迁移到了 Hexo。随后的一段时间里,我对 Hexo 进行了大量定制,包括修改主题,实现图片自适应,适配视频等。
基础架构
本站使用 Hexo 博客框架生成静态网页,使用 Nginx 服务器分发静态网页、图片等。本站的静态文件和图片同时部署在全球四台服务器上,使用 Route 53 实现了分区解析和宕机后自动切换,使用 CloudFront 和 UPYUN CDN 分发视频。
主题
本站主题是在 Claudia 的基础上进行定制与魔改。原主题就有如下功能:
- 移动端适配
- 评论插件集成
- 页面边栏 (个人信息,最近文章等)
- 自适应设备的深色模式
- 目录自动生成
- 现代化的设计语言
我对其增加的功能主要如下:
图片
- 自动为文章中图片增加 srcset 和 sizes 属性
- 支持全屏展示
- 支持全屏后缩放,并按需加载缩放后的分辨率
- 移动端
100vw
宽度无边距 - 自动将图片转化为
<figure>
并展示图片描述
视频
- 自动解析
<iframe>
中src=
的内容并将其放在srcdoc=
中减少网络请求 - 可以实现在首页显示视频预览并自动静音播放(集成了
HLS.js
)
搜索
原本主题的搜索功能在电脑端只会在最右侧的边栏展示搜索结果,我对其进行了改进,实现了用户在搜索时搜索栏和搜索结果自动延长。各位可以直接前往首页体验,或者查看下方的效果视频:
分区解析与 CDN
我的网站目前同时部署在国内外多个主机上,使用着相同的配置。域名使用 Route 53 的延迟记录进行分区解析,并开启了 “运行状况检查” 实现宕机后自动切换服务器目前本站使用了 5 个服务器,分别部署在北京、东京(日本)和拉斯维加斯 (美国)、蒙特利尔 (加拿大) 和法兰克福 (德国)。
选择使用 Route 53 作为解析服务器的原因是:
- 它使用了 Anycast 技术,在全球访问速度都很快
- 国内连接 Route 53 的 DNS 服务器可以连接到延迟小于 50ms 的日本节点,
- 支持 DNSSEC 和 IPv6
- 支持按地区的分区解析、延迟解析等。在美国可以细分到州,其他地方也可以细分到国家
- 支持 “运行状况检查”
- 按量计费。起步价每个域名 $0.50 /月,“运行状况检查” 每个服务器 $0.50 /月。上述的所有功能也均是按量付费,没有固定月费。
视频 CDN
本站视频虽然是使用 Cloudflare Stream 存储和转码,但实际分发使用的是 CloudFront 和阿里云 CDN。主要是 Cloudflare 的视频是按量计费,其单价比 CloudFront 要贵。其次是 Cloudflare Stream 不提供国内节点,因此为国内提使用阿里云 CDN。
双 CDN 配置——Nginx 替换 CDN URL
实现双 CDN 有两种方法,第一种是使用 Nginx 根据访客国家替换 CDN URL。这样做相比分区解析的好处是即便用户配置的 DNS 服务器非用户所在地服务器,替换 CDN URL 方式依然会让用户使用正确的 CDN。可以采用了 Nginx 根据客户端 IP 的国家进行 CDN 域名的替换,具体配置——在 http
中:
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
auto_reload 1d;
$geoip2_data_country_code default=CN country iso_code;
}
map $geoip2_data_country_code $tlo_domain {
default "tlo.xyz";
"CN" "tloxygen.com";
}
在 server
中:
sub_filter '//videodelivery.net/' "//video.${tlo_domain}/";
这样一来,用户访问网站时,服务器会根据用户访问网站时的 IP 地址(而非 DNS 提供的 IP 地址)来选择合适的 CDN。
双 CDN 配置——分区解析
这就不用多说了,将两个 CDN 绑定在一个域名上,使用 GeoDNS 对不同地区的访客返回不同的结果。
图像 CDN
本站的图片均使用 Cloudflare Images 进行压缩与存储,并通过 Nginx 进行代理与缓存。Nginx 配置如下,实现了根据设备兼容性优先提供 AVIF、WebP 和 JPEG。这是因为 Cloudflare Images 在国内的速度不佳。此外 Cloudflare Images 可以在访问时自动将图片调整分辨率和格式。当用户访问相对路径 /cdn-cgi/imagedelivery/
时,网站就会加载相应的图片,还省去了与新的域名建立 HTTP 连接的时间。
Nginx 配置
在 http
中:
map $http_accept $suffix {
default "jpeg";
"~image/avif" "avif";
"~image/webp" "webp";
"~image/apng" "apng";
}
map $http_accept $png_suffix {
default "png";
"~image/avif" "avif";
"~image/webp" "webp";
"~image/apng" "apng";
}
map $http_accept $gif_suffix {
default "gif";
"~image/avif" "avif";
"~image/webp" "webp";
"~image/apng" "apng";
}
map $http_accept $default_suffix {
default "default";
"~image/avif" "avif";
"~image/webp" "webp";
"~image/apng" "apng";
}
map $sent_http_Content_Type $file_name {
default "default";
"image/avif" "avif";
"image/webp" "webp";
"image/apng" "apng";
"image/jpeg" "jpeg";
"image/png" "png";
"image/gif" "gif";
}
在 server
中
proxy_store /var/www/images$uri/image.$file_name;
location /cdn-cgi/imagedelivery/6T-behmofKYLsxlrK0l_MQ/ {
root /var/www/images/;
try_files $uri/image.$suffix $uri/image.$png_suffix $uri/image.$gif_suffix $uri/image.$default_suffix @proxy;
}
location @proxy {
proxy_ssl_name imagedelivery.net;
proxy_ssl_server_name on;
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
proxy_set_header Host imagedelivery.net;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_hide_header Upgrade;
proxy_hide_header Alt-Svc;
proxy_hide_header Expect-CT;
proxy_http_version 1.1;
proxy_set_header Connection "";
if ($uri ~* '^https://cdn.tloxygen.net/6T-behmofKYLsxlrK0l_MQ/(.*)$') {
add_header X-Cache-Status "MISS";
proxy_pass https://cdn.tloxygen.net/6T-behmofKYLsxlrK0l_MQ/$1;
break;
}
}
通过 proxy_store
,图片将会在第一次请求后永久的存储在本地,并在下次访问时从本地提供。在存储时,图片的变体名和格式会作为文件名的一部分进行存储。在访问时,服务器会根据客户端发送的 http_accept
请求头去查找对应文件。
AVIF 格式比 WebP 的压缩效率更好,而 WebP 格式比 JPEG 的压缩效率更好。然而对于兼容性而言,JPEG > WebP > AVIF。尽量不要在网站上使用 GIF,如果需要展示动画,可以使用静音自动循环播放的 <video>
代替。
WebP/AVIF 自适应之 CDN 配置
如果原站支持 WebP/AVIF 自适应,那么在配置 CDN 的时候,需要选择根据客户端的 Accept 头进行缓存。CloudFront 和阿里云 CDN 的配置分别如下:
自动部署
本站使用 GitHub Action 在服务器上运行 SSH 脚本实现自动部署。在服务器上配置了 post-merge
的 git hooks,在有改动后自动运行脚本。具体来讲,是在一个部署专用的服务器上生成静态页面,然后将这些静态页面分发到对应的服务器。
统计
本站使用自建的 Matomo网站统计。Matomo 是一个基于 PHP 和 MySQL 的非常强大的开源统计软件。
除此之外,本站还通过 JavaScript 实现了对视频播放,图像查看和搜索的统计。本站还使用了 Nginx 的 post_action 功能实现了异步发送统计,具体在 server
中的配置如下:
location /api/a {
post_action @tracker;
return 204;
}
location @tracker {
internal;
proxy_pass https://origin/matomo.php$is_args$args;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Host matomo.tloxygen.com;
proxy_set_header TLO-Connecting-IP $remote_addr;
}
HTTP/2 Server Push
本站还启用了 HTTP/2 Server Push,用户在访问网站时,服务器会一次性直接推送首屏渲染必要的 CSS 和 JS 文件,由于用户不需要在获取到 HTML 页面后再去获取 CSS 和 JS 文件,首屏渲染的时间大大缩短。实测在模拟 Fast 3G 环境下,启用 HTTP/2 Server Push 后首屏时间减少了约 0.3 秒:
具体 Nginx 中 server
的配置如下
listen 443 ssl http2;
listen [::]:443 ssl http2;
location ~* \.(?:html)$ {
http2_push /style/common/bulma.css;
http2_push /style/base.css;
http2_push /style/common/helper.css;
http2_push /style/post.css;
http2_push /style/widget-header.css;
http2_push /style/widget-post-list.css;
http2_push /style/themes/highlight-theme-light.css;
http2_push /js/common.js;
http2_push /js/sdk.latest.js;
http2_push /js/post.js;
}