从 0 到 1 搭建这个博客:选型与部署全记录
本文是一篇完整的实战记录,分两部分:
- 第一部分:完整的选型分析(为什么是 Astro + Cloudflare Pages)
- 第二部分:从 0 到 1 的实际搭建与部署全过程(所有命令都在本机实测执行过)
全程由 Agent 在命令行完成,因此把每一步的命令和决策都原样记录,方便复现,也给自己留一份操作档案。
第一部分:选型 —— 为什么是 Astro + Cloudflare Pages
一、需求与约束
这次选型的核心目标不是单纯”把博客跑起来”,而是兼顾长期维护和服务器资源规划:
- 拥有独立域名和可迁移的博客内容
- 尽量少占用云服务器的常驻内存
- 云服务器后续还要部署其他网站
- 使用 Markdown 写作,并用 Git 管理内容
- 支持现代化样式、深色模式和一定的交互能力
- 不在第一版维护数据库、后台程序和复杂容器栈
在这些约束下,静态站点比 WordPress、Halo、Ghost 等动态 CMS 更合适。
二、静态站点到底”静态”在哪里
“静态”不是指页面不能动、不能搜索或不能评论,而是指:
访客访问页面时,托管平台直接返回提前生成好的 HTML、CSS 和 JavaScript 文件,不需要现场查询数据库并生成 HTML。
content/posts/hello.md
↓ Astro 构建
dist/posts/hello/index.html
↓ 访客访问
Cloudflare Pages 直接返回 index.html
页面仍可通过浏览器端 JavaScript 实现深色模式、搜索、图片灯箱、代码复制、动画、评论和阅读进度。
SSG、SSR、CSR 与 ISR
| 模式 | 全称 | HTML 生成时机 | 运行时要求 | 典型场景 |
|---|---|---|---|---|
| SSG | Static Site Generation | 构建、发布时提前生成 | 否 | Hugo、Hexo、Astro 静态模式 |
| SSR | Server-Side Rendering | 每次请求到达服务器时生成 | 需要服务端或边缘运行时 | Next.js SSR、Astro Server 模式 |
| CSR | Client-Side Rendering | 浏览器下载 JavaScript 后生成 | 通常不需要渲染服务 | React/Vue 单页应用 |
| ISR | Incremental Static Regeneration | 先生成,过期后按需更新 | 需要平台或运行时支持 | 内容量较大的现代网站 |
可以用做饭来类比:
SSG:客人来之前,饭已经做好
SSR:客人点单后,服务器现场做饭
CSR:服务器给食材,由客人的浏览器做饭
Astro 默认采用静态输出,因此本方案属于 SSG,而不是 SSR。
三、动态 CMS 与静态方案的初步筛选
| 方案 | 主要优点 | 主要代价 | 是否符合本次目标 |
|---|---|---|---|
| Halo | 中文后台、主题插件、Markdown/富文本编辑 | Java 应用和数据库长期占用内存 | 一般 |
| Ghost | 写作体验好,支持会员与 Newsletter | Node.js、MySQL;使用邮件功能时还需 SMTP | 一般 |
| WordPress | 插件和主题生态最大 | PHP、数据库、插件安全更新较重 | 不优先 |
| Hugo | 构建极快、依赖少、长期稳定 | 中文主题和现代组件能力相对有限 | 符合 |
| Hexo | 中文博客生态强,Butterfly 等主题成熟 | npm 依赖较多,构建速度不如 Hugo | 符合 |
| Astro | 静态输出、现代组件化、扩展空间最大 | 相比传统博客生成器需要更多前端开发 | 最符合 |
如果目标是登录后台直接写文章,Halo 更方便;如果目标是低资源占用、Git 管理和长期可迁移,静态方案更有优势。
四、Hugo、Hexo 与 Astro 对比
| 维度 | Hugo | Hexo | Astro |
|---|---|---|---|
| 技术基础 | Go | Node.js | Node.js / TypeScript |
| 默认产物 | 静态 HTML | 静态 HTML | 静态 HTML |
| 线上常驻内存 | 接近 0 | 接近 0 | 静态模式接近 0 |
| 构建速度 | 最快 | 中等 | 中等 |
| 博客开箱程度 | 高 | 很高 | 中等 |
| 中文主题生态 | 一般 | 很强 | 相对较少 |
| UI 深度定制 | Hugo 模板 | 主题、Pug、Stylus | 组件化开发,能力最强 |
| 依赖复杂度 | 最低 | 中等 | 中等 |
| 适合人群 | 内容与稳定性优先 | 快速套成熟博客主题 | 现代化、个性化网站 |
1. Hugo
Hugo 通常以单个可执行文件运行,不依赖庞大的 node_modules,适合追求构建速度和长期稳定的人。
- 优点:构建速度快(文章很多时优势明显);依赖链短,升级和安全风险较低;静态产物容易部署到任何 Web 服务器。
- 不足:中文主题和教程不如 Hexo 丰富;深度修改主题需要学习 Hugo 模板语法;现代前端组件和交互扩展不如 Astro 自然。
2. Hexo
Hexo 专门面向博客,中文生态成熟。
- 优点:分类、标签、归档、友链等博客功能成熟;Butterfly 等中文主题开箱即用;中文教程和社区资料丰富。
- 不足:npm 依赖和主题插件之间可能存在版本兼容问题;构建速度和依赖体积不如 Hugo;高度定制时容易进入主题”魔改”模式。
3. Astro
Astro 默认生成静态 HTML,也可以按需嵌入 React、Vue、Svelte 等交互组件。
- 优点:默认 SSG,不需要线上 Node.js 常驻进程;可以只给需要交互的部分发送 JavaScript;TypeScript、组件化和现代 CSS 工具支持好;后续可从博客扩展到个人主页、作品集或交互页面;构建后的
dist/可部署到 Pages,也可迁回自己的服务器。 - 不足:博客主题生态不如 Hexo;需要理解页面布局、内容集合和组件结构;如果以后切换到 SSR,需要额外的服务端、边缘或 Serverless 运行时;若部署在自有服务器,还会增加常驻内存开销。
4. 选择结论
- 极致稳定、构建最快:Hugo
- 想直接获得 Butterfly 风格:Hexo
- 希望兼顾静态博客和未来前端扩展:Astro
最终选择 Astro。原因不是它比 Hugo、Hexo 更省线上内存——三者静态部署后几乎相同——而是 Astro 在保持静态部署的同时提供了更大的界面和功能扩展空间。
五、云服务器、GitHub Pages 与 Cloudflare Pages
生成器和托管平台属于不同层次:
Hugo / Hexo / Astro:负责生成网站
云服务器 / GitHub Pages / Cloudflare Pages:负责托管网站
| 维度 | 自有云服务器 | GitHub Pages | Cloudflare Pages |
|---|---|---|---|
| 费用 | 已购买,但消耗资源和带宽 | 免费 | 免费额度足够个人博客 |
| 自定义域名 | 支持 | 支持 | 支持 |
| HTTPS | 自己配置 Caddy/Nginx | 自动 | 自动 |
| 构建方式 | 本地或 CI 构建后上传 | GitHub Actions | Git 集成自动构建 |
| 占用本机内存 | 静态托管增量很低 | 0 | 0 |
| 占用本机带宽 | 是 | 否 | 否 |
| 私有源码仓库 | 自己决定 | 免费账户的 Pages 能力受仓库类型限制 | 可连接私有 GitHub 仓库 |
| 预览部署 | 需要自己实现 | 可通过 Actions 实现 | 原生支持 |
| 动态扩展 | 可自由部署后端 | 基本没有 | 可接 Workers / Pages Functions |
为什么选择 Cloudflare Pages
- 不消耗自己的 2C4G 服务器内存
- 不占用服务器有限的 5 Mbps 带宽
- Git 推送后自动构建、发布
- 免费提供 HTTPS 和自定义域名
- 可以连接 GitHub 私有仓库
- 支持预览每次修改
- 静态产物标准化,随时可以迁回云服务器
六、域名、子域名与镜像
1. 一个域名可以创建多个子域名
不需要为主站和镜像分别购买两个域名。购买一个 example.com 后,可以创建:
| 子域名 | 用途 | 指向 |
|---|---|---|
blog.example.com | 博客主站 | Cloudflare Pages |
mirror.example.com | 博客镜像 | GitHub Pages 或云服务器 |
comment.example.com | 评论 API | 自有云服务器 |
www.example.com | 个人主页 | 任意平台 |
子域名通常不需要额外购买。
2. 什么是博客镜像
博客镜像是同一份静态构建产物部署到多个地址:
同一份 Git 仓库
↓ 构建
dist/
├── Cloudflare Pages 主站
├── GitHub Pages 镜像
└── 自有云服务器镜像
镜像主要用于故障备用、比较访问质量和避免单个平台不可用。它不是 Docker 镜像,也不等于完整灾备;Git 仓库和文章源码仍应额外备份。
七、最终架构:Astro + Cloudflare Pages
本地编写 Markdown / MDX
↓
提交到 GitHub 私有仓库
↓
Cloudflare Pages 检测到 push
↓
平台安装依赖并执行 npm run build
↓
Astro 将内容构建到 dist/
↓
Cloudflare 全球静态网络提供访问
↓
blog.example.com + 自动 HTTPS
Cloudflare Pages 基本构建配置:
Build command: npm run build
Build output directory: dist
Astro 保持默认静态模式:
import { defineConfig } from 'astro/config'
export default defineConfig({
output: 'static'
})
不要使用 output: 'server',除非未来确实需要请求时动态渲染,并愿意承担运行时服务和额外维护成本。
八、资源占用分析
1. 构建放在 Cloudflare 时
Astro 构建所需 CPU/内存:由 Cloudflare 承担
静态文件存储和流量:由 Cloudflare 承担
自己的云服务器内存:0
自己的云服务器带宽:0
这能把 2C4G 云服务器完整留给以后真正需要后端运行时和数据库的网站。
2. 如果以后迁回云服务器
只需将 dist/ 上传,通过一个共享的 Caddy 或 Nginx 进程托管多个目录:
Caddy/Nginx
├── blog.example.com → /var/www/blog
├── site-a.example.com → /var/www/site-a
└── site-b.example.com → /var/www/site-b
多个静态站不需要分别启动多个 Node.js 服务,新增站点的常驻内存成本很低。
3. 容易被忽略的动态服务
| 功能方案 | 是否占用自有服务器 |
|---|---|
| Giscus(GitHub Discussions) | 否 |
| Gitalk(GitHub Issues) | 否 |
| Artalk 自托管 | 是 |
| Waline 自托管 | 是 |
| Astro 本地静态搜索 | 否 |
| Meilisearch 等独立搜索 | 是 |
为保持轻量,第一版可使用 Giscus 或暂时不加评论,搜索使用构建时索引或浏览器端本地搜索。
九、第一版功能边界
第一版实现:
- Markdown/MDX 文章
- 首页、文章列表与文章详情
- 标签或分类
- 代码高亮与深色模式
- RSS、Sitemap、SEO 和 Open Graph
- 响应式布局
- Cloudflare Pages 自动部署
- 自定义域名与 HTTPS
后续再加:
- Giscus 评论与全文搜索
- 图片灯箱和阅读进度
- 访问统计、友链、归档和作品集
- Cloudflare R2 图片存储
暂不引入:
- SSR、数据库和用户登录
- 自建管理后台
- 自建搜索集群
- 复杂微服务或容器编排
十、实施清单
项目初始化
- 创建 Astro 项目
- 确认
output: 'static' - 规划
src/content文章集合 - 选定基础主题或页面风格
- 建立 Markdown Frontmatter 规范
博客基础能力
- 首页、文章列表与文章详情
- 代码高亮、标签、分类或归档
- RSS、Sitemap 和 404 页面
- SEO、Canonical URL、Open Graph
- 深色模式与移动端适配
部署
- 源码推送到 GitHub 私有仓库
- Cloudflare Pages 连接仓库
- 构建命令设为
npm run build - 输出目录设为
dist - 绑定自定义子域名
- 验证 HTTPS、重定向和预览部署
运维与备份
- 本地保留 Git 仓库副本
- 定期同步到第二个备份位置
- 图片压缩为 WebP/AVIF
- 不存放大视频和大量原图
- 固定 Node.js 与依赖版本
- 定期检查构建日志和失效链接
第二部分:从 0 到 1 的实际搭建过程
以下是本机实际执行的全过程,所有命令都可复现。
一、环境准备
本机已有 Node.js 和 npm(Node v24.19.0 / npm 11.17.0),Git 已配好 SSH 密钥访问 GitHub,网络走本地 Clash 代理(127.0.0.1:7890 / 7891)。
node -v # v24.19.0
npm -v # 11.17.0
ls ~/projects # 工作目录
二、创建 Astro 项目
用 Astro 官方 blog 模板(自带文章集合、RSS、sitemap、深色模式)。
cd ~/projects
npm create astro@latest blog -- --template blog --install --no-git --yes
生成的目录结构(关键部分):
blog/
├── astro.config.mjs # 站点配置
├── package.json
├── src/
│ ├── content/blog/ # ▸ Markdown/MDX 文章存放处
│ ├── content.config.ts # 内容集合 schema
│ ├── consts.ts # 站点标题/描述
│ ├── layouts/BlogPost.astro
│ ├── pages/ # 首页、列表页、rss.xml.js
│ ├── styles/global.css
│ └── components/
└── public/ # 静态资源
三、本地配置与定制
1. 确认静态模式(SSG)
在 astro.config.mjs 显式声明 output: 'static':
export default defineConfig({
site: 'https://example.com', // 之后替换成真实域名
output: 'static', // SSG:构建时生成静态 HTML,不部署 Node 常驻进程
integrations: [mdx(), sitemap()],
});
2. 站点元信息
在 src/consts.ts 里改为本站标题和描述:
export const SITE_TITLE = '肖飞的博客';
export const SITE_DESCRIPTION = '记录 AI 与开发学习笔记的个人博客。';
3. 内容集合加标签字段
在 src/content.config.ts 的 schema 里加可选 tags 数组:
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
tags: z.array(z.string()).optional(),
heroImage: z.optional(image()),
4. 列表页和文章页渲染标签
列表页 src/pages/blog/index.astro——每个文章卡片日期下方显示标签:
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
{post.data.tags && (
<p class="tags">
{post.data.tags.map((tag) => <span class="tag">#{tag}</span>)}
</p>
)}
配套样式:
.tags { margin: 0.25rem 0 0; display: flex; gap: 0.5rem; flex-wrap: wrap; }
.tags .tag { font-size: 0.8rem; color: rgb(var(--gray)); background: rgba(var(--gray), 0.12); padding: 0.1rem 0.5rem; border-radius: 999px; }
文章页 src/layouts/BlogPost.astro——在标题下方同样显示标签(从 Props 解构 tags 即可)。
5. 给示例文章加标签
在 src/content/blog/first-post.md 的 frontmatter 里加上:
tags: ['astro', 'blog', '入门']
四、本地构建验证
cd ~/projects/blog
npm run build
输出在 dist/ 生成静态文件(首页、/blog/... 文章页、rss.xml、sitemap 等):
✓ Completed in 1.14s
[@astrojs/sitemap] `sitemap-index.xml` created at `dist`
[build] 8 page(s) built in 2.29s
用 grep 验证标签已渲染进产物:
grep -o 'class="tags"' dist/blog/index.html
grep -o '#astro' dist/blog/index.html # → 找到标签
五、git 初始化与提交
cd ~/projects/blog
git init -b main
git add -A
git commit -m "init: Astro blog (static SSG) - content collections, tags, rss/sitemap"
六、安装 gh CLI 并登录 GitHub
为了在命令行创建仓库和推送,安装 GitHub CLI(gh),并走 OAuth 设备码授权登录(headless 环境的标准方式)。
安装 gh(二进制,免 sudo)
cd /tmp
VER=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'].lstrip('v'))")
curl -sL -o gh.tgz "https://github.com/cli/cli/releases/download/v${VER}/gh_${VER}_linux_amd64.tar.gz"
tar xzf gh.tgz
mkdir -p ~/.local/bin
cp gh_${VER}_linux_amd64/bin/gh ~/.local/bin/
chmod +x ~/.local/bin/gh
export PATH="$HOME/.local/bin:$PATH"
gh --version # gh version 2.98.0
设备码授权登录
调用 GitHub 的设备码接口拿到一次性代码,用户在浏览器打开 https://github.com/login/device 输入即可完成授权(授权范围:repo, read:org, gist)。授权成功后 gh 会把 token 写入 ~/.config/gh/hosts.yml:
gh auth status
# ✓ Logged in to github.com account Starflyr
# Git operations protocol: ssh
💡 提示:这台机器之前配的 GitHub 账号是
Xiao198,后来改名为Starflyr(显示名Starflyer,与域名starflyer.top呼应),所以登录账号是Starflyr。
七、创建私有仓库并推送
设置 git 提交身份与账号一致,然后一键创建私有仓库并推送:
git config --global user.name "Starflyer"
git config --global user.email "Starflyr@users.noreply.github.com"
cd ~/projects/blog
gh repo create Starflyr/blog --private --source=. --remote=origin --push \
--description "Personal blog built with Astro (static SSG)"
成功输出:
https://github.com/Starflyr/blog
To github.com:Starflyr/blog.git
* [new branch] HEAD -> main
branch 'main' set up to track 'origin/main'.
八、Cloudflare Pages 部署
-
登录
dash.cloudflare.com→ Workers & Pages → Pages → Connect to Git -
授权 Cloudflare 访问 GitHub,选择账号 Starflyr,选中仓库
blog -
构建配置:
项 值 Framework preset AstroBuild command npm run buildBuild output directory dist -
点 Save and Deploy,Cloudflare 自动拉取仓库、构建、发布
⚠️ 小坑:第一次很容易点进 Workers(会要求
npx wrangler deploy,那是跑服务端代码的),静态博客必须选 Pages。Pages 的构建界面不会出现Deploy command: npx wrangler deploy这一行,这是两个入口最明显的区分。
部署完成得到地址:https://blog-8jx.pages.dev/(blog 是项目名,8jx 是 Cloudflare 为全球唯一自动补的随机后缀)。
九、上线验证
# 关键页面 HTTP 状态
for u in "https://blog-8jx.pages.dev/" "https://blog-8jx.pages.dev/blog/first-post/" "https://blog-8jx.pages.dev/rss.xml"; do
echo "$(curl -s -o /dev/null -w '%{http_code}' "$u") $u"
done
# 全部 200
# 确认中文标题与编码
curl -s "https://blog-8jx.pages.dev/" | grep -io 'charset="?[a-z0-9-]*'
# charset=utf-8、title=肖飞的博客
# 确认标签渲染
curl -s "https://blog-8jx.pages.dev/blog/first-post/" | grep -o '#astro'
# → #astro
所有页面 HTTP 200,中文标题正常(utf-8),RSS / 标签 / 文章页全部跑通,博客正式上线。
十、后续规划
- 📝 写第一篇文章(就是这篇)
- 🏷 替换示例文章(first-post 等模板内容)
- 🌐
starflyer.top域名审核通过后,绑定blog.starflyer.top子域名 + HTTPS - 💬 加评论(Giscus)、全文搜索、访问统计
- 🖼 图片灯箱、阅读进度、归档、友链、作品集
总结
Astro 静态模式(SSG)
+
GitHub 私有仓库(Starflyr/blog)
+
Cloudflare Pages 自动构建与托管
+
一个自定义域名(starflyer.top,待审核)
这个方案全程没有占用云服务器的内存和带宽;用 Markdown 写作 + Git 管理,便于备份和迁移;dist/ 标准化产物随时可以迁回自有服务器。
最重要的是这句:生成器决定怎么制作网站,托管平台决定网站在哪里运行。