用 Codex 協作建立雙語技術部落格:從規格整理到第一篇文章
一開始,我其實只是想把 VitePress 網站的第一篇文章發出去。
網站已經架好,也已經透過 GitHub 和 Cloudflare Pages 公開了,第一篇建站教學也寫得差不多。照理說,接下來應該只是把文章搬到正式目錄,再加個入口和導覽就好。
結果一路整理下去,事情慢慢長成了:
- 雙語首頁
- 中英文文章目錄
- About 頁面
- 文章分類
- Tag 彙整
- 站內搜尋
- 草稿和正式文章的發布規則
- 圖片素材目錄
- 語言切換和偏好記憶
- Cloudflare Pages 部署設定
這篇文章會記錄我怎麼和 Codex 一起把這些東西整理出來,以及過程中遇到的一個意外問題:AI 有時候不是做得不夠好,而是做得太好,好到文章突然不像自己寫的。
先讓 Codex 理解現在的狀況
我沒有一開始就叫 Codex 直接改網站,而是先請它閱讀:
- 現有的 VitePress 專案
draft/建站教學.md- VitePress 設定
package.json- Git 和部署狀況
- 原本寫好的網站建置計畫
這一步看起來很普通,但是十分重要。
如果 AI 不知道專案現在長什麼樣子,它很容易拿一套「一般 VitePress 部落格的最佳實踐」直接蓋上去。那些做法不一定錯,但很可能和現況對不上,最後就會得到一份看起來很完整、實際上到處需要重做的答案。
先閱讀、先整理、不要急著改,至少可以先確認幾件事:
- 專案本身就在 GitHub repository 根目錄。
- Cloudflare Pages 的輸出目錄應該是
.vitepress/dist。 - 原本文章寫的
docs/.vitepress/dist和實際專案不一致。 - 首頁和導覽還保留 VitePress 預設範例。
- 第一篇文章仍然只存在
draft/裡。 - 原本的
.gitignore沒有正確排除目前使用的 cache 和 dist 路徑。
這些都是先讀現況才會發現的問題。
先決定第一版到底要做到哪裡
我原本有一份比較完整的雙語技術部落格計畫,但是如果完全照計畫做,第一篇文章大概會永遠發不出去。
所以這次先把目標縮成「能夠正式發布第一篇文章的基本版本」。
最後決定第一版包含:
- 繁中和英文首頁
- 繁中和英文文章列表
- 繁中和英文 About
- 同一篇文章的中英文切換
- 文章分類
- Tag 彙整頁
- 本地站內搜尋
- 基本品牌色和排版
先不做:
- 留言系統
- RSS
- 完整 SEO
- 社群分享圖
- 複雜動畫
- 完整的世界觀視覺介面
- 資料庫和後端 API
這個切法對我來說比較實際。先有文章、先能讀、先能部署,其他東西之後再慢慢補。
網站的基本規格
品牌和作者分開
網站名稱是:
INN666文章作者是:
Hazime這兩個身份要分清楚。INN666 是網站和品牌,Hazime 才是作者名稱。
公開內容也維持一個原則:網站不主動放真名、私人信箱、履歷連結或尚未公開的設定資料。技術部落格可以帶一點自己的風格,但不代表要把所有東西一次攤在陽光下。
雙語網址
網站使用對等的雙語路徑:
/zh/
/en/文章網址則是:
/zh/articles/{category}/{slug}
/en/articles/{category}/{slug}例如第一篇文章:
/zh/articles/engineering/build-blog-with-vitepress
/en/articles/engineering/build-blog-with-vitepress中英文使用相同的 slug 和 translationKey,這樣語言切換時可以前往同一篇文章,而不是只跳回另一個語言的首頁。
根網址的語言判斷
讀者進入 / 時,網站會依照以下順序選擇語言:
- 先看使用者之前有沒有手動選過語言。
- 沒有的話,再看瀏覽器偏好語言。
- 如果還是無法判斷,就使用繁中。
使用者切換語言後,選擇會記在瀏覽器裡,下次進站不用再選一次。
用資料夾當文章分類
文章分類沒有放在 frontmatter,而是直接使用資料夾。
目前固定三種:
engineering
ai-workflows
games正式文章結構如下:
zh/
└─ articles/
├─ engineering/
├─ ai-workflows/
└─ games/
en/
└─ articles/
├─ engineering/
├─ ai-workflows/
└─ games/分類直接反映在網址裡。
這樣做的好處是很直觀。檔案放在哪裡,它就是哪一類,不需要同時維護「資料夾是 engineering,但是 frontmatter 不小心寫成 games」這種會讓未來的自己想翻桌的雙重設定。
文章載入器也會檢查分類。如果文章被放進不支援的資料夾,建置會直接失敗,避免錯字默默變成新的分類。
草稿不是正式文章
這次特別確立了一個規則:
發布文章時,不移動原始草稿,而是複製一份完成稿到正式文章目錄。
也就是:
draft/建站教學.md會一直留著。完成後另外產生:
zh/articles/engineering/build-blog-with-vitepress.md
en/articles/engineering/build-blog-with-vitepress.md這樣做有幾個好處:
- 原稿不會因為潤飾和翻譯消失。
- 可以回頭比較文章是怎麼被修改的。
- 如果正式稿改得太遠,還有原本的聲音可以找回來。
- 草稿可以自由存放,不需要一開始就決定分類和語言。
VitePress 設定也會用 srcExclude 排除整個 draft/**。這不只是讓草稿不出現在文章列表而已,而是連建置都不會把草稿變成網頁。
不然即使導覽沒有連結,只要有人猜到 /draft/建站教學,還是可能直接看到內容。這種「看起來沒公開,其實網址打得到」的狀況十分危險。
正式文章的 frontmatter
正式文章目前使用以下格式:
title: 文章標題
description: 文章摘要
author: Hazime
date: 2026-06-20
updated: 2026-06-20
tags:
- Codex
- AI Workflow
lang: zh-TW
translationKey: article-slug
draft: false幾個重點:
author固定使用Hazime。- 中英文的
translationKey必須相同。 tags可以自由增加。- 分類不寫在這裡,由資料夾決定。
draft: true的文章不會出現在文章列表和 Tag 頁。
雖然正式文章通常會直接設成 draft: false,但是保留這個欄位還是很方便。文章已經搬到正式目錄、但還不想讓它出現在列表時,就有一個緩衝空間。
文章列表和 Tag 怎麼產生
文章列表不是手動維護,而是由 VitePress 的 createContentLoader 自動掃描:
zh/articles/*/*.md
en/articles/*/*.md載入時會:
- 排除
draft: true。 - 檢查分類資料夾是否合法。
- 檢查標題、摘要和
translationKey。 - 整理作者、日期、標籤和分類名稱。
- 依日期從新到舊排序。
- 分開中英文,不要混在同一份列表。
Tag 則從每篇文章的 frontmatter 收集。
例如:
tags:
- VitePress
- GitHub
- Cloudflare會自動產生:
/zh/tags/vitepress
/zh/tags/github
/zh/tags/cloudflare英文也會有自己的 Tag 頁,只顯示英文文章。
Tag 顯示時保留原本大小寫,網址則會轉成小寫的 kebab-case。相同 Tag 即使大小寫不同,也會視為同一個,避免 VitePress 和 vitepress 分裂成兩個世界。
搜尋只搜尋文章
第一版直接使用 VitePress 內建的本地搜尋,不接 Algolia,也不需要額外的搜尋服務。
搜尋範圍刻意只包含正式文章:
- 不搜尋首頁。
- 不搜尋 About。
- 不搜尋文章列表。
- 不搜尋 Tag 頁。
- 不搜尋草稿。
繁中頁只搜尋繁中內容,英文頁只搜尋英文內容,避免結果混在一起。
另外,Tag 也會放進搜尋索引。讀者搜尋 Codex、VitePress 或 Cloudflare 時,即使關鍵字主要存在 frontmatter,也還是能找到文章。
圖片素材也要有分類
文章之後一定會放圖片,所以一開始就把素材路徑定下來:
public/assets/{category}/{article-slug}/例如:
public/assets/ai-workflows/build-bilingual-blog-with-codex/一般圖片由中英文共用。如果圖片裡面有文字,才用語言後綴區分:
workflow.zh-TW.webp
workflow.en-US.webpMarkdown 裡使用固定網址引用:
草稿圖片則先留在:
draft/assets/確認要發布後,才把正式素材複製進 public/assets/。因為 public/ 裡的檔案會直接公開,就算文章沒有引用,知道網址的人還是可能看到。
部署設定
這個專案本身就在 GitHub repository 根目錄,所以 Cloudflare Pages 使用:
Root directory: /
Build command: pnpm docs:build
Build output directory: .vitepress/dist本機常用指令:
pnpm docs:dev
pnpm docs:build
pnpm docs:preview其中 pnpm docs:dev 是持續執行的開發伺服器。按下 Ctrl+C 停止時,pnpm 有時會顯示:
[ELIFECYCLE] Command failed with exit code 1只要它是在按下 Ctrl+C 後才出現,而且網站啟動時一切正常,通常只是 pnpm 把手動中斷視為非正常結束,不代表專案壞掉。
實際和 Codex 協作的流程
這次的協作大致分成幾個階段。
1. 先閱讀,不要急著改
先請 Codex 閱讀草稿、專案結構、設定檔和原始計畫,整理目前狀況。
這時候只盤點,不修改檔案。
2. 把選項問清楚
接著才決定:
- 第一版要不要直接做雙語。
- 首頁需要哪些入口。
- 文章網址怎麼命名。
- 根網址怎麼選語言。
- 文章列表要手動還是自動。
- Tag 要只顯示,還是可以點進彙整頁。
- 搜尋要不要跨語言。
- 圖片要不要中英文各放一份。
這些不是單純的技術問題,而是會影響未來工作方式的選擇。先決定清楚,後面才不會一直拆掉重來。
如果沒有特定想法,可以反問AI,請AI提供建議選項,再逐一檢討。
3. 先產生完整計畫
決定完成後,再整理一份可以直接實作的計畫,包含:
- 目錄結構
- 網址結構
- frontmatter
- 文章索引
- Tag 和搜尋
- 語言切換
- 圖片素材
- 部署設定
- 驗收方式
這份計畫最大的用途不是「看起來很專業」,而是讓後面的實作不用一直猜。
4. 實作後真的跑建置
寫完檔案不代表完成。
下面幾個問題,是 Codex 在這次實作完成後,實際執行建置和驗收時遇到的,不是我原本建站時留下的錯誤:
- ESM 模組設定缺少
"type": "module":Codex 加入文章資料載入器後,第一次執行pnpm docs:build,VitePress 被當成舊式 CommonJS 模組載入,建置直接失敗。後來在package.json補上"type": "module"才解決。 - 動態 Tag 路由使用
createContentLoader的時機不對:這也是 Codex 第一版實作的問題。動態路由在 VitePress 設定完成前就呼叫了createContentLoader,出現content loader invoked without an active vitepress process。最後改成在路由產生階段直接讀取文章檔案和 frontmatter。 - 日期格式判斷錯誤:Codex 原本以為 frontmatter 的日期一定是
YYYY-MM-DD字串,但是 YAML 解析後可能已經變成日期物件,伺服器渲染時因此出現Invalid time value。後來改成同時處理字串和日期物件。 - 語言切換入口重複:Codex 一開始另外做了一個自訂語言切換按鈕,建置後檢查 HTML 才發現 VitePress 已經因為雙語設定產生內建選單。這不會讓建置失敗,但是畫面會有兩個功能相同的入口,所以最後移除自訂按鈕,保留內建選單,再補上語言偏好記憶。
另外還有一個是在驗收時發現的既有專案問題:
- cache 和 dist 已經被 Git 追蹤:原本的
.gitignore使用docs/.vitepress/cache/和docs/.vitepress/dist/,但是這個專案其實就在 repository 根目錄,所以規則沒有生效。Codex 在檢查 Git 狀態時才發現這些建置產物已經進入版控,之後把規則改成.vitepress/cache/和.vitepress/dist/,並移除被追蹤的生成檔。
這段過程也很能說明為什麼「Codex 寫完了」不等於「真的完成了」。前面幾個錯誤有一部分就是 Codex 第一版實作自己造成的,如果只看產生出來的程式碼,很容易覺得應該可以;真的執行 pnpm docs:build,問題才會一個一個現形。
5. 檢查公開邊界
建置成功後,還要確認:
draft/沒有出現在輸出裡。- 中英文文章和 Tag 頁都有產生。
- 搜尋索引沒有混入其他語言。
- 導覽沒有失效連結。
- 公開內容沒有意外放進私人資料或未公開素材。
網站能跑只代表技術上成功,不代表它適合公開。
AI 把文章潤得太好,也可能是問題
第一篇文章完成時,我遇到了一個很微妙的問題。
Codex 把文章整理得很好,段落清楚、語句流暢、技術說明也更完整。但是好到我開始不知道該不該接受,因為那篇文章讀起來已經不太像我。
第一次潤飾後,有些比較口語的詞幾乎都被換掉了。文章確實變得更正式,但也變得很像一篇標準技術文件。
這讓我重新整理了 AI 潤稿規則:
- 保留原本的文脈、例子和遣詞用字。
- 語氣隨意一點沒關係。
- 可以整理標題、段落和條列。
- 可以修正技術錯誤和模糊步驟。
- 不要因為要「寫得更好」,就把作者的聲音整個換掉。
- 英文忠實翻譯完成後的中文,不要自行增加論點和結論。
所以保留原始草稿非常重要。
沒有原稿,就很難回答「現在這篇文章還像不像我」。AI 的版本可以是一份很好的編輯建議,但是最後要發布的,還是作者自己的文章。
英文版如果由 Codex 翻譯,文末會加上:
English translation provided by Codex.這不是要把整篇文章的作者變成 AI,而是清楚說明翻譯是怎麼完成的。
之後發布文章的實際步驟
以這篇文章為例,未來發布流程會是這樣。
1. 在 draft 寫繁中原稿
draft/用Codex協作建立雙語技術部落格.md草稿階段可以自由修改,不會被 VitePress 建置,也不會出現在網站上。
2. 整理但保留原本語氣
讓AI依照草稿產生正文後,要檢查:
- 章節是否清楚。
- 指令和路徑是否正確。
- 條列是否容易閱讀。
- 有沒有不必要的重複。
- 潤稿後還像不像自己。
- 如果英文能力足夠,也要一併檢查英文版。
3. 複製繁中完成稿
這篇屬於 ai-workflows,所以完成稿放到:
zh/articles/ai-workflows/build-bilingual-blog-with-codex.md不要移動原稿,保留 draft/ 裡的版本。
4. 建立英文版
英文版放到:
en/articles/ai-workflows/build-bilingual-blog-with-codex.md中英文必須使用相同的:
- slug
translationKey- 發布日期
- Tag 概念
- 文章結構
英文完成後,在文末加入 Codex 翻譯聲明。
5. 放入正式圖片
如果有圖片,放到:
public/assets/ai-workflows/build-bilingual-blog-with-codex/並確認:
- 檔名使用小寫和連字號。
- 中英文共用圖片時不要重複存放。
- 含文字的圖片有語言後綴。
- 中英文 Markdown 都有適合自己的替代文字。
6. 本機驗收
pnpm docs:build
pnpm docs:preview檢查:
- 中英文文章都能開啟。
- 語言切換會前往同一篇文章。
- 文章有出現在正確語言的列表。
- Tag 可以點擊。
- 搜尋找得到標題、內文和 Tag。
- 圖片在深色、淺色和手機版都正常。
- 草稿沒有被建置。
7. 提交和發布
git add .
git commit -m "Publish Codex blog workflow article"
git pushCloudflare Pages 會自動執行:
pnpm docs:build建置成功後,新版本才會上線;失敗時,原本成功部署的版本仍會保留。
結語
這次本來只是要發第一篇文章,最後卻順便整理出整套雙語文章和發布流程。範圍確實比預期大了一點,但是至少後面每一篇文章不需要再重新思考一次目錄、Tag、搜尋、翻譯和部署。
我覺得這也是和 AI 協作最有趣的地方:在和的AI來回討論中,藉由AI的反饋和提示可以瞭解到不同領域的知識技術,進而擴增自己的知識儲備和眼界。
Codex 在這個過程裡最有幫助的地方,不只是寫程式,而是把散落的想法整理成可以選擇的問題,再把選擇變成能夠驗收的規格。
不過 AI 做得很完整時,人還是要記得踩一下煞車。技術架構可以交給工具協助整理,文章的聲音則需要自己守住。
附錄:純網站規格(Markdown)
下面把目前的網站規格另外抽出來,不包含前面的協作過程和心得。需要參考或建立類似專案的人,可以直接複製這份 Markdown 再自行調整。
# INN666 雙語技術部落格規格
## 專案定位
- 網站名稱:`INN666`
- 作者名稱:`Hazime`
- 網站類型:繁中/英文雙語技術部落格
- 技術架構:VitePress、Vue、Markdown、GitHub、Cloudflare Pages、Cloudflare DNS
- 內容方向:Engineering、AI Workflows、Games
- 第一版以閱讀體驗和快速發布為主,不製作複雜後端或完整視覺系統
## 公開內容原則
- `INN666` 是網站和品牌名稱,不作為作者姓名。
- 所有文章統一署名 `Hazime`。
- 網站不主動公開真名、私人信箱、履歷或個人社群。
- 不公開尚未核准的文章或其他未公開素材。
- Git metadata、頁面 metadata 和公開檔案不得意外包含私人身份資料。
## 網站入口與導覽
```text
/
├─ zh/
│ ├─ 首頁
│ ├─ Articles
│ └─ About
└─ en/
├─ Home
├─ Articles
└─ About
```
- `/` 先讀取瀏覽器內已儲存的語言偏好。
- 沒有偏好時,依瀏覽器語言選擇繁中或英文。
- 無法判斷時使用繁中。
- 使用者切換語言後,以 `localStorage` 保存偏好。
- 語言切換應前往目前頁面的對應語言版本。
## 正式網址
首頁:
```text
/zh/
/en/
```
文章列表:
```text
/zh/articles/
/en/articles/
```
文章:
```text
/zh/articles/{category}/{slug}
/en/articles/{category}/{slug}
```
Tag:
```text
/zh/tags/{tag-slug}
/en/tags/{tag-slug}
```
About:
```text
/zh/about
/en/about
```
## 內容目錄
```text
draft/
├─ {article-draft}.md
└─ assets/
zh/
├─ index.md
├─ about.md
├─ articles/
│ ├─ index.md
│ ├─ engineering/
│ ├─ ai-workflows/
│ └─ games/
└─ tags/
└─ [tag].md
en/
├─ index.md
├─ about.md
├─ articles/
│ ├─ index.md
│ ├─ engineering/
│ ├─ ai-workflows/
│ └─ games/
└─ tags/
└─ [tag].md
public/
└─ assets/
├─ engineering/
├─ ai-workflows/
└─ games/
```
## 分類規則
固定支援以下分類:
| 目錄 | 顯示名稱 |
| --- | --- |
| `engineering` | Engineering |
| `ai-workflows` | AI Workflows |
| `games` | Games |
- 分類以文章所在資料夾為唯一來源。
- frontmatter 不重複設定 `category`。
- 不支援的分類資料夾應讓正式建置失敗。
- 分類會直接反映在文章網址中。
## 草稿規則
- 草稿可以自由存放在 `draft/`,不要求先分語言或分類。
- 發布時保留原始草稿,另外複製完成稿到正式文章目錄。
- `draft/**` 必須加入 VitePress `srcExclude`。
- 文章列表、Tag 和搜尋不得掃描 `draft/`。
- 草稿素材先放在 `draft/assets/`,確認發布後才複製到 `public/assets/`。
## 文章 frontmatter
繁中範例:
```yaml
title: 文章標題
description: 文章摘要
author: Hazime
date: 2026-06-20
updated: 2026-06-20
tags:
- Codex
- AI Workflow
lang: zh-TW
translationKey: article-slug
draft: false
```
英文版使用:
```yaml
lang: en-US
```
- `title`、`description`、`translationKey` 和有效日期為必要欄位。
- 中英文版本使用相同的 `translationKey` 和 slug。
- `author` 預設為 `Hazime`。
- `tags` 可以自由新增,並以不分大小寫方式去重。
- `draft: true` 的文章不出現在列表和 Tag 頁。
## 文章列表
文章載入器只掃描:
```text
zh/articles/*/*.md
en/articles/*/*.md
```
處理規則:
- 排除 `draft: true`。
- 由檔案路徑推導語言和分類。
- 驗證分類、必要 frontmatter 和日期。
- 繁中與英文文章分開產生索引。
- 依 `date` 由新到舊排序。
- 列表顯示日期、標題、摘要、分類和 Tag。
## Tag
- Tag 來源為文章 frontmatter 的 `tags`。
- 顯示文字保留原本大小寫。
- 網址轉成小寫 kebab-case。
- Tag 以不分大小寫方式去重。
- Tag 頁只列出同語言、非草稿且包含該 Tag 的文章。
- 文章頁和文章列表上的 Tag 均可點擊。
## 搜尋
- 使用 VitePress 內建本地搜尋。
- 不依賴 Algolia 或其他外部搜尋服務。
- 繁中頁只搜尋繁中內容,英文頁只搜尋英文內容。
- 只索引正式文章。
- 不索引首頁、About、文章列表、Tag 頁和草稿。
- 搜尋內容包含標題、內文和 Tag。
- 搜尋介面提供繁中和英文文字。
## 圖片與素材
正式素材路徑:
```text
public/assets/{category}/{article-slug}/
```
Markdown 引用方式:
```markdown

```
- 一般圖片由中英文文章共用。
- 含語言文字的圖片使用 `.zh-TW` 和 `.en-US` 後綴。
- 檔名使用小寫 kebab-case。
- 中英文文章分別提供符合內容的替代文字。
- `public/assets/` 只放已核准公開的素材。
## 翻譯與潤稿
- 繁中為原始寫作語言。
- 潤稿保留作者原本的文脈、例子、遣詞和口語感。
- 可以整理標題、段落、條列、連結和步驟順序。
- 可以修正技術錯誤,但不應因此替換作者聲音或新增無關論點。
- 英文版忠實翻譯完成後的繁中稿,不額外增加說明、觀點或結論。
- Codex 產生的英文翻譯在文末加入:
```text
English translation provided by Codex.
```
## 本機指令
開發預覽:
```bash
pnpm docs:dev
```
正式建置:
```bash
pnpm docs:build
```
預覽正式建置:
```bash
pnpm docs:preview
```
## Cloudflare Pages
```text
Production branch: main
Root directory: /
Build command: pnpm docs:build
Build output directory: .vitepress/dist
```
- Git push 後由 Cloudflare Pages 自動建置。
- 建置成功後才替換正式版本。
- 建置失敗時保留上一個成功部署的版本。
## `.gitignore`
```text
node_modules/
.vitepress/cache/
.vitepress/dist/
dist/
```
- cache、dist 和 `node_modules` 不得提交到 repository。
- 如果 `node_modules` 已被追蹤,使用:
```bash
git rm -r --cached node_modules
```
## 發布流程
1. 在 `draft/` 撰寫繁中原稿。
2. 整理章節、檢查技術內容,並保留作者語氣。
3. 複製繁中完成稿到 `zh/articles/{category}/{slug}.md`。
4. 建立英文版到 `en/articles/{category}/{slug}.md`。
5. 複製核准公開的圖片到 `public/assets/{category}/{slug}/`。
6. 執行 `pnpm docs:build`。
7. 預覽並檢查雙語文章、Tag、搜尋、圖片和語言切換。
8. 檢查草稿、私人資料和未公開素材沒有進入建置結果。
9. Git commit 並 push,由 Cloudflare Pages 自動發布。
## 驗收條件
- `/zh/` 和 `/en/` 可正常開啟。
- 中英文首頁、文章列表、About 和文章頁可正常瀏覽。
- 語言切換會前往同一頁面的對應語言版本。
- 文章只出現在正確語言和分類的列表。
- Tag 頁只列出相符的同語言文章。
- 搜尋只回傳目前語言的正式文章。
- 搜尋可找到標題、內文和 Tag。
- `draft/` 和草稿素材不在建置輸出中。
- 手機、桌面、深色和淺色模式皆可正常閱讀。
- 圖片和內部連結沒有失效。
- 公開內容不含私人身份資料或未公開素材。
- `pnpm docs:build` 成功完成。