# 本地写 Markdown，git push 后自动上线：我的双语博客发布流程

> Canonical: https://vxi.me/posts/markdown-blog-publish-workflow/
> Locale: zh-cn
> Published: 2026-03-25
> Updated: 2026-03-25
> Markdown: https://vxi.me/posts/markdown-blog-publish-workflow.md

## 这篇文章解决什么问题

很多个人博客卡住，不是因为不会搭站，而是因为发布链路太重。每写一篇文章都要手工改配置、手工构建、手工上传，久而久之内容生产就断掉了。

我这次把自己的博客整理成一条更顺手的流程：本地直接写 Markdown，提交到 GitHub 后由 GitHub Actions 自动构建和发布。这样写文章这件事，才更像持续做内容，而不是每次都做一次运维。

## 适用场景

- 你做的是个人博客、技术内容站或轻量文档站
- 你希望文章以 Markdown 文件保存在仓库里
- 你不想上 WordPress，也不想维护重后台
- 你希望中英双语内容能长期维护，而不是靠手工映射

如果你更想要在线后台、多人协作编辑和可视化审核流，那这套方式就不是最优解。

## 最终效果

这套链路跑通之后，发一篇新文章只需要这几步：

1. 本地生成中英双语文章骨架
2. 编辑两份 Markdown
3. 本地预览确认内容和排版
4. `git push origin main`
5. GitHub Actions 自动部署上线

也就是说，内容生产和部署已经被拆开了。你只关心写文章，发布动作由自动化接手。

## 环境准备

我当前这套博客栈是：

- 框架：`Astro`
- 内容层：`astro:content`
- 内容格式：`Markdown`
- 部署方式：静态产物发布
- Web 服务：`Nginx`
- 自动部署：`GitHub Actions`

内容目录是双语分开的：

```text
src/content/posts/zh-cn/*.md
src/content/posts/en-us/*.md
```

同一篇文章的中英文文件共享一个 `path`，站点会自动生成：

- 中文：`/posts/<path>/`
- 英文：`/en/posts/<path>/`

## 具体步骤

### 1. 先把内容模型定死

如果一开始内容模型不稳定，后面你每加一篇文章都要补逻辑。

我现在把博客的文章、页面、分类、资源都统一放进了 `astro:content`，关键点有两个：

- 文章用 `path`
- 页面和分类用 `key`

这样做的好处是，新增内容时只增加 Markdown 文件，不再去碰路由映射代码。

### 2. 提供一个文章脚手架命令

如果每次发文章都手工复制旧文件，效率很快就会下降。

我在项目里补了一个脚本：

```bash
npm run new:post -- "markdown-blog-publish-workflow" "本地写 Markdown，git push 后自动上线：我的双语博客发布流程" "Write Markdown Locally, Deploy on git push: My Bilingual Blog Publishing Workflow" writing
```

执行后会直接生成两份草稿：

```text
src/content/posts/zh-cn/markdown-blog-publish-workflow.md
src/content/posts/en-us/markdown-blog-publish-workflow.md
```

默认是：

```yaml
draft: true
```

这很重要，因为它能防止你写到一半就被自动部署出去。

### 3. 本地只负责写作和预览

写文章时，我只做两件事：

1. 编辑 Markdown
2. 本地 `npm run dev`

浏览器里主要看这些点：

- 首页是否出现新文章
- 中文和英文详情页是否都可访问
- 分类和标签是否正常聚合
- 标题和描述是否适合 SEO

如果这一步通过，后面其实就不该再手工 SSH 发布了。

### 4. 把部署动作交给 GitHub Actions

这一步是整个流程能不能真正省事的关键。

我现在的工作流是：

1. push 到 `main`
2. Actions 自动 `npm ci`
3. 自动 `npm run build`
4. 打包 `dist`
5. 通过 SSH 上传服务器
6. 解压到新的 `releases/<timestamp>-<sha>`
7. 切换 `current`
8. 做一次公网可访问性校验

这意味着文章发布已经变成标准 CI/CD 流程，而不是依赖我手工操作。

### 5. 生产服务器只做静态托管

我的一个明确原则是：服务器不负责构建，只负责托管。

这样有几个直接好处：

- 服务器更稳定
- 构建环境更统一
- 回滚更简单
- 出问题时能快速分层定位

目前线上结构大概是：

```text
/var/www/aitokenhacks.com/releases/<release-id>
/var/www/aitokenhacks.com/current
```

每次发布都只是在切换软链接，不是覆盖线上目录。

### 6. 给自动部署补诊断信息

自动化不是写完 workflow 就结束了。真正有用的自动化，出错时要能快速说明白问题。

所以我后来又给 workflow 补了几类诊断：

- 缺少哪个 Deploy Secret
- SSH 是否能连通
- 远端目录是否存在
- `current` 最终切到了哪个 release

这样以后如果部署失败，不会只看到一个抽象的红叉，而是能很快知道问题是在 GitHub Secrets、SSH、目录权限，还是公网验证。

## 常见问题

### 为什么我不直接上在线后台

因为我现在这个站的重点是内容积累和 SEO，而不是编辑器体验。

Markdown 文件化管理有几个我更看重的优点：

- 全量进入 Git
- 可回滚
- 可审阅
- 内容和代码结构一致
- 不依赖第三方后台可用性

### 双语内容为什么不用单文件混写

因为单文件里塞两套 frontmatter 和两套正文，维护成本很快会上来。

我最后选的是“双文件，同 path”的方式：

- 符合 Astro 内容集合的天然工作方式
- 正文维护最直观
- 不需要额外翻译映射层

### 自动部署失败最常见的原因是什么

我这次实际踩到过的主要有两类：

- GitHub Actions 计费或额度问题，作业直接起不来
- Deploy Secrets 没配对，SSH 配置阶段直接失败

所以我现在会优先检查：

1. Actions 是否真的开始执行
2. Secrets 是否齐
3. SSH 用户和密钥是否有效
4. 远端目录权限是否允许发布

## 最后总结

如果你也在做个人技术博客，我非常建议把“写作”和“发布”拆开。

写作时，只管内容质量。
发布时，让 CI/CD 去做重复动作。

当你能把发文动作压缩成“本地写 Markdown，然后 `git push`”，内容站才真正进入可持续维护的状态。
