首页 JS 从 1.73MB 到 58KB:一次博客访问速度优化复盘

AI摘要
【知识分享】本文记录了一次博客前端性能优化的完整过程。作者通过Vite拆包、Vue按需加载、nginx缓存与TLS调优等手段,将首页JS传输量从1.73MB降至58KB,首屏加载耗时从约6秒降至0.4秒。文章提供了具体的技术方案、配置代码和前后数据对比,对前端性能优化实践有参考价值。

一次例行测速,发现博客首屏要 6 秒。拆包、按需加载、nginx 调优三板斧下去,首屏传输量降了 93%,二次访问近乎秒开。

在线地址:https://blog.ishare.cool/(本文说的就是它自己)


序言

博客重构之后,今天顺手测了下访问速度,结果有点难看:首页那个 app.js 足足 1.73MB(gzip 后),在我这台机器上下载要 5、6 秒。服务端响应明明很快(TTFB 才 200ms),问题全在前端资源上。

于是做了一次完整优化:前端拆包 → Vue 全按需加载 → nginx 缓存与 TLS 调优 → 镜像发布,全程有数据对照。最终首页 JS 传输量从 1.73MB 降到 58KB,降幅 97%。这篇文章把整个过程和踩的坑记录下来。

先测速:问题定位

优化之前先看数据,用 curl 把每个环节的耗时拆开:

请求链路: 浏览器 ──► nginx(443) ──► Go(qinblog:8080) ──► MySQL / Redis
实测基线 (优化前)
═══════════════════════════════════════════════════════
HTML TTFB            ~200ms          ✓ 服务端没问题
TLS 握手             偶发 3.4~4.9s   ✗ 有异常
app.js (单包)        1.73MB gzip     ✗✗✗ 罪魁祸首
app.js 下载耗时 5.5 ~ 6.3s      ✗ 带宽被单包打满
静态资源缓存头       无 ✗ 二次访问重复下载
静态资源 Set-Cookie 有 ✗ 资源也穿过会话中间件

折算一下服务器出口带宽:1.73MB ÷ 5.5s ≈ 315KB/s(约 2.5Mbps),这就是小带宽服务器,字节数就是时间。结论很清楚:

优化优先级
──────────────────────────────────────────────
① 减少首屏必须下载的字节数 (拆包/按需加载)让二次访问不再重复下载 (浏览器强缓存)压缩与握手开销 (gzip/TLS 调优)

1.73MB 里装了什么

Vite 默认把静态 import 的依赖全打进入口。查了下 app.js 的依赖图,好家伙:

app.js 单包 1.73MB (gzip)┌──────────────────────────────────────────────────┐
│ highlight.js 全语言包 (~190 种语言)     [大头]    │
│ EasyMDE + CodeMirror (Markdown 编辑器)  [大头]    │
│ @vueform/multiselect (标签多选)                   │
│ inline-attachment (拖拽上传)                      │
│ mermaid / KaTeX 的触发逻辑 ││ Vue 3 运行时 (含编译器)                           │
│ jquery + popper + bootstrap                       │
│ Toc 目录组件 + 3 个后台表单组件 │└──────────────────────────────────────────────────┘
 ▲ │ 问题是:这些只有特定页面才用得到
 │  首页:只用 bootstrap 导航交互 (其余全白下载)
  文章页:多一个 Toc + 代码高亮 (编辑器白下载)
  发文页:才真正需要编辑器全家桶 (唯一全量页面)

一个只展示文章列表的首页,却背着 Markdown 编辑器和 190 种语言的高亮包跑。这就是典型的”入口贪吃”。

优化一:Vite 拆包,重依赖全按需加载

思路一句话:谁用谁加载,入口只留地基

改动清单

1. highlight.js:不再 import hljs from 'highlight.js'(全语言包),新建 hljs-loader.js,页面存在代码块时才动态加载 core + 24 种常用语言;
2. EasyMDE / inline-attachment / 三个表单组件:从入口静态引入改为动态 import,仅当页面出现对应组件标签时才加载;
3. vite.config.js 增加 manualChunks,把 jquery/popper/bootstrap 拆成 vendor-base、Vue 拆成 vendor-vue,独立长缓存。

优化前:一锅端                      优化后:分餐制
─────────────────────              ──────────────────────────────
index.html                         index.html
 │                                  │ ▼                                  ├──► app.js (6KB, 路由胶水)
app.js  [1.73MB 全部加载]              ├──► vendor-base (jquery/bs)
 │ └──► 按需动态 chunk:
 ├─ hljs core+24语言 ◄─ 有代码块才加载
 ├─ mermaid          ◄─ 有图表才加载
 ├─ katex            ◄─ 有公式才加载
 └─ EasyMDE/选择组件 ◄─ 仅发文页加载

有一个约束要注意:所有 CSS import 必须留在入口。因为 Go 侧渲染 Vite 标签时只输出入口关联的 CSS,动态 chunk 的 CSS 不会自动注入 HTML,拆出去样式就丢了。

一个容易忽略的细节:window.hljs

EasyMDE 的预览高亮(codeSyntaxHighlighting)依赖全局 window.hljs。hljs 改成异步加载后,编辑器初始化时必须先等它就绪,所以 SimpleMdeComponent 的 mounted 改成了 async:

async mounted() { // 预览代码高亮依赖 window.hljs,先确保已加载
 await loadHljs() this.easymde = new EasyMDE({ ... })}

第一轮效果

