惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
aimingoo的专栏
aimingoo的专栏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
J
Java Code Geeks
博客园_首页
Google Online Security Blog
Google Online Security Blog
Hugging Face - Blog
Hugging Face - Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
Intezer
P
Palo Alto Networks Blog
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
O
OpenAI News
SecWiki News
SecWiki News
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
N
News | PayPal Newsroom
Project Zero
Project Zero
Forbes - Security
Forbes - Security
IT之家
IT之家
A
Arctic Wolf
WordPress大学
WordPress大学
Jina AI
Jina AI
T
Tor Project blog
博客园 - 三生石上(FineUI控件)
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
博客园 - 聂微东
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy International News Feed
Cloudbric
Cloudbric
G
GRAHAM CLULEY
博客园 - 叶小钗
H
Hacker News: Front Page
腾讯CDC
量子位
Help Net Security
Help Net Security
人人都是产品经理
人人都是产品经理
C
Cyber Attacks, Cyber Crime and Cyber Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
爱范儿
爱范儿
L
Lohrmann on Cybersecurity
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
C
CERT Recently Published Vulnerability Notes

kkocdko's blog

极限压缩文档扫描件 - kkocdko's blog Bypass the Blank Chars Detection crknob - Chrome portable patch 如何极限优化压缩文件体积 - kkocdko's blog EmEditor 精简版 - kkocdko's blog Backup Data from VSCode Web 优雅地游玩 Minecraft - kkocdko's blog 让程序以管理员权限运行的 PE 清单 - kkocdko's blog 注销 ZEIT 账号之后遇到的坑 - kkocdko's blog 本博客的协议已更改为 CC0 - kkocdko's blog mobp - 使 MSOffice 后台常驻 在安装 VS 生成工具时规避 .NET 安装错误 各种格式的占位空文件 - kkocdko's blog 优雅地游玩 Flash 游戏 - kkocdko's blog WPS 2016 极限精简版 - kkocdko's blog Convert Const to Let by Terser 规避 Visual Studio 许可证过期的 Bug 方便地使用 iPod - kkocdko's blog 用 UserJS 注入 UserCSS - kkocdko's blog 注册表修改文件关联(默认程序) - kkocdko's blog 周期精准的太阳系模型 - kkocdko's blog 关闭屏幕的 WinAPI - kkocdko's blog 重置 Windows 图标缓存 - kkocdko's blog AIDA64 极限精简单文件版 - kkocdko's blog 几何画板单文件精简版 - kkocdko's blog 世界之窗浏览器单文件版 - kkocdko's blog 在 MBR + Legacy 下用 Bootmgr 引导 Ubuntu 简洁的 Markdown 预览工具 - kkocdko's blog 修复 MSOffice 墨迹不可用问题 - kkocdko's blog 使用 VBA 批量转换 Office 文档格式 不便携的有道词典便携版 - kkocdko's blog 关于清华同方还原卡的一个坑 - kkocdko's blog 也许是最简洁的搜狗输入法皮肤 - kkocdko's blog 用批处理弄个 U 盘复制器 - kkocdko's blog 美化版 TRIXX - kkocdko's blog Windows 默认蓝色代码 - kkocdko's blog A4 大小的普通英文稿纸 - kkocdko's blog 在批处理中延时(等待) - kkocdko's blog 让批处理隐藏窗口运行 - kkocdko's blog 让批处理以管理员权限运行 - kkocdko's blog 批量修改 PPT 字体样式 - kkocdko's blog New Post - kkocdko's blog Block Android dex2oat on demand Disable “insecure download blocked” warning on Chrome and Edge Reduce memory usage of FreeCAD and OrcaSlicer 用 GDB + Python 获取 Linux 端微信本地数据库密钥 Android bug prevents exfat readonly USB drive Cons of IronRDP today, compare to FreeRDP Multi call binary for existing projects WPS Android modified and stripped Disadvantages of APE - kkocdko's blog Change Linux UKI cmdline without regeneration Disk duplicator is considered deprecated 请弃用网易邮箱 - kkocdko's blog Summary of window state restore on desktop Draft of Mono Client Project Right path to use new glibc on old Linux Linux 笔电的所谓省电技巧 - kkocdko's blog Android 腾讯 TIM 迁移 QQ 指北 消费级 Android on Windows 是没有前途的 请停止滥用新 Web 标准特性 - kkocdko's blog Rescuing data from offline sites Differences in AMD P-State modes after kernel 6.3 Fix Docker 'input device is not a TTY' elegantly 在 2023 年评测前端静态资源公共 CDN - kkocdko's blog Recording + streaming on Wayland kk 对耳机主动降噪功能的体验与评价 - kkocdko's blog Node.js native addon without node-gyp Standalone and portable MSVC - kkocdko's blog 腾讯云安装 Fedora - kkocdko's blog 优化 Git 仓库的额外提示 - kkocdko's blog Using hwmoncat, a tiny alternative to lm_sensors Build Fedora ISO in 3 Commands 简陋的 Win on Linux 兼容方案 Tradition and Modernity in Async Rust Change ZSTD Level in QCOW2 无损转换 PDF 到 Word 是不可能的 Dump PDF from HEP Website Fedora for Termux - kkocdko's blog Using VSCode Web Server Variant Tricks for C++ Simple App Development Tweak CPU Power Elegantly on Windows Fix Windows Power Options Ineffective 优雅地使用联想电脑管家 - kkocdko's blog Restone Image's Alpha Channel - kkocdko's blog 成立 hal-drv 组织,维护一些 Rust embedded-hal 外设驱动 PotPlayer 图标包:爱奇艺播放器 - kkocdko's blog 爱奇艺万能播放器调教指南 - kkocdko's blog Draft - kkocdko's blog
我如何将博客首页体积降至 3 KB - kkocdko's blog
2026-04-11 · via kkocdko's blog

