这个博客从写下第一行 Markdown,到能在公网被任何人打开,中间串了十来项技术。这篇文章把它们逐个拆开讲清楚 —— 不只是「用了什么」,更重要的是「每一项到底解决了什么问题」,以及我在哪几个地方摔过跤。
配合作者本人的背景(日常写 C 和 Linux 系统编程,前端是业余),下面的解释会尽量往”它本质上在干什么”这个方向讲。
Table of contents
Open Table of contents
先看一张全景图
一次访问要穿过这样一条链路:
写作 → 构建 → 提交 → 部署 → 分发 → 访问
对应到技术上:
| 环节 | 用的东西 | 它解决的问题 |
|---|---|---|
| 写作 | Markdown + frontmatter | 内容和排版彻底分离 |
| 构建 | Astro(承重) + AstroPaper(骨架) + Tailwind(样式) | 把纯文本编译成静态网页 |
| 依赖 | pnpm | 装得快、装得准、锁得住 |
| 搜索 | Pagefind | 纯静态站也能做站内搜索,无需后端 |
| 版本控制 | Git + GitHub | 每次改动可追溯,顺便当部署触发器 |
| 部署 | Vercel | 推一次代码,自动构建、自动上线 |
| 域名 | DNS(腾讯云 DNSPod) | 把域名翻译成 IP |
| 传输 | HTTPS + 自动签发的证书 | 加密 + 身份验证 |
| 统计 | Vercel Analytics | 知道有多少人来过,且不用 Cookie |
| 评论 | Giscus | 借用 GitHub Discussions 当评论区 |
一项一项说。
一、Markdown:让内容不受排版绑架
Markdown 的本质是用纯文本表达结构。## 表示二级标题、**加粗** 表示强调 —— 它描述的是”这是什么”,而不是”这长什么样”。
这个区别很关键。如果你用 Word 写博客,文字和格式(字号、缩进、行距)是绑死在一起的;等哪天想换一套主题,几百篇文章就得重新排一遍。而 Markdown 文件里没有一个字节跟视觉有关,换主题只是换了一层渲染规则,内容一个字都不用动。
这也是为什么这个博客的文章能被同时输出成网页、RSS 和 sitemap —— 它们都只是同一份 Markdown 的不同渲染结果。
frontmatter:文章的”元数据”
每篇文章开头那段被 --- 夹住的 YAML,叫 frontmatter。它不属于正文,而是给程序看的结构化信息:
---
title: "文章标题"
pubDatetime: 2026-10-13T11:00:00+08:00
description: "摘要,会用在首页卡片、搜索结果和 SEO 描述里"
tags:
- 技术
draft: false
---src/content/posts/example.md
这一小段是整个博客”自动化”的基础:因为标题、时间、标签是结构化的,程序才能自动生成首页列表、标签页、归档页、RSS 和 sitemap。如果这些信息藏在一段自由文本里,机器就读不懂了。
二、Astro:把 Markdown 编译成网站
Astro 是这个博客的承重结构。它的核心工作是在构建阶段,把你写的内容渲染成一批完整的 HTML 文件。
静态生成,而不是每次现算
传统的博客系统(比如 WordPress)是请求时才现算:用户打开页面 → 服务器查数据库 → 拼 HTML → 返回。这要求服务器一直在线,并且每次访问都要付出这部分计算。
我的做法相反:在构建的时候一次性算完,产出纯静态的 HTML,然后丢给 CDN。用户来访问时,拿到的是已经生成好的文件,中间没有任何程序在跑。好处是显而易见的 —— 没有数据库、没有后端进程、没有要维护的东西,快且几乎不可能崩。
代价是:内容变了必须重新构建。对博客这种更新频率的东西,这个代价可以忽略。
岛屿架构:默认零 JavaScript
前端圈这两年流行”什么都在浏览器里跑”,代价是首屏要下载几百 KB 的 JS 才能看见内容。Astro 反其道而行:默认一个字节的 JS 都不发,页面就是纯粹的 HTML + CSS。
需要交互的组件(这个主题里只有主题切换、搜索框这些)才被单独”激活”,Astro 管这叫岛屿架构(Islands Architecture)—— 静态的海洋里浮着几个需要跑 JS 的小岛。
对一个文字为主的博客来说,这几乎是最优解:文章页打开就是”立刻”。
内容集合与类型校验
Astro 的 Content Collections 让我能给 frontmatter 定义 schema,写错了会直接报错,而不是等到线上出现空白页面:
const posts = defineCollection({
loader: glob({ pattern: "**/[^_]*.{md,mdx}", base: "./src/content/posts" }),
schema: ({ image }) =>
z.object({
author: z.string().default(config.site.author),
pubDatetime: z.date(),
title: z.string(),
draft: z.boolean().optional(),
tags: z.array(z.string()).default(["others"]),
description: z.string(),
}),
});src/content.config.ts
上面这段里有个细节值得单独说:glob 模式写的是 **/[^_]*.{md,mdx},意思是文件名以 _ 开头的 markdown 会被完全忽略。我正是靠这条规则,在文章目录里放了一个 _template.md 当发文模板 —— 它永远不会被当成正文发布出去。
三、AstroPaper:一套现成的骨架
AstroPaper 是社区里一套成熟的博客主题。它提供的不是”库”,而是一整套已经写好的页面和组件:首页、文章列表、标签页、归档页、404、RSS 生成、sitemap、暗色模式、动态 OG 图……
我选择它而不是从零写,理由很朴素:我想写文章,不想设计网页。
用现成主题有个必须知道的前提:它的配置不一定都在配置文件里。
我踩过这个坑 —— 首页的大标题和简介是硬编码在 src/pages/index.astro 里的,改 astro-paper.config.ts 完全不起作用,导致首页很长一段时间显示着主题自带的模板文案。换主题时,务必先确认哪些内容是”可配置的”,哪些是”写死的”。
四、Tailwind CSS:样式写在标签上
Tailwind 的思路是把 CSS 拆成一个个极小的原子类,直接写在 HTML 标签上:
<h1 class="text-2xl font-bold sm:text-3xl">标题</h1>
乍看很反直觉(这不是把样式和结构又混在一起了吗),但实际用下来有两个好处:改样式时不用在文件之间跳来跳去,以及不用再为类名发愁。
这个主题用的是 Tailwind v4,配置方式已经变成”在 CSS 里写 @theme”而不是 JS 配置文件。配合 CSS 变量,暗色模式的切换就只是换一组变量值 —— 不用维护两套样式表。
五、pnpm:快、省,而且严格
pnpm 是包管理器,和 npm 干同一件事,但有两个本质区别。
一是存储方式:npm 会把每个项目的依赖各存一份;pnpm 用一个全局的内容寻址仓库,项目里通过硬链接指过去。十个项目用同一个库,磁盘上只有一份实体。
二是对锁文件的态度:pnpm-lock.yaml 记录的是精确到版本和哈希的依赖树。CI 环境里默认加 --frozen-lockfile,意思是”锁文件和 package.json 必须完全一致,否则直接构建失败”。
这个严格性是双刃剑。我就被它拦住过一次:往 package.json 里加了依赖,但没同步更新 pnpm-lock.yaml,Vercel 直接以 ERR_PNPM_OUTDATED_LOCKFILE 报错退出。
教训:换包管理器必须同步换锁文件。 这个博客的约定是——新增依赖一律用 pnpm add,绝不用 npm install 生成 package-lock.json。
六、Pagefind:纯静态站也能搜索
前面说了这个站没有任何后端,那搜索框怎么办?
Pagefind 的思路很巧妙:它在构建完成之后,去扫描生成的 HTML 文件,离线建立一份索引,然后在浏览器里加载这份索引完成搜索。整个过程没有服务器参与。
代价是两条:索引是构建产物,新增文章必须重新构建才会被搜到;以及只有带有 data-pagefind-body 标记的页面才会被索引 —— 这样导航栏、页脚之类的重复内容就不会污染搜索结果。
七、Git + GitHub:不只是代码仓库
Git 负责版本控制,这部分没什么可说的。真正有意思的是它在部署链路里扮演的角色:GitHub 仓库是整条流水线的触发源 —— 每次往 main 分支推送,就自动触发一次构建部署。
也就是说,“提交代码”和”发布网站”被我合并成了同一个动作。
顺带记录一个网络层的坑:我这台机器上 GitHub 的 HTTPS(443) 和 SSH(22) 都连不上,最后是走 SSH over 443(ssh.github.com:443)才通的。如果你的网络环境也这样,~/.ssh/config 里加一段 Host github.com / Hostname ssh.github.com / Port 443 就能解决。
八、Vercel:从推送到上线
Vercel 是整条链路的部署平台。我把 GitHub 仓库连上去之后,它做的事是:
- 监听到
main分支有新提交 - 拉取代码,自动识别出这是个 Astro 项目
- 跑安装和构建命令
- 把产物分发到全球边缘节点
- 绑定我的自定义域名并签发证书
实测数据(就用这篇文章的发布来测的):推送后 3 秒被接住,46 秒构建完成并上线。 整个过程我没有点过任何一个按钮。
Vercel 还会给每次部署分配一个 xxx.vercel.app 的临时地址,用来预览。但要注意:那只是默认域名,不是你的门牌号。真正对外的地址是你自己绑的域名,两者是完全独立的两回事。
九、DNS:这本”电话簿”的两个关键记录
DNS 是这整条链路里最容易被忽视、但最需要理解的一层。它的工作就一句话:把域名翻译成 IP 地址。因为人是靠名字记东西的,而机器只认 IP。
配置时只需要两条记录,但它们的机制完全不同:
A 记录:直接给出 IP
happyfenglife.club → 216.198.79.1
A 记录的答案是一个 IP 地址,一次查询就拿到终点,直接连过去。代价是 IP 被写死在 DNS 里 —— Vercel 那边要换 IP,得我去 DNS 后台手动改。
CNAME 记录:给出一个”别名”
www.happyfenglife.club → <项目标识>.vercel-dns-017.com → 216.198.79.65
CNAME 的答案不是 IP,而是另一个域名。所以查询要绕一圈:先问 www,得到”你去找那个名字”,再查一次才拿到 IP。
好处是:IP 是什么由 Vercel 决定,我完全不用管。 它想换就换。
那 apex 为什么不能用 CNAME?
因为 DNS 协议规定,每个域名的顶点(happyfenglife.club 本身)天生就必须携带 NS 和 SOA 两种记录,而 CNAME 的规则恰恰是”这个名字上只能有它自己”。两条规定直接冲突,所以顶点只能填 A 记录。
而 www 只是个普通子域,不背这个包袱,所以可以用 CNAME。
这也是 Vercel 官方推荐”用 www 作主域名、apex 重定向过去”的原因:走 CNAME 路径能让 CDN 对流量有更强的调度能力。不过 apex 作主域同样是被支持的(靠 Anycast 做就近路由),这个博客最后就选了 apex —— 域名短一点,看着舒服。
排查 DNS 时有个容易犯的错误:不要只用一个解析器就下结论。 改完记录后,我实测发现阿里 DNS 还在返回旧 IP(缓存没到期),而 DNSPod 已经是新值了。递归解析器各自按 TTL 缓存,多个解析器交叉验证才可靠。
十、HTTPS 与证书:你什么都没做,它就上线了
现在的浏览器对没有 HTTPS 的站点会直接标”不安全”。但这件事我一行配置都没写 —— Vercel 在域名验证通过后自动向 Let’s Encrypt 申请证书,并且自动续期。
背后是 ACME 协议在起作用:平台需要证明”我确实控制这个域名”,然后就能拿到证书。证书同时覆盖 apex 和 www 两个名字,所以两个地址都是绿的。
站点还带了 HSTS 响应头(max-age=63072000,即两年),意思是告诉浏览器”以后访问这个域名只准用 HTTPS”,从根上杜绝降级攻击。
十一、Vercel Analytics:无 Cookie 的访问统计
我想知道有多少人来看过,但不想为此引入 Cookie 和随之而来的隐私声明麻烦。
Vercel Analytics 是 cookieless 的:它不往用户浏览器里写任何标识,而是用匿名化的请求特征做聚合统计。所以这个站没有、也不需要那个烦人的”本网站使用 Cookie”横幅。
十二、Giscus:把评论区”外包”给 GitHub
评论区通常是博客里唯一需要后端的东西 —— 要存评论、要防垃圾广告、要审核。
Giscus 的思路很取巧:它把评论直接存进仓库的 GitHub Discussions。 文章的评论区本质上就是一条 Discussion,读者用 GitHub 账号登录后留言。存储、鉴权、通知全部由 GitHub 负责,我这边零成本、零维护。
前提条件是两条:仓库必须是公开的,以及必须开启 Discussions 功能。
这套架构的代价
说好处说了一路,也得把代价讲清楚:
- 内容改动需要重新构建 —— 静态站点的天然约束。适合博客,不适合内容每秒都在变的场景。
- 没有服务端逻辑 —— 需要登录、需要实时数据、需要调用私有 API 的功能做不了(或者说,得额外引入别的东西)。
- 依赖平台 —— 构建、分发、证书、统计全绑在 Vercel 上。好在有 Git 仓库在,迁移成本不算高。
- 评论和统计是第三方 —— 它们的可用性不由你控制。
一句话总结:这套方案是为”以文字为主的个人博客”量身定做的,换个场景就未必合适。
小结
回头看,这十来项技术其实各自只做一件很小的事:Markdown 管内容、Astro 管编译、Pagefind 管搜索、Git 管版本、Vercel 管构建和分发、DNS 管寻址、证书管加密。
真正的巧妙之处不在任何单项,而在于它们能首尾相接成一条完整的流水线 —— 我在本地写一个 .md 文件,执行一条 git push,几十秒后全世界就能访问。中间没有一步需要我去点按钮。
这也是我这次搭博客最大的收获:好的架构不是把问题解决得多么聪明,而是让正确的路径变得足够短。
如果这篇对你有点用,欢迎在下方留言聊聊你的方案。