这个博客是怎么搭起来的
终于把博客搭起来了。这篇记录一下整套技术选型和实际踩到的坑,省得以后重装环境时再翻一遍。
为什么是纯静态
博客这种东西,内容变更频率低、读者分散在全球各地,本质上就是一个「把 Markdown 渲染成 HTML 然后分发出去」的问题。
一旦接受这个前提,动态博客(WordPress 那类,PHP + 数据库)就很明显是过度设计了:
- 要维护运行环境和数据库备份
- 要操心安全补丁
- 服务器挂了站点就挂了
- 为了防住爬虫还得配缓存和 WAF
静态站点把这些全部消掉——构建产物就是一堆 HTML 文件,扔到 CDN 上,没有后端可攻击,没有数据库可炸。
技术栈
| 层 | 选型 | 理由 |
|---|---|---|
| 框架 | Astro | 默认零 JS,内容集合做类型校验 |
| 内容 | Markdown / MDX | 纯文本,好迁移,好做版本管理 |
| 搜索 | Pagefind | 构建时生成索引,无后端 |
| 评论 | Giscus | 基于 GitHub Discussions,无数据库 |
| 托管 | Cloudflare Pages | 免费、全球 CDN、自动 HTTPS |
Astro 的内容集合
Astro 5 之后内容集合改成了 loader 模式,schema 里可以拿到 image() 做图片优化:
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
schema: ({ image }) =>
z.object({
title: z.string(),
pubDate: z.coerce.date(),
heroImage: image().optional(),
}),
});
注意几个点:
z要从astro/zod引,从astro:content引虽然还能用但已经是废弃写法image()不是 import 进来的,是 schema 函数的上下文参数- 渲染方式也变了:
const { Content } = await render(entry),不是以前那种entry.render()
踩到的坑
GitHub 从这台服务器完全不连通,
create-astro的模板校验全部超时。
官方脚手架 npm create astro@latest 无论选哪个模板都会去 GitHub 拉校验,在这台机器上直接 ECONNRESET。最后的解法是跳过脚手架手写项目结构,依赖全部从 npm registry 装——registry 是通的,所以这条路完全可行。
另一个坑是中文标签的 URL 编码:
// getStaticPaths 里必须先编码,否则中文路径会 404
params: { tag: encodeURIComponent(tag) }
部署流程
日常写作的循环就三步:
npm run dev # 本地写,热更新
npm run build # 生成 dist/ 并跑 Pagefind 建索引
npm run deploy # 推到 Cloudflare Pages
没有 CI、没有服务器、没有需要守护的进程。构建产物推上去,全球 300+ 节点几分钟内就是最新的。
目前先这样跑着,等文章多起来再考虑接 Git 集成自动部署。
评论