这并不是一篇网络上泛滥的“前端体积优化”文章。

百尺竿头,更进一步!本文以我的博客为例,介绍极限控制页面体积的奇技淫巧。

成果预览

眼见为实,本博客 首页 的网络传输总体积为 2.6 KB

需求精简

若是简陋的页面,体积再小,也不足为奇。我需要:

没有代码是最好的代码。尽量削减需求,才能根本上减小体积。于是——

  • 仅适配新版浏览器。
  • 仅使用 Markdown 核心语法。
  • 部分遵循 Material Design,舍弃复杂特性。
  • 前端与生成器均不使用框架。

打包与压缩

资源打包早已是常识,但我希望走得更远一些,将所有资源(除页面本身外)合并至单个文件。这不仅能省去请求头体积,还可降低信息熵,使压缩后的总体积更小。

于是有 bundle.js

let avatar = `/*{avatar}*/`;
document.head.insertAdjacentHTML("beforeend", `/*{head}*/`);

其中形似 /*{xxx}*/ 的标记,将被替换为需要嵌入的资源。而嵌入的内容中也可含有标记,不断替换,直至所有资源嵌入完成。

例如,/*{head}*/ 将被替换为 head.html

<link rel="icon" href="${avatar}" />
<style>
  /*{style}*/
</style>

注意到,我在这里将网页图标也嵌入了。但即便你不需要图标,也应指定一个 <link ... href="data:"> 空白图标,否则浏览器将自动向 /favicon.ico 发送多余请求。

要嵌入图像,我们通常会将其以 Base64 进行编码。但我使用的是 SVG 图标,为文本格式,因而将特殊字符使用 encodeURIComponent() 转换后,就可直接直接写作 data:image/svg+xml,<svg ... </svg>,从而避免 Base64 编码所带来的体积膨胀。

切记,引入 bundle.js<script> 标签不应有 defer 属性,且必须在 <head> 中。这与大多数教程的推荐做法背道而驰,却正是我想要的效果:在嵌入的 CSS 加载完成之前,不要渲染页面。

由于请求数量少,再佐以 HTTP2 的 服务端推送,阻塞渲染并不会明显拖慢加载速度。

单页面方案

在静态页面实现 SPA,通常需分别生成静态页面和 JSON。框架辅佐下开箱即用,但有诸多缺点:

  • 响应内容是未转换的 Markdown,解析导致页面卡顿(可改善)。
  • 首次访问加载时间较长(可使用 SSR 解决)。
  • 体积大,构建缓慢(无解)。

还有一种方法是 以 404 页面为路由。易于实现(利用 GitHub API)但首屏加载缓慢,且极不利于 SEO。

而我的博客则选择了另一条路——

得益于前文的资源打包,页面中无效内容极少(只需引入 bundle.js 即可)。例如,某篇文章生成页面如下:

<title>Hello - kkocdko's blog</title>
<script src="/bundle.js"></script>
<main>
  <article>
    <h1>Hello</h1>
    <p>Hello world!</p>
  </article>
</main>

实现页内切换,首先要标记页内链接。一般思路是使用 data-xxx 自定义属性,但在这里我们约定:<a> 标签 href 属性以 /. 前缀,即为页内链接,如 <a href="/./hi">Hi</a>。众所周知 . 代表当前目录,因而此做法不会造成行为改变。

