本文へ

開発記録

下書きをプレビューしながら、本番ビルドへの漏出を防ぐ

Astro のノートシステムで Markdown の下書きを dev 専用プレビューに表示し、公開ルート、sitemap、静的出力には公開可能な記事だけを含める仕組みを説明します。

林予晴
公開
  • コンテンツエンジニアリング
  • Astro
  • 下書きプレビュー
  • 静的サイト

下書きは校正のために閲覧できる必要がありますが、公開サイトが公開済みの記事として扱ってはいけません。この二つを一つの if だけで制御すると、ホームページ、記事詳細ページ、sitemap のどこかで漏出経路を見落としやすくなります。

2026年10月2日の原稿に記録した実装では、コンテンツを二つの経路に分けています。ローカルプレビューは有効な 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() を使うため、この出口にも同じ公開条件が適用されます。

本番ビルド後に出力をもう一度確認します

本番ビルドは、プレビュー integration のルートを登録しません。記事詳細ページの getStaticPaths も、公開可能な集合からだけパスを生成します。sitemap も同様に、getPublishedNotes() が返した記事 URL だけを追加します。

さらに verify:notes は、コマンドが成功したかどうかだけでなく、ビルド成果物を読み戻して次の項目を確認します。

  • dist/ に /preview/notes が含まれていないこと。
  • 下書きや公開日が未到来の記事の slug、タイトル、本文の一部が漏れていないこと。
  • 公開記事にタイトル、単一の h1、静的な本文、canonical URL、sitemap URL があること。
  • 三言語のノート一覧とホームページのカード数が、実際の公開可能な記事数と一致していること。
  • 公開記事のない言語では、架空の記事ではなく、実態に合った空の状態を表示すること。

これらは「存在してはいけないルート」「漏れてはいけないコンテンツ」「実際に出力されるべき公開記事」という三つのリスクに対応します。build を実行するだけでは、すべての条件が成立したとは証明できません。

下書きを校正する実際の順序

  1. 有効な Markdown frontmatter を作成し、draft: true を維持します。publishedAt は空のままにし、架空の公開日を入れません。
  2. Astro dev を起動し、/preview/notes または対応する個別プレビュー URL から内容を確認します。
  3. タイトル、概要、本文、目次、リンクを読み、内部の引き継ぎ情報が公開文に入り込んでいないことを確認します。
  4. 本番ビルドを実行し、下書きの公開記事ページや sitemap URL が生成されないことを確認します。
  5. verify:notes を実行し、source Markdown、dist/、sitemap を同じ検証の中で照合します。
  6. 技術的な検証が終わっても、サイトオーナーが内容、公開範囲、画面上での読みやすさを確認します。公開の許可がなければ、下書きのままにします。

プレビューは、書き手がその記事を閲覧できることを確認できます。build と出力検証は、本番成果物がその記事を外に出していないことを確認できます。どちらも必要ですが、互いの代わりにはなりません。

この境界では解決できないこと

ここで説明するプレビュールートには、アカウント、権限、コンテンツの暗号化はありません。Astro dev をローカルで使うことと、本番ビルドにはプレビュールートを登録しない規則に依存しています。そのため、開発サーバーを公開インターネットに開放したり、プレビュー URL を公開記事の URL として扱ったりしないでください。

同様に、noindex は検索エンジンがその URL を絶対に見つけないという意味ではありません。response と HTML が提供するインデックス指示にすぎません。最初の境界はあくまで、プレビュールートを本番出力に含めないことです。noindex と状態の案内は、その次の層です。

下書きプレビューの目的は、公開前に内容を確認できるようにすることです。未確認のコンテンツをひそかに production に置くための仕組みではありません。プレビュー用と公開用の問い合わせを別の入口にすれば、まず書き手が記事を閲覧し、読者がいつ閲覧できるかは公開条件で決められます。

この記録の範囲

本記事は、Astro integration、プレビューページ、Content Collection の問い合わせ、公開記事のルート、sitemap、ノート出力の検証スクリプトをもとに整理しています。2026年10月2日に記録した repository のローカル校正と本番ビルドの境界を説明するものであり、production host の権限設定や検索エンジンの挙動を検証済みだと示すものではありません。