# 本地寫 Markdown，git push 後自動上線：我的雙語部落格釋出流程

> Canonical: https://vxi.me/zh-tw/posts/markdown-blog-publish-workflow/
> Locale: zh-tw
> Published: 2026-03-25
> Updated: 2026-03-25
> Markdown: https://vxi.me/zh-tw/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`”，內容站才真正進入可持續維護的狀態。
