我的博客架构长什么样
很多人好奇这个博客是怎么搭的.
看起来很简单,但背后东西不少.算不上大工程,但从工程角度确实折腾了不少东西.双语架构,自动化构建, PWA, Google SEO 等等.
所以打算写个系列,把现在的这套东西记录下来.用什么,为什么这么选,代码怎么写的,性能上怎么考虑的.
整体架构
Markdown 内容 (中英双语)
↓
Hugo SSG (extended, v0.164)
↓
自定义主题 (fork cactus-dark, 大改)
↓
Gulp Pipeline
├── hugo --minify
└── Workbox generateSW
↓
Cloudflare Pages (GitHub Actions CI)
三层: 内容层, 构建层, 部署层. 没有数据库, 没有后端, 全是静态文件.
为什么用 Hugo
我是前端, 喜欢用前端圈的东西. Hugo 是用 Go 写的, 文章多了以后构建速度就是快. 两百多篇文章, 几秒就构建完了. 这就是核心原因.
构建快了, 更新博客的心理负担就小. 这是一个正反馈.
Go 模板用起来也挺顺手, 变量作用域, 管道语法, partial 系统, 该有的都有. 不过刚开始接触的时候, {{ range }} 和 {{ with }} 的区别可能会绕一下.
为什么用 Gulp
Hugo 只管静态生成. 但我还需要集成 service worker 的生成和一些后处理. Gulp 就是用来干这个的. 不需要复杂的构建系统, 几个任务串起来就行.
整个 gulpfile.js 大概五十行:
const gulp = require("gulp");
const shell = require('gulp-shell');
const workbox = require('workbox-build');
gulp.task('build', () => gulp.src('./').pipe(shell(['hugo --buildFuture --minify'])));
gulp.task('generate-service-worker', () => {
return workbox.generateSW({
globDirectory: './public',
globPatterns: ['**/*.{woff2,woff,js,css,png.jpg}'],
globIgnores: ['sw.js'],
swDest: `./public/sw.js`,
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [
{ urlPattern: /.*\.js/, handler: 'NetworkFirst' },
{ urlPattern: /.*\.css/, handler: 'StaleWhileRevalidate' },
{ urlPattern: /.*\.(?:png|jpg|jpeg|svg|gif)/, handler: 'CacheFirst',
options: { cacheName: 'images', expiration: { maxEntries: 50 } } },
{ urlPattern: /.*\.html/, handler: 'NetworkFirst' }
]
})
.then(() => console.info('SW 生成完成'))
.catch(error => console.warn('SW 生成失败: ' + error));
});
gulp.task("default", gulp.series('build', 'generate-service-worker'));
两个任务, 串行执行. 先构建, 再生成 SW.
为什么用 Cloudflare Pages
静态站点零配置, 全球 CDN, 免费额度够用. GitHub Actions 集成很简单, push 到 master 自动构建部署. 唯一要提一下: 第一次部署的时候, Cloudflare Pages 的项目名不能随便改, 改了之后域名映射要重新配, 有点蛋疼.
- name: Deploy to Cloudflare Pages
run: npx wrangler pages deploy public --project-name=time-friend
CI 文件一共 35 行. 装 Hugo, 装依赖, 构建, 部署.
性能优化
构建速度
Hugo 的构建速度跟文章数量关系不大, 始终是秒级. --minify 会把 HTML 层面的压缩做掉, 不需要额外处理.
页面加载
纯静态 HTML, 没有服务端渲染开销. 主题 CSS 大概 20KB, 加载方式很直接. 默认没有阻塞渲染的 JS, 脚本都在页面底部的 js.html partial 里加载.
缓存策略
Service Worker 对不同资源做了不同的缓存策略. 具体每种策略为什么这么选, 单独写了一篇: 如何让博客支持离线访问.
第一次访问后, 大部分资源都从缓存走了, 后面的导航几乎是秒开.
Google SEO
每篇文章在构建时自动注入 JSON-LD Article 结构化数据. 在 head.html 里:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "{{ .Title }}",
"datePublished": "{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}",
"dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}",
"author": { "@type": "Person", "name": "{{ .Site.Params.author }}" },
"description": "{{ .Description }}",
"url": "{{ .Permalink }}"
{{ with .Params.tags }}
,"keywords": "{{ delimit . ", " }}"
{{ end }}
}
</script>
Google Search Console 通过 DNS 记录验证. Sitemap 由 Hugo 自动生成. 每页有 canonical URL. 就这些, 其他搜索引擎不搞.
系列文章
全部写在一起了, 没有固定顺序, 方便随时加:
- 概览 (这篇) — 架构概览, 技术选型, 为什么这么选
- 首页打字效果 — Typed.js, quotes.json, 实现逻辑
- i18n 国际化 — 内容国际化 + 界面国际化, translationKey, module mounts
- 主题定制 — 覆盖了哪些模板, 加了什么功能
- 构建流水线 — Gulp 任务详解, CI/CD
- Google SEO — 结构化数据, meta 标签, sitemap, 验证
- 部署 — Cloudflare Pages 配置, 环境管理, 域名
- 评论系统 — Giscus 对接, GitHub Discussions, 完整流程
- 页面统计 — Busuanzi + Google Analytics 两套方案
- Service Worker — 什么是 SW, 缓存策略, 注册流程