Hugo や Astro などの静的サイトジェネレータ(SSG)でブログを運用していると、記事の公開判定を「フロントマターの日付(datepublishDate)が現在時刻より過去であるか」で評価してしまいがちです。

しかし、定期的な自動デプロイ(Scheduled CI/CD)を組んでおらず手動デプロイで運用しているサイトでは、「日付が過去=公開済み」という前提は簡単に崩壊します。メタデータ上の日付は過去であっても、本番サーバへのビルド・デプロイが叩かれていなければ、実際の URL は 404 Not Found のまま放置されているためです。

本記事では、未デプロイ記事の 404 リンクを SNS 自動告知や外部システムへ誤配信しないために、単なるメタデータ比較ではなく 実際の本番 URL に対する HTTP 200 OK 応答確認 をゲートとする堅牢な自動化設計パターンを解説します。


1. 導入: 「日付が過去=公開済み」という暗黙の前提が引き起こす事故

静的サイトでブログ記事を書き貯め、フロントマターに過去や予約投稿の日付を記述して運用するスタイルは一般的です。ここで、記事の更新を検知して X(旧Twitter)への自動投稿や RSS フィード、sitemap 生成を外部スクリプトで自動化しているケースを考えます。

自動化スクリプトが以下のような判定ロジックを持っていた場合、大きなトラブルにつながります。

# ❌ メタデータ(日付)だけに頼った危険な判定ロジック
if [ "$ARTICLE_DATE" <= "$CURRENT_DATE" ]; then
    # 日付が過去なので公開されたとみなして SNS に投稿
    post_to_sns "$ARTICLE_URL"
fi

記事のフロントマターに「2026-08-01」と記載していても、著者が手動デプロイコマンド(mise run deploytools/gh-pages.sh 等)を叩いていなければ、本番サーバ上に静的 HTML は一瞬たりとも生成されていません。

この状態で自動化スクリプトが動くと、まだアクセスできない 404 URL を SNS や外部に拡散・告知してしまう誤投稿事故 が発生します。アクセスした読者は 404 エラーページに案内され、サイトの信頼性を損ねる結果となります。SSG の記事メタデータは、あくまで「著者が希望する表示上の日時」であって、本番環境におけるファイルの存在を一切保証しません。


2. 実録: 未デプロイ記事8本が全部404だった — 実測ログで見る危険性

この落とし穴は単なる理論上のリスクではありません。実際に Hugo で運用している自社ブログにおいて、過去の日付がフロントマターに設定されていたにもかかわらず手動デプロイが未実行であった未公開記事 8 件に対してアクセス検証を行った実測データがあります1

実測アクセス検証の結果

  • 対象: フロントマターの日付は経過済みだが、手動デプロイが未実行だった未公開記事 8 件
  • 実測アクセス結果: 8 件すべてが HTTP 404 Not Found(404 発生率 100%)

本番 URL に対するアクセスの明暗は、以下の検証例からも明らかです。

  • 公開済み記事: https://www.nqou.net/2026/07/29/070705/HTTP 200 OK
  • 未デプロイ記事: https://www.nqou.net/2026/08/11/070705/HTTP 404 Not Found (※フロントマター上で指定日時は経過しているが、デプロイコマンド未実行のためサーバ上に HTML が存在しない)

もし「日付が過去であること」だけをトリガーにして投稿キューを生成していた場合、これら 8 件の 404 URL がすべて X に誤自動投稿されるところでした。

投稿処理の直前に「対象 URL に HTTP アクセスし、200 OK が返るものだけをキューに投入する」という判定ゲートを噛ませた結果、未デプロイ記事の誤告知事故を 8 件中 0 件(完全遮断)に抑え込むことに成功しました1


3. 構造的課題: なぜメタデータ(日付)判定は失敗するのか

なぜ「日付の比較」という直感的な判定が失敗するのでしょうか。それには 3 つの構造的理由が存在します。

(1) 手動デプロイと SSG ビルドタイミングの乖離

WordPress などの動的 CMS であれば、リクエスト時にデータベースを参照して「日時が過去なら描画する」という動的判定が可能です。しかし SSG の場合、HTML が作られるのは「ビルドコマンドが実行された瞬間」のみです。デプロイを手動操作に依存している環境では、「著者が意図した日時」と「HTML が本番に並ぶ日時」は構造的に乖離します。

(2) SSG フレームワークのビルド除外仕様

SSG フレームワーク自体も、デフォルトでは未来日付や下書き記事を出力しません。

  • Hugo の仕様: ビルド実行時点のサーバ時刻より未来の publishDatedate を持つコンテンツ、および draft: true が設定されたファイルは、静的 HTML の出力対象から除外されます2-F (--buildFuture) フラグを明示的に付与してビルドを行わない限り、ファイル自体が生成されません。
  • Astro の仕様: コンテンツコレクションにおいて getCollection('blog', ({ data }) => data.pubDate <= new Date()) のようにビルド時にフィルタリングを行う実装パターンが一般的です3。SSG モードではビルドした瞬間の HTML が固定されるため、ビルドが走らない限り時間の経過だけで記事が出現することはありません。

