资讯文章 资讯 · NEWS
列表页 HTML 从 200KB 压到 40KB:目录站首屏体积的六个动作
2026-10-24 约 1,699 字 预计阅读 4 分钟 yiyunkj
列
先量三个数,再决定动哪里:HTML 原始字节数(curl 看 Content-Length 或者在 DevTools 的网络面板看资源大小)、传输字节数(压缩后的实际下载量)、DOM 节点数。三个数指向的问题不一样,改法也不一样。
原始字节大但传输字节小,说明压缩生效了,问题在解析和渲染;传输字节本身就大,说明压缩没配上或者内容实打实太多;DOM 节点数超标(Lighthouse 的参考线是 800 个开始提醒、1400 个以上算多),说明结构太碎,跟体积是两回事,但往往一起出现。

图片来源:网络,版权归原作者所有,侵权请联系删除
六个动作,按性价比排序
| 动作 | 典型降幅 | 副作用 | 优先级 |
|---|---|---|---|
| 1. 结构化简(去掉冗余 wrapper 与重复属性) | 20%–40% | 需要改模板 | 高 |
| 2. 首屏只输出首屏条目 | 30%–60% | 要处理后续加载 | 高 |
| 3. 内联脚本与内联样式外置 | 5%–15% | 关键 CSS 要单独处理 | 中 |
| 4. 清空注释、空白与调试输出 | 3%–8% | 基本没有 | 中 |
| 5. 内联 JSON 数据减肥 | 10%–30% | 要看前端用不用得上 | 中 |
| 6. 压缩协商(gzip / Brotli / zstd) | 60%–80% | 要加 Vary 头 | 先做 |
第六个动作其实应该最先做,因为它是唯一一个不动代码就能生效的。静态资源用 Brotli 预压缩,动态响应用 zstd 或中低等级 Brotli,gzip 留着兜底。加了压缩就必须加 Vary: Accept-Encoding,否则 CDN 会给不支持 Brotli 的客户端返回 Brotli 版本。
动作一:结构化简
这是目录站最肥的一块。一张条目卡片如果外面套了三层 div,每层都带 class 和一个 data 属性,一百条卡片就是三百个多余节点加六百个多余属性。
做法上是两件事:一是去掉纯粹为了布局历史遗留的 wrapper 层,改用 grid 或 flex 直接在父容器上排版;二是 data 属性只保留前端真正会读的,其余的从 HTML 里拿掉,需要时从接口取。
class 名长度在压缩之后影响很小,不用为了省字节牺牲可读性——真正占地方的是节点数量和属性个数。
动作二:首屏只输出首屏
一个列表页输出 200 条条目,用户第一眼能看到的只有前面几条,剩下的 190 条 HTML 是为「可能滚动」准备的。
改成首屏只输出一屏的量,其余滚动到底时再取。这里有三个坑:
- 后续加载的内容要能被正常抓取,或者明确接受它不被抓取——取决于那部分内容对你的价值。
- 分页或追加加载的每一页,canonical 都要指向自己所在的那一页,而不是统一指向第一页。
- 骨架屏只能用在非主力内容上。用它顶替首屏的主条目,等于给爬虫一个空壳。
动作三与动作五:把不该在 HTML 里的东西拿出去
内联脚本和内联样式有两个问题:体积只是其一,其二是它会让 CSP 必须开 unsafe-inline。外置之后两边都受益,但要记得把首屏渲染必需的那部分 CSS 单独内联(这是关键 CSS 的范畴,别把整个样式表都塞回去)。
很多目录站会在 HTML 里塞一段 JSON 给前端初始化用,里面往往包含后端查出来的完整字段——包括前端一个都不用的那些。做法是在序列化之前先做一次字段白名单,只留前端真正读的字段。
动作四:别小看空白和注释
模板里留下的 HTML 注释、调试时打印的查询信息、以及循环产生的空行,加起来能占掉几个百分点。这一项几乎没有风险,构建阶段加一步处理就行。
怎么验收
用 curl 直接量传输体积:
curl -s -o /dev/null -w '%{size_download}\n' \
-H 'Accept-Encoding: br, gzip' https://example.com/cate/1.html
再在控制台里数一遍 DOM 节点:
document.getElementsByTagName('*').length
两个数字分别对应传输成本和渲染成本,一起看才知道优化有没有跑偏。只压了体积、节点数没降,解析压力还在。
什么时候该停
HTML 文档压缩后控制在几十 KB、DOM 节点数在几百量级,就已经足够了。再往下压,收益会被维护成本吃掉——把 class 名全改成单字母这种事,省下来的字节远不如它带来的可读性损失。
真正该继续投入的是另一件事:为什么一个列表页需要输出两百条条目。这可能是信息架构的问题,不是体积的问题。