一次博客首包优化记录
为什么要做这次优化

这个博客的文章系统最初追求简单:构建时直接扫描所有 Markdown 文件,解析 frontmatter 和正文,然后在首页、新闻页和文章页复用同一份数据。
文章少的时候,这种方式足够直接。但它有一个明显问题:所有文章正文都会被打进首包。即使用户只打开首页,也要下载尚未阅读的 Markdown 正文、文章页渲染逻辑和代码高亮依赖。
后续内容会继续增加,所以这次优化的目标很明确:
- 首页和列表页只加载文章元信息。
- 进入文章详情页后再加载对应正文。
- 代码高亮只在文章页遇到代码块时加载。
- 页面路由拆分,减少首次进入站点的 JavaScript 体积。
- 保留现有 Markdown 写作方式和文章 URL。
路由级懒加载
第一步是把页面按路由拆开。
原来 src/App.jsx 会同步引入所有页面组件:
import Home from './pages/Home'
import Post from './pages/Post'
import News from './pages/News'
import About from './pages/About'
优化后改成 React.lazy 和 Suspense:
const Home = lazy(() => import('./pages/Home'))
const Post = lazy(() => import('./pages/Post'))
const News = lazy(() => import('./pages/News'))
const About = lazy(() => import('./pages/About'))
这样首次进入首页时,不再同步加载文章页、新闻页、关于页等页面代码。
文章元数据与正文拆分
原来的文章加载方式是:
const postModules = import.meta.glob('../posts/*.md', {
query: '?raw',
import: 'default',
eager: true,
})
eager: true 会让所有 Markdown 在构建时同步进入同一个依赖链。为了让首页只拿到必要信息,我在 vite.config.js 新增了一个 post-metadata 虚拟模块。
构建时它只读取每篇文章的 frontmatter,并导出这些字段:
slugtitledatetagscategorycoverImageexcerpt
正文则保留动态导入:
const postModules = import.meta.glob('../posts/*.md', {
query: '?raw',
import: 'default',
})
getPostBySlug(slug) 现在变成异步函数。只有用户进入 /post/:slug 时,才加载对应的 Markdown 正文。
文章页正文懒加载
文章页增加了加载状态:
const [post, setPost] = useState(null)
const [loading, setLoading] = useState(true)
当路由参数变化时,文章页会异步调用 getPostBySlug(slug)。正文加载完成后,再生成目录、SEO 信息和 Markdown 内容。
这意味着首页只需要文章卡片数据,不再提前下载所有正文。
代码高亮按需加载
技术文章里经常有代码块,但不是每个访问者都会进入文章页,也不是每篇文章都需要完整高亮能力。
原来文章页直接引入完整高亮库:
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
优化后改为懒加载高亮组件,并使用 prism-light 只注册博客实际用到的语言:
bashjavascriptjsonjsxnginxpythonyaml
普通 text 代码块不触发高亮库加载,直接用原生 <code> 渲染。
构建结果
优化前生产构建:
| 项目 | 数值 |
|---|---|
| 主 JS | 1,394.38 kB |
| gzip | 471.74 kB |
| 转换模块 | 2716 |
| 构建耗时 | 约 19.24s |
优化后生产构建:
| 项目 | 数值 |
|---|---|
| 首屏入口 JS | 151.71 kB |
| 首屏入口 gzip | 18.63 kB |
| 首页 chunk | 10.84 kB |
| 首页 gzip | 3.88 kB |
| 文章页 chunk | 438.92 kB |
| 文章页 gzip | 133.60 kB |
| 高亮核心 chunk | 23.75 kB |
| 高亮核心 gzip | 8.03 kB |
| 转换模块 | 1989 |
| 构建耗时 | 约 2.87s |
文章页 chunk 仍包含 Markdown 渲染、数学公式和目录逻辑,但它只在进入文章详情页时加载,不再影响首页首包。
标签也顺手收敛
这次优化后,我也调整了博客标签策略。
旧标签偏细,例如 AI建站、Vibe Coding、OAuth、医学图像分割。文章多起来以后,首页筛选栏会越来越长。
新的原则是:
- 每篇文章默认只放一个主标签。
- 标签描述内容形态,不追求覆盖所有关键词。
- 关键词留在标题、摘要和正文里,不全部塞进 tags。
当前保留的主标签:
工程实践工具效率研究实验行业观察故障排查
未来新增文章优先归入这些大类。只有长期稳定的新方向出现时,再新增主标签。
验证
本次优化完成后做了几项检查:
npm run build构建通过。- 本地首页可以正常打开。
- 文章详情页可以正常异步加载正文。
- 文章目录、代码块和 SEO 标题正常渲染。
- 浏览器控制台没有错误。
这次改动的核心不是让某一页极限变小,而是把加载成本放回对应的访问路径上:看首页的人只下载首页需要的东西,看文章的人再下载文章需要的东西。
See also
- 一次完整的 WSL2 安装排障:从 BIOS、TPM 判断到 Windows 11 修复安装 2026-07-07
- 把一个 SPA 博客补成可预渲染、可同步、可持续部署 2026-05-29
- 把宿舍 Windows 主机改成可远程训练的 WSL 工作站 2026-05-27
- 把每日科技日报改成服务器自运行 2026-05-26
- Chrome 一打开就跳到 360 导航页?按这份手册一步步修复 2026-05-20