(3) CI/CD スケジュールビルドの非決定性

「GitHub Actions などの cron (on: schedule) で定期自動ビルドを回せば解決するのではないか」と考えるかもしれません。しかし、Scheduled CI/CD にも以下の制限が存在します。

  • 実行タイミングの遅延: GitHub Actions の Scheduled events はベストエフォート(best-effort)で動作します4。毎時 00 分などのピーク時間帯には、数分から 1 時間以上の実行遅延やスキップが発生することが公式にドキュメント化されています。
  • リポジトリ無活動による自動停止: リポジトリに 60 日間アクティビティがない場合、Scheduled workflow は自動的に pause(一時停止)されます4

したがって、自動デプロイを組んでいたとしても「指定日時に必ず 200 OK になっている」という保証はできません。


4. 設計パターン: 実際の HTTP 200 OK 応答をゲートとする公開判定

この課題を解決する堅牢な設計パターンはシンプルです。

設計原則: 「設定値(メタデータ)は意図であり、HTTP 200 OK 応答のみが事実である」

自動化パイプラインの処理順序を以下のように組み替えます。

HTTP 200 OK 疎通確認による公開判定パイプライン構成図

graph TD
    A[リポジトリから素材抽出] --> B[投稿済み台帳の確認]
    B -->|未投稿| C[実本番 URL へ HTTP アクセス]
    C -->|HTTP 200 OK| D[SNS投稿 / 外部連携実行]
    C -->|HTTP 404 等| E[スキップ / 次回処理へ繰り延べ]

curl を使った疎通確認の実装例

投稿直前に curl で本番 URL のステータスコードを検証します5

#!/usr/bin/env bash

TARGET_URL="https://www.nqou.net/2026/07/29/070705/"

# レスポンスボディを破棄し、ステータスコードのみを取得
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$TARGET_URL")

if [ "$HTTP_STATUS" -eq 200 ]; then
    echo "【確認成功】 200 OK: 投稿処理を続行します。"
    # 投稿スクリプトの呼び出し
else
    echo "【スキップ】 HTTP $HTTP_STATUS: 未デプロイのため投稿を見送ります。"
fi
Note

GET vs HEAD リクエストの注意点 高速化のために curl -I (HEAD リクエスト) を使いたくなりますが、推奨されません。Cloudflare Pages や Vercel などの CDN / エッジサーバーやオリジンの設定・WAF によっては、HEAD リクエストに対して 405 Method Not Allowed403 を返したり、キャッシュのパージ挙動が GET と異なるケースがあるためです。curl -s -o /dev/null を指定した GET リクエストでボディを破棄する方式 が、実際のブラウザ閲覧と最も近く確実です5


5. 実装上のハマりどころと注意点

実際の自動化パイプラインを構築するにあたっては、以下の 2 つの実装ポイントに注意してください。

(1) X (Twitter) の t.co 文字数計算と重み付け

SNS 告知文の組み立てにおいて、URL の長さ判定には注意が必要です。X では投稿に含まれるすべての URL が t.co ショートリンクに自動変換され、元の文字列長に関わらず 一律 23 文字 (transformedURLLength: 23) として計算されます6

単純に url.length で文字数を加算して 280 文字(重み)の超過チェックを行うと、長い URL を含む投稿が「文字数オーバー」と誤判定されて弾かれてしまいます。告知スクリプト内では、URL 部分を一律 23 文字として計算する重み付け判定関数(post_weight())を独立させて文字数制限を制御します1

(2) 追記式台帳による二重投稿防止と冪等性

HTTP 200 OK チェックを通過して告知が成功した記事は、x-post-ledger.tsv などの追記式台帳ファイルに URL や slug を記録します。

  • 台帳の欠損対策: 台帳ファイルが存在しない場合は、空の台帳を自動生成せず異常終了させる安全設計を推奨します。台帳の欠損を「全件未投稿」と誤認して過去記事が一括重複投稿される事故を防ぐためです1

6. まとめ: 状態はメタデータではなく実際の応答で取る

システム設計における黄金律は、「外部依存や手動ステップが介在する処理において、状態の判定をソースコード内のメタデータで行ってはならない」ということです。

  • フロントマターの日付は、あくまでコンテンツの「表示用データ」
  • 本番環境でユーザーに届くかどうかの真偽は「実際の HTTP レスポンス」

自動化スクリプトや CI/CD 連携を構築する際は、必ず本番 URL への HTTP 200 OK アクセス確認を最終ゲートとして組み込み、誤告知のない堅牢なシステムを構築しましょう。


静的サイトの自動化運用や CI/CD 構築に関するご相談は コンタクトフォーム よりお問い合わせください。


Footnotes

  1. 当社の一次検証ログ 2 3 4

  2. Hugo Build Options Documentation

  3. Astro Content Collections Guide

  4. GitHub Actions Scheduled Events Docs 2

  5. curl Manual / Status Code Options 2

  6. twitter-text Official Specification