博客零外部依赖改造:从 6 个远程域名到纯静态

背景

Misaka Network Blog 一直以「纯静态博客」自居,但一次全面审计后发现:每访问一个页面,浏览器都会向 6 个外部域名发起请求。这些请求不仅是隐私隐患(Google Fonts 会记录访客 IP),还引入了不必要的网络依赖——如果 CDN 挂了,博客的样式和交互就会出问题。

审计工具很简单:

grep -rn "cdn\.\|fonts\.google\|jsdelivr\|unpkg" src/

审计结果:6 个外部域名

域名用途加载范围
fonts.googleapis.comSpace Grotesk + JetBrains Mono 字体 CSS每页
fonts.gstatic.com字体文件每页
cdn.jsdelivr.netKaTeX CSS每页
cdn.jsdelivr.netMermaid.js(流程图)有图表的博文(懒加载)
cdn.jsdelivr.netECharts(Blog Galaxy)Galaxy 页面
cdn.jsdelivr.netChart.js(标签统计)标签统计页
q1.qlogo.cn / q.qlogo.cn / q2.qlogo.cn / s2.loli.net / cdn.jsdelivr.net友链头像友链页(懒加载 img)

Google Fonts 是最大的隐私问题——它让 Google 能看到每一个访问你博客的人。

改造方案

1. 字体自托管

之前:两个 <link> 标签从 fonts.googleapis.com 加载 Space Grotesk 和 JetBrains Mono,附带两个 preconnect 到 Google。

之后:从 Google Fonts 下载 woff2 格式的字体文件到 public/fonts/,在 global.css 中添加 @font-face 声明:

@font-face {
  font-family: 'Space Grotesk';
  src: url('/fonts/space-grotesk-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

共下载了 7 个字体文件(Space Grotesk 三个字重、JetBrains Mono 四个字重),总大小约 126 KB。项目已有的 Atkinson 字体早就做了自托管,算是给后来者打了个样。

font-display: swap 确保字体加载期间用系统字体回退,不会出现白屏。

2. KaTeX CSS 按需加载

之前BaseHead.astro 里无条件加载 KaTeX CSS,每页都多一个 ~23KB 的请求,即使 90% 的页面没有数学公式。

之后:KaTeX CSS 改为仅在博文页面加载。通过 Astro 的 prop 传递机制实现:

// BaseHead.astro - 新增 showKaTeX prop
interface Props {
  showKaTeX?: boolean;
}
const { showKaTeX = false } = Astro.props;

// 条件渲染
{showKaTeX && (
  <link rel="stylesheet" href="/libs/katex.min.css"/>
)}

BlogPost.astroshowKaTeX={true}Layout.astro 透传。首页、友链、关于等页面不再加载 KaTeX CSS。

KaTeX CSS 通过 npm 包 import 'katex/dist/katex.min.css' 加载,由 Vite 在构建时处理字体路径解析和拷贝,不再依赖 CDN。

3. CDN JS 库本地化

三个运行时加载的 JavaScript 库全部从 jsdelivr CDN 下载到 public/libs/

文件大小
Mermaid.jsmermaid.min.js3.2 MB
EChartsecharts.min.js1.0 MB
Chart.jschart.umd.min.js201 KB

Mermaid 是其中最重的。它之前是懒加载(仅在有 Mermaid 代码块的页面才加载),这个策略保留不变——只是加载源从 CDN 换成了本地 /libs/mermaid.min.js

改动的文件:

  • MermaidRendererOptimized.astroscript.src = '/libs/mermaid.min.js'
  • BlogGalaxy.astro<script src="/libs/echarts.min.js">
  • TagsStatistics.astro<script src="/libs/chart.umd.min.js">

4. 死代码清理

src/components/MermaidRenderer.astro 是老版 Mermaid 渲染器,未被任何组件 import,但代码里引用了 3 个 CDN URL(Mermaid.js + KaTeX JS + KaTeX CSS)。直接删除。

5. 友链头像本地化

5 位友链的头像分布在不同域名(QQ 头像 CDN、jsdelivr、SM.MS 图床),现在统一下载到 public/avatars/consts.ts 中的路径改为本地:

- avatar: 'https://q1.qlogo.cn/g?b=qq&nk=7618557&s=640',
+ avatar: '/avatars/xia-yeling.jpg',

改造前后对比

改造前(每页请求):
  fonts.googleapis.com  ─── 2 个 CSS
  fonts.gstatic.com     ─── 字体文件
  cdn.jsdelivr.net      ─── KaTeX CSS

改造后(每页请求):
  (无外部请求)

字体和 JS 库均从 blog.misaka-net.top 本地加载;KaTeX CSS 通过 npm 引入
友链头像仅特定页面加载,且均为本地路径

用一行命令验证:

grep -r "cdn\.\|fonts\.google" dist/ --include="*.html"
# 返回空

总结

这次改造的核心思路不是简单的”屏蔽一切外部资源”,而是:

  1. 字体自托管是最有价值的——消灭了影响所有页面、所有访客的隐私泄露
  2. 按需加载 > 无条件加载——KaTeX CSS 从”每页必加载”改为”仅博文页面加载”
  3. 死代码该删就删——留着不仅有维护负担,还是安全隐患
  4. 友链头像是容易被忽略的隐私泄漏点——<img> 标签的 Referer 头会暴露访客来源

改造后的博客做到了真正的「零外部运行时依赖」——浏览器不会向任何第三方发起自动请求。如果用户不点击外部链接,他们的访问行为完全留在自己的服务器上。