博客零外部依赖改造:从 6 个远程域名到纯静态
背景
Misaka Network Blog 一直以「纯静态博客」自居,但一次全面审计后发现:每访问一个页面,浏览器都会向 6 个外部域名发起请求。这些请求不仅是隐私隐患(Google Fonts 会记录访客 IP),还引入了不必要的网络依赖——如果 CDN 挂了,博客的样式和交互就会出问题。
审计工具很简单:
grep -rn "cdn\.\|fonts\.google\|jsdelivr\|unpkg" src/
审计结果:6 个外部域名
| 域名 | 用途 | 加载范围 |
|---|---|---|
fonts.googleapis.com | Space Grotesk + JetBrains Mono 字体 CSS | 每页 |
fonts.gstatic.com | 字体文件 | 每页 |
cdn.jsdelivr.net | KaTeX CSS | 每页 |
cdn.jsdelivr.net | Mermaid.js(流程图) | 有图表的博文(懒加载) |
cdn.jsdelivr.net | ECharts(Blog Galaxy) | Galaxy 页面 |
cdn.jsdelivr.net | Chart.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.astro 传 showKaTeX={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.js | mermaid.min.js | 3.2 MB |
| ECharts | echarts.min.js | 1.0 MB |
| Chart.js | chart.umd.min.js | 201 KB |
Mermaid 是其中最重的。它之前是懒加载(仅在有 Mermaid 代码块的页面才加载),这个策略保留不变——只是加载源从 CDN 换成了本地 /libs/mermaid.min.js。
改动的文件:
MermaidRendererOptimized.astro:script.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"
# 返回空
总结
这次改造的核心思路不是简单的”屏蔽一切外部资源”,而是:
- 字体自托管是最有价值的——消灭了影响所有页面、所有访客的隐私泄露
- 按需加载 > 无条件加载——KaTeX CSS 从”每页必加载”改为”仅博文页面加载”
- 死代码该删就删——留着不仅有维护负担,还是安全隐患
- 友链头像是容易被忽略的隐私泄漏点——
<img>标签的 Referer 头会暴露访客来源
改造后的博客做到了真正的「零外部运行时依赖」——浏览器不会向任何第三方发起自动请求。如果用户不点击外部链接,他们的访问行为完全留在自己的服务器上。