首页必须加载的 JS (gzip)══════════════════════════════════════════
优化前 app.js 单包 1.73MB  ████████████████████优化后 app 6.8KB + vendor-base 174KB       ~120KB  █▌──────────────────────────────────────────
降幅:约 93%```

浏览器端到端验证:文章页 mermaid 图表、KaTeX 公式、代码高亮全部正常,发文页编辑器可用,控制台零报错。

# 优化二:Vue 也按需,首页一个字节都不多给

第一轮做完还剩一个尾巴:vendor-vue(177KB)仍然被入口静态引入,**首页没有 Vue 挂载点却也照下载不误**。

本项目的 Vue 是"撒点式"使用:页面内联脚本设置 `window.__vueOptions = { el, data }`,入口检测到才挂载。既然如此,Vue 运行时本身也可以等检测到再加载:

```javascript
async function mountVue(opts) {
 // 仅存在挂载点的页面(文章详情/发文页)才拉取 vue 运行时
 const { createApp } = await import('vue') const app = createApp({ ... })
 // 各组件按页面实际出现的标签动态加载
 if (document.querySelector('toc')) { const { default: Toc } = await import('./components/Toc.vue') app.component('toc', Toc) } if (document.querySelector('simple-mde-component, ...')) { // 编辑器三组件 ... } app.mount(opts.el)}

各页面最终加载的 JS 变成这样:

页面            加载的 JS────────────────────────────────────────────────────────
首页 app + vendor-base                        (~58KB gzip)文章详情页 + vendor-vue + Toc + hljs/mermaid/katex   (有什么加载什么)
发文页 + vendor-vue + EasyMDE + 选择组件 (唯一的全量页面)

首页再降约 170KB。两轮下来:

首页 JS (gzip) 演进
══════════════════════════════════════════════════
原始单包 1.73MB   ██████████████████████████████第一轮拆包 ~120KB   ██Vue 按需 ~58KB    █──────────────────────────────────────────────────
累计降幅 97%```

# 优化三:nginx 层——缓存、压缩与握手

前端瘦完了,轮到链路。原配置有三个问题:静态资源没有缓存头(二次访问重复下载)、穿过 Go 应用还带着 Set-Cookie、TLS 握手偶发抽风。

## 核心配置

```nginx
# Vite 带 hash 产物:一年 immutable 强缓存(优化核心)
location /build/assets/ {
 proxy_pass http://qinblog_backend; proxy_hide_header Cache-Control; proxy_hide_header Set-Cookie;      # 静态资源无需会话 cookie add_header Cache-Control "public, max-age=31536000, immutable" always; add_header Strict-Transport-Security "max-age=16070400;includeSubDomains;preload" always; access_log off;}

# HTML/API:不缓存,保证发版后拿到最新 hash 引用
location / {
 proxy_pass http://qinblog_backend; proxy_http_version 1.1;            # upstream keepalive 的前提
 proxy_set_header Connection ""; ... add_header Cache-Control "no-cache, must-revalidate";}

配套还有:upstream keepalive(减少 nginx→Go 重复建连)、ssl_session_cache(TLS 会话复用)、OCSP stapling(缩短证书验证往返)、显式 gzip(level 6,覆盖 js/css/json/svg)。

immutable 敢用一年的前提是 Vite 产物文件名带内容 hash:内容一变文件名就变,HTML 指向新文件,旧文件的缓存自然作废。这也决定了强缓存范围必须收窄到 /build/assets/——文章图片是同 URL 可变内容,绝不能进这个 location。

两次访问的资源流转对比

第一次访问
────────────────────────────────────────────
浏览器 ──HTML──► nginx ──► Go        (no-cache,每次回源)
浏览器 ──JS/CSS─► nginx ──► Go       (gzip 传输,约 127KB)
第二次访问
────────────────────────────────────────────
浏览器 ──HTML──► nginx ──► Go        (3.7KB)浏览器 ──JS/CSS─► [本地缓存 immutable]  (0 请求,0 字节)

对 315KB/s 的小带宽服务器来说,让回头客”零下载”比把首次下载再压快一点更值钱。

发布与部署

改动走完整的发布链路:

代码提交 (git)27e9cc2 perf(frontend): Vue 全按需加载
 │  e09d0e6 deploy: nginx 优化配置 + compose 调整
 ▼Docker 三阶段构建 (Vite 构建 → Go 静态编译 → Alpine 运行时)
 │ 前端阶段重建,Go 阶段命中 BuildKit 缓存
 ▼推送腾讯云 CCR: qinblog:2026-08-09-v2 / latest ▼服务器: docker compose pull + up -d qinblog + nginx -s reload
 │  storage 用命名卷,镜像升级零数据迁移
 ▼线上测速验收

最终结果

部署后复测,全部达标:

指标                  优化前              优化后
═══════════════════════════════════════════════════════
首页 JS (gzip)        1.73MB              58KB        ▼97%首屏传输合计 ≈1.8MB              ≈127KB      ▼93%HTML TTFB             190~250ms           158~172ms
TLS 握手              偶发 3.4~4.9s       ~100ms 稳定
首屏下载耗时(315KB/s)5.7s               ≈0.4s
二次访问静态资源       重复下载             本地缓存 0 请求
静态资源缓存头         无 immutable 一年
8080 端口公网暴露      是                  否

首次访问从”转圈 6 秒”变成”基本无感”,回头客秒开。 折腾完这一轮,博客终于配得上“Go 重构”时承诺的“轻、快”了。

本作品采用《CC 协议》,转载必须注明作者和本文链接
Was mich nicht umbringt, macht mich stärker
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
53
粉丝
133
喜欢
295
收藏
421
排名:77
访问:11.0 万
私信
所有博文
社区赞助商