跳至主要內容

開發紀錄

草稿預覽如何存在,又不讓正式 build 洩漏草稿

拆解 Astro 筆記系統如何把 Markdown 草稿放進 dev-only 預覽,同時讓正式路由、sitemap 與靜態輸出只看可發布內容。

林予晴
發布
  • 內容工程
  • Astro
  • 草稿預覽
  • 靜態網站

草稿需要能被看見,正式網站卻不能把它當成已發布文章。這兩件事如果只靠一個 if 判斷,很容易在首頁、文章詳頁或 sitemap 漏掉其中一個出口。

目前的做法把內容分成兩條路:本機預覽可以讀取所有合法 Markdown,正式建置只能讀取可發布文章。主要判斷是:草稿安全要靠多層邊界一起成立,不能只在列表上隱藏草稿卡片。

先把預覽集合和公開集合分開

本機預覽使用 getAllNotes()。它會讀取整個 Content Collection,包含草稿和尚未到發布日期的文章,讓作者可以在文章還沒公開前檢查標題、正文、目錄和 metadata。

正式頁面則使用 getPublishedNotes()。它會排除草稿、沒有 publishedAt 的內容,以及發布日期晚於台北今天的文章。首頁、列表、正式文章詳頁和 sitemap 都從這個可發布集合建立,不直接把 Markdown 檔案當成公開頁面。

兩個函式的差異是刻意保留的:

使用情境 讀取方式 可以看到的內容
本機校稿 getAllNotes() 所有通過 schema 的文章,包含草稿與未到日期內容
正式首頁與列表 getPublishedNotes() 已發布且日期已到的文章
正式文章詳頁 getPublishedNotes() 只有可發布文章會生成路由
正式 sitemap getPublishedNotes() 只有可發布文章與必要的列表頁

這樣做的重點不是讓預覽複製一套內容系統,而是讓兩條路在資料入口就分開。

預覽路由只在 Astro dev 注入

notes-preview integration 會先檢查 Astro 的 command。只有 command === 'dev' 時,才注入以下兩條路由:

  • /preview/notes
  • /preview/notes/[locale]/[slug]

在 build、preview 或 sync 模式下,integration 不注入任何預覽路由。正式輸出因此不是「先生成預覽頁,再靠 robots 標籤藏起來」,而是根本沒有這些路由的正式入口。

這個差異很重要。noindex 是給爬蟲的索引指示,不能代替路由邊界,也不能把一個公開可連線的預覽頁變成權限控制。預覽路由的定位是本機校稿,不是登入系統或私有檔案服務。

noindex 是第二層保護,不是唯一保護

預覽列表和單篇預覽都設定 X-Robots-Tag: noindex, nofollow。共用 layout 也會輸出 noindex metadata;單篇預覽另外顯示「草稿,尚未發布」的提示,讓作者和讀者不會把它誤認成正式文章。

這幾層各自處理不同問題:

  • integration 決定預覽路由只在 dev 存在。
  • response header 告訴搜尋引擎不要索引預覽 response。
  • HTML metadata 讓頁面本身也保留 noindex 意圖。
  • 預覽提示把狀態直接告訴正在校稿的人。

如果只是把草稿卡片從首頁拿掉,卻仍讓正式文章的 getStaticPaths 讀取所有 Markdown,草稿還是可能擁有可猜到的 URL。現在正式文章路由改用 getPublishedNotes(),所以這個出口也受到同一個發布條件限制。

正式 build 會再檢查一次輸出

正式建置不會註冊預覽 integration 的路由;文章詳頁的 getStaticPaths 也只會從可發布集合產生路徑。sitemap 同樣只加入 getPublishedNotes() 回傳的文章 URL。

此外,verify:notes 不只看命令是否成功,還會讀回建置產物,逐項檢查:

  • dist/ 不包含 /preview/notes。
  • 草稿或未到日期文章的 slug、標題與正文片段沒有洩漏。
  • 正式文章有標題、單一 h1、靜態正文、canonical 與 sitemap URL。
  • 三種語言的筆記列表和首頁卡片數量符合實際可發布文章。
  • 沒有正式文章的語言顯示真實空狀態,而不是假文章。

這些檢查分別對應「路由不存在」、「內容沒有洩漏」和「正式文章真的輸出」三種風險。只跑 build,無法單獨證明這些條件全部成立。

一篇草稿的實際校稿順序

  1. 建立合法的 Markdown frontmatter,保留 draft: true,並把 publishedAt 預留為空值,不填虛構的正式日期。
  2. 啟動 Astro dev,從 /preview/notes 或對應的單篇預覽網址檢查內容。
  3. 讀標題、摘要、正文、目錄和連結,確認文章沒有把內部交接資料寫進公開文字。
  4. 執行正式 build,確認草稿不會產生正式文章頁或 sitemap URL。
  5. 執行 verify:notes,把 source Markdown、dist/ 和 sitemap 放在同一個檢查裡。
  6. 工程檢查完成後,仍由站主確認內容、公開範圍與視覺閱讀;沒有得到發布授權,就維持草稿。

預覽能證明作者看得到這篇文章,build 和輸出檢查能證明正式產物沒有把它放出去。兩者都需要,卻不能互相代替。

這套邊界沒有解決什麼

目前的預覽路由沒有帳號、權限或內容加密。它依賴 Astro dev 的本機使用方式,以及正式 build 不註冊預覽路由的規則。因此不要把開發伺服器開放到公網,也不要把預覽網址當成正式文章網址。

同樣地,noindex 不代表搜尋引擎一定永遠不會看見網址;它只是 response 和 HTML 提供的索引指示。真正重要的第一層仍是預覽路由不進正式輸出,第二層才是 noindex 與狀態提示。

草稿預覽的目的,是讓內容在發布前可以被檢查,而不是把尚未確認的內容偷偷放到 production。只要預覽和公開查詢使用不同入口,文章就能先被作者看見,再由發布條件決定讀者什麼時候看得見。

紀錄範圍

本文根據 Astro integration、預覽頁、Content Collection 查詢、正式文章路由、sitemap 與筆記輸出驗證腳本整理。它描述目前 repository 的本機校稿和正式 build 邊界,不代表已驗證任何 production host 的權限設定或搜尋引擎行為。