这种做法的好处,远不止于抠出几个字节,更重要的是,这允许我们以原生 Markdown 语法在文章内写出页内链接 [关于](/./about) 而不是突兀的 <a data-spa-link href="/about>关于</a>

链接被点击后,直接 fetch 目标页面,提取内容,更新到当前页面上

onpopstate = () =>
  fetch(location) // location.toString() === location.href
    .then((res) => res.text())
    .then((text) => {
      // 有些玄学的解构,实现提取内容
      [, document.title, , box.innerHTML] = text.split(/<\/?title>|<\/?main>/);
    });

赋值给 onpopstate 是为了使得页面在前进、后退时也能更新内容。

再实现一下监听页内链接(每次页面更新后运行):

for (const element of document.querySelectorAll('a[href^="/."]'))
  element.onclick = function (event) {
    event.preventDefault(); // 避免直接跳转
    history.pushState(null, null, this.href); // 更新 URL
    onpopstate(); // 因为 "pushState" 不会触发 "popstate" 事件
  };

至此,我们初步实现了单页面支持。

简洁的实现代码

有很多技巧,能够在实现等价功能的前提下,减少所需的代码量。当然,在生产项目中使用时需谨慎。

此处仅举一例。本博客页面中 <main> 是页面主要内容的容器,其对应 CSS 需要实现的功能有:

  • 在顶部、底部留白。
  • 一代子元素(卡片)居中,圆角,投影效果,元素间留白。
  • 宽度过低时(移动端)取消各处空白、阴影;子元素的间隙改为分隔线。

通常的实现如下,共 452 字符:

main {
  display: grid;
  grid-gap: 20px;
  justify-content: center;
  margin-top: 75px;
  margin-bottom: 25px;
}

main > * {
  width: 680px;
  margin-top: 20px;
  border-radius: 8px;
  box-shadow: 0 1px 4px #aaaaaa;
}

@media screen and (max-width: 750px) {
  main {
    grid-gap: 0;
    margin-top: 50px;
    margin-bottom: 0;
  }

  main > * {
    width: 100%;
    border-bottom: 1px solid #aaa;
    border-radius: unset;
    box-shadow: none;
  }
}

这里有很多可优化的位点。

  • @media 查询中 screen and 是不必要的,匹配所有类型并没有太大问题。

  • 有些属性在 @media (max-width ... 中被重置,可以改 max-widthmin-width,再将宽度过低 / 宽度正常的属性调换,省去重置语句。

  • Grid 和 justify-content 是不必要的,我们可以对 <main> 固定宽度以约束子元素,再使用 margin: auto 居中。

  • 上一条修改过后,margin 可以与顶部留白 margin-top 缩写,原有的 4 行代码,缩减为单行 margin: 75px auto 25px

  • 子元素间隙用 margin-top 实现。首个子元素的 margin-top 与容器的 margin 重叠,顶部空白保持正常。

  • 使用 box-shadow 向下偏移 1px 来替代 border-bottom,同时省去 @media 块中的重置语句。

应用上述技巧,实现如下:

main {
  width: 100%;
  min-height: 100vh;
  margin: 50px 0 0;
}

main > * {
  margin-top: 1px;
  box-shadow: 0 1px #ddd;
}

@media (min-width: 750px) {
  main {
    width: 680px;
    margin: 75px auto 25px;
  }

  main > * {
    margin-top: 20px;
    border-radius: 8px;
    box-shadow: 0 1px 4px #aaa;
  }
}

仅 309 字符,相较原来的 452 字符,减少了 32%,非常可观。

看得开心么~

这只是本人博客项目中所用技巧的一小部分。其他内容限于篇幅,不再穷举。若你想要深入了解,请见 kblog - GitHub

  • 测试用静态服务器代码(推荐使用 mkcert 管理证书):
const serve = require("http2").createSecureServer;
const read = require("fs").readFileSync;
const load = (p) => require("zlib").brotliCompressSync(read(p));
serve({ cert: read("cert.pem"), key: read("key.pem") }, (_, res) => {
  res.setHeader("content-type", "text/html;charset=utf8");
  res.writeHead(200, { "content-encoding": "br" }).end(load("index.html"));
  res.createPushResponse({ ":path": "/bundle.js" }, (_, r) => {
    r.writeHead(200, { "content-encoding": "br" }).end(load("bundle.js"));
  });
}).listen(4000);