一次博客首包优化记录

Date 2026-05-08 · Category blog · Status finished · Confidence likely
工程实践, Web 开发

为什么要做这次优化

一次博客首包优化记录 阅读导航图

这个博客的文章系统最初追求简单:构建时直接扫描所有 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.lazySuspense

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,并导出这些字段:

  • slug
  • title
  • date
  • tags
  • category
  • coverImage
  • excerpt

正文则保留动态导入:

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 只注册博客实际用到的语言:

  • bash
  • javascript
  • json
  • jsx
  • nginx
  • python
  • yaml

普通 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 CodingOAuth医学图像分割。文章多起来以后,首页筛选栏会越来越长。

新的原则是:

  • 每篇文章默认只放一个主标签。
  • 标签描述内容形态,不追求覆盖所有关键词。
  • 关键词留在标题、摘要和正文里,不全部塞进 tags。

当前保留的主标签:

  • 工程实践
  • 工具效率
  • 研究实验
  • 行业观察
  • 故障排查

未来新增文章优先归入这些大类。只有长期稳定的新方向出现时,再新增主标签。


验证

本次优化完成后做了几项检查:

  • npm run build 构建通过。
  • 本地首页可以正常打开。
  • 文章详情页可以正常异步加载正文。
  • 文章目录、代码块和 SEO 标题正常渲染。
  • 浏览器控制台没有错误。

这次改动的核心不是让某一页极限变小,而是把加载成本放回对应的访问路径上:看首页的人只下载首页需要的东西,看文章的人再下载文章需要的东西。


See also