# 静的サイトで予約公開を反映するには：Webhook・再ビルド・cronの設計

公開日: 2026-10-21 | 更新日: 2026-10-21 | URL: https://blog.genko.me/scheduled-publishing-static-site

> CMSで予約公開しても静的HTMLが変わらない理由を、genko.meの配信APIと予約公開Webhookで解説します。Astro・Next.js static exportの再ビルド、ISRの再検証、公開終了、cronによる復旧まで扱います。

静的サイトで予約公開を反映するには、CMSの公開時刻に加えて、サイトの再ビルドかキャッシュ再検証を動かす必要があります。genko.meでは予約公開の確定時に`entry.published`のWebhookが届くので、これを更新の起点にできます。通知の失敗に備え、定期的な再ビルドや再検証も組み合わせます。

本記事はgenko.meの運営側が執筆しています。2026年10月21日に[予約公開ガイド](https://docs.genko.me/guides/scheduled-publishing)、公開中のドキュメント、配信API・定期処理・SDKの実装を確認しています。

## 予約公開と静的HTMLの更新は別の処理です

9時に公開する記事をCMSで予約し、8時にサイトをビルドしたとします。8時の配信APIにはまだ記事が含まれないため、そのビルドでは一覧にも詳細ページにも入りません。9時にCMS側の配信対象になっても、8時に作成したHTMLが自動で書き換わるわけではありません。

ここでは、次の3つの時刻を分けて考えます。

| 時刻 | 意味 | 誰が処理するか |
| --- | --- | --- |
| 公開開始日時 | 配信APIから記事を取得できる時刻 | CMS |
| 更新処理の開始 | 再ビルドや再検証を始める時刻 | Webhook受信側・ジョブ |
| サイトへの反映 | 新しいHTMLやデータを閲覧できる時刻 | サイト・ホスティング環境 |

CMSの予約設定だけで管理できるのは、最初の条件です。実際の反映には通知までの時間、ビルド待ち、ビルドとデプロイの時間、サイト側のキャッシュが関係します。「9時にAPIで取得可能」と「9時ちょうどに全ページが更新済み」は異なる約束です。

ISRを使う場合も、時刻が来れば閲覧中の画面が更新されるという意味にはなりません。期限を過ぎたキャッシュが次のリクエストをきっかけに更新される構成では、アクセスがない間は処理が始まりません。静的生成、再検証、ブラウザの再読み込みを一つの仕組みとして考えないことが大切です。

## genko.meの予約公開ではWebhookが送られます

[予約公開ガイド](https://docs.genko.me/guides/scheduled-publishing)に記載のとおり、期限が来た公開予定の記事は定期処理で確定され、その時点で`entry.published`のWebhookが送られます。通知本文の構成は[Webhookガイド](https://docs.genko.me/guides/webhooks)で確認でき、予約公開でWebhookが送られないという前提の回避策は不要です。

一方、APIの公開判定とWebhookの通知は別々です。配信APIはリクエスト時点の日時を確認し、開始日時を過ぎた公開予定の記事を配信できます。定期処理による状態の確定を待つ必要はありません。終了日時を過ぎれば、通常の一覧から除外され、単一取得は404になります。

定期処理は通常60秒間隔です。Webhookはその処理で公開状態を確定するときに送られるため、指定時刻と完全に同時に届く保証はありません。予約公開の`publishedAt`には、処理の実行時刻ではなく指定した開始日時が入ります。[公開開始・終了の仕様](https://docs.genko.me/guides/scheduled-publishing)に沿って運用してください。

メイン言語の予約公開通知は次の形です。日時とIDは説明用の値です。

```json
{
  "event": "entry.published",
  "workspace": "your-workspace",
  "api": "blog",
  "entry": {
    "id": "abcDEF123456",
    "status": "published"
  },
  "triggeredAt": "2026-10-21T00:00:42.000Z"
}
```

本文や公開開始日時はこのJSONに含まれません。`triggeredAt`を予約日時として表示したり、通知だけで記事本文を構築したりしないでください。更新処理では、その時点の配信APIから公開対象を取り直します。サブ言語の通知にはトップレベルの`lang`が付き、同じ記事IDでも言語別のページやキャッシュを更新する必要があります。

## Astro・Next.js static exportでは再ビルドします

静的ファイルを配信する構成では、Webhookからホスティング先のビルドフックを呼ぶ方法が分かりやすい選択です。受信先がgenko.meのPOSTを受け付ける仕様なら直接登録でき、イベントの選別や署名検証が必要なら小さな受信サービスを間に置きます。

Astroの静的ページでは、次のような取得処理をビルド時に再実行します。接続済みの`client`は[Astro導入ガイド](https://docs.genko.me/frameworks/astro/getting-started)の`createGenko(...).apis`を想定しています。

```astro
---
// src/pages/blog/[id].astro
import { client } from "../../lib/genko";

export async function getStaticPaths() {
  const posts = await client.blog.getAll();
  return posts.map(post => ({
    params: { id: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
---
<html lang="ja">
  <head><title>{post.title}</title></head>
  <body>
    <h1>{post.title}</h1>
    <div set:html={post.body} />
  </body>
</html>
```

`blog`は`title`がテキスト、`body`がリッチテキストのリスト形式APIです。予約時刻より後にビルドすれば、その時点で公開対象の記事を取得します。APIキーはビルド環境だけで使い、ブラウザへ渡しません。`getAll()`はSDK内部でページを進めるため、一覧取得の先頭ページだけで詳細ページを生成する取りこぼしも防げます。

更新対象は詳細だけではありません。記事一覧、ページ送り、トップページの新着、RSS、サイトマップ、カテゴリ一覧も同じビルドで更新するか確認します。新規記事の詳細が生成されても一覧が古いままだと、読者はそこへたどり着けません。

Next.jsの`output: 'export'`も、配布済みの静的ファイルを更新するには新しいビルドとデプロイが必要です。静的配信先にNext.jsの再検証用Route Handlerが常駐するわけではありません。[static exportの公式説明](https://nextjs.org/docs/app/guides/static-exports)で、利用中の構成にサーバー処理があるか確認してください。

## ISRではデータとページの再検証を選びます

Next.jsのサーバーが動き、キャッシュを使っている場合は、Webhookから必要なデータやパスを再検証できます。再ビルドするか再検証するかは、予約公開という機能名ではなく、サイトをどのように配信しているかで決めます。

| 配信構成 | 主な反映方法 | 気を付ける点 |
| --- | --- | --- |
| Astroの静的ページ | 再ビルド・再デプロイ | 新規ルートと一覧を生成する |
| Next.js static export | 再ビルド・再デプロイ | 再検証を実行するサーバーは別途必要 |
| Next.jsのキャッシュ付きサーバー構成 | タグ・パスの再検証 | 取得時のタグと更新対象を合わせる |
| 毎回取得するSSR | 次回取得で公開状態を確認 | CDNや独自キャッシュも確認する |

[revalidateTag](https://nextjs.org/docs/app/api-reference/functions/revalidateTag)はタグ付きデータ、[revalidatePath](https://nextjs.org/docs/app/api-reference/functions/revalidatePath)はページやレイアウトのパスを対象にします。`revalidateTag(tag, 'max')`では古い内容を返しながら更新でき、`{ expire: 0 }`は次の取得で新しいデータを待つ選択です。どちらも、呼び出した瞬間に全ページを先回りして生成する処理とは考えないでください。

受信ルート、署名検証、取得側へのタグ付けを含むコードは、[Next.jsでCMSの公開を即反映する](https://blog.genko.me/nextjs-webhook-revalidate)にまとめています。ここでは同じ実装を繰り返さず、予約日時から公開反映までの運用に絞ります。

## 手動公開と公開終了も更新対象にします

genko.meのWebhookは、予約公開専用の通知口ではありません。対象APIへ登録した通知先には各イベントが届くので、必要なものを受信側で選びます。[Webhookガイド](https://docs.genko.me/guides/webhooks)の区別は次のとおりです。

| 操作 | イベント | サイト側の対応 |
| --- | --- | --- |
| 新規保存・手動公開を含む作成 | `entry.created` | 公開データを取り直す |
| 保存・手動公開・下書きへ戻す | `entry.updated` | 新しい状態で再生成する |
| 削除 | `entry.deleted` | 古い詳細と一覧を更新する |
| 予約公開の確定 | `entry.published` | 新しい詳細と一覧を更新する |
| 時刻による公開終了の確定 | `entry.unpublished` | 掲載終了を反映する |
| テスト送信 | `webhook.test` | 受信確認。`entry`は`null` |

`entry.published`だけに絞ると手動公開を見落とします。反対に`entry.status === 'published'`だけを処理すると、下書きへ戻した記事や公開終了の記事を消せません。公開中の原稿を「下書きとして保存」するだけではWebhookは送られません。

公開終了は特に注意が必要です。APIが404を返しても、配信済みHTMLがそのまま残る場合があります。ビルドが失敗したときに古いページを配り続けるのか、掲載期限をページ表示時にも確認するのか、コンテンツの性質に合わせて決めます。厳密な終了時刻が必要な告知では、再ビルドの成功だけに依存する構成は適しません。

## cronを通知失敗からの回復に使います

genko.meのWebhook配送は10秒でタイムアウトし、自動リトライはありません。配信ログから手動再送できますが、夜間の予約公開など、人が気付くまでの遅れも考えると定期ジョブが役立ちます。

静的サイトなら、Webhookとは別に10分ごとに同じビルドフックを呼ぶ設計ができます。次はcron実行環境へ`SITE_BUILD_HOOK`を安全に設定済みで、そのフックがPOSTを受ける場合の例です。実際のURLを記事やリポジトリへ書き込まないでください。cronの実行環境は対話シェルの環境変数を引き継がない場合があるため、シークレットはジョブ実行側で明示的に設定します。

```cron
*/10 * * * * curl --fail --silent --show-error --request POST "$SITE_BUILD_HOOK"
```

これは常にビルドを要求する単純な例です。ビルド回数を減らしたい場合は、公開一覧のID・更新日時などを前回の正常取得結果と比較し、変化がある場合だけ実行します。追加だけでなく削除や公開終了も検知する必要があるため、最新記事1件の更新日時だけでは不十分です。APIのエラーを空の一覧として扱わないようにします。

10分間隔なら、通常は最大で次の実行までの待ち時間に、ビルド待ちとデプロイ時間が加わります。ジョブの失敗や停止もあるため、10分以内の反映保証にはなりません。受信サービスを使う場合はWebhookとcronが同じ処理キューへ依頼し、同時ビルドや古いビルドによる上書きを制御します。

Next.jsなら、定期ジョブから認証付きの内部エンドポイントへ再検証を依頼する設計もできます。公開Webhook用の署名を自作して流用するより、ジョブ用認証を分けて同じ更新処理を呼ぶと、失敗の切り分けが明確になります。

## 本番前に予約から表示まで通して確認します

確認用の記事を数分後に予約し、設定したタイムゾーン、配信APIで見える時刻、Webhookの配信ログ、ビルドまたは再検証、公開URLの順に確認します。Webhookが200でもデプロイが失敗していれば反映は完了していません。

同じ記事を公開終了にして、詳細が非表示になり、一覧・新着・RSSからも外れることを確認します。通知を手動で再送しても処理が壊れず、cronだけでも最新状態へ戻れることまで確認すると、予約公開を人の立ち会いなしで運用しやすくなります。

## よくある質問

### genko.meで予約公開した記事にはWebhookが届きますか？

定期処理で予約公開が確定すると`entry.published`が送られます。配信APIは指定時刻から記事を配信できますが、通知には定期処理や通信による遅れがあります。

### 予約時刻と同じ時刻にビルドを予約すれば十分ですか？

予約変更やジョブ失敗に追従できるかも確認が必要です。CMSからのWebhookを主経路にし、定期ジョブを回復経路にすると、日時ごとの手動設定を減らせます。

### ISRの有効期限だけで予約公開できますか？

更新のきっかけが次のアクセスになる構成では、指定時刻と表示更新が一致しません。許容できる遅れを決め、Webhookによる再検証と合わせて設計します。

### APIで公開終了になれば静的ページも消えますか？

配信済みHTMLは自動では消えません。公開終了のWebhookから一覧・詳細を更新し、通知やビルドに失敗した場合の回復方法も用意してください。
