AIを使ったメディアを作るとき、記事生成APIの呼び出しだけでは解決しない問題があります。生成中に管理画面を閉じたらどうするか、翻訳中に原文が変わったらどうするか、公開した記事とキャッシュの内容をどう揃えるか。運用を続けるには、こうした処理のつなぎ方が必要になります。
私が運営しているAI Syncsは、AIニュースや生成AIの活用、AI検索などを扱うメディアです。WordPressを中心に、日本語を原文として11の言語へ展開できる構成にしています。
この記事では、記事の制作、翻訳、閲覧の経路を整理しながら、非同期ジョブ、HTMLキャッシュ、多言語SEO、予約公開の設計を紹介します。同じようにWordPressへAI処理を組み込みたい方の参考になればと思います。
※構成と公開ページの確認日は2026年10月21日です。日本語を含む12言語に対応していますが、すべての記事に全言語の翻訳が揃うことを意味しません。
1. 全体構成:WordPressを中心に、制作と閲覧の処理を分ける
記事はWordPress標準の投稿として保存し、画像はメディアとして扱います。公開画面にはSWELLを使い、記事作成や翻訳などの独自機能はプラグインへ分けています。
大まかな構成は次のとおりです。
独自プラグインの役割は、次のように分けています。
| プラグイン | 担当する処理 |
|---|---|
| Editorial Desk | RSSなどの情報収集、調査、記事・画像作成、下書き、公開予約 |
| AI News Polylang Translator | 日本語原文からの翻訳、各言語の記事との関連付け、更新・公開状態の同期 |
| Language Switcher | 言語選択UI、翻訳関係を示すタグ、サイトマップなど |
| Static Accelerator | 公開HTMLのキャッシュ制御、更新時の失効、事前生成 |
| X Publisher | 英語記事を起点とするX向け紹介文の作成・投稿 |
| Growth Desk | 企画、出典と確認記録、下書き作成、公開前確認、SEO検査、実績CSVの管理 |
ここでの分割単位はWordPressプラグインです。同じWordPress環境を使いながら、処理の責任と保存するデータを分けています。
記事本文を標準の投稿へ保存しておけば、編集画面、テーマ、カテゴリー、メディアといったWordPressの仕組みをそのまま使えます。独自の制作画面から作った記事も、公開後は通常のWordPress記事として扱えます。
2. AI処理はジョブとして保存し、画面の寿命から切り離す
記事本文や画像の生成は、外部APIの応答を待つ処理です。これを一度の管理画面のリクエストに詰め込むと、通信切断やタイムアウトのたびに、どこまで終わったのか分からなくなります。
Editorial Deskでは、依頼をデータベースのジョブとして保存し、Action Schedulerを利用してバックグラウンドで進めます。ブラウザを閉じても、依頼と処理済みの情報は残ります。Action Schedulerは、WordPress向けのジョブキューです。
記事作成の流れには、調査や構成の確認、本文作成、完成本文からのタイトル作成、画像作成などの段階があります。構成案の確認を省略するルートも用意していますが、生成された記事はまず下書きになります。
工程ごとの結果を保存しておくと、途中で止まっても再利用できます。たとえば本文が完成した後にタイトル作成で失敗した場合、保存済みの本文を使って続けられます。
また、本文作成では依頼時のプロバイダーやモデルを保持します。管理者が設定を変えても、進行中の依頼が途中から別の条件で動くことを避けるためです。
この方式では、画面に「処理中」と表示するだけでなく、サーバー側でどの工程まで保存したかを管理する必要があります。
タイムアウトした依頼を、そのまま自動再送しない
外部APIへの送信後に通信が切れた場合、API側では処理が完了している可能性があります。
Editorial Deskでは、結果が不明な依頼を停止し、概算費用の予約枠を保持します。管理画面でも、保存済みの結果を使って残りを進める「再開」と、新しいAPIリクエストを送る「新しく再試行」を分けています。
これは、再試行による重複生成と追加費用を抑えるための設計です。すべてのプラグインが共通の費用台帳を使っているわけではなく、翻訳など別プラグインの費用は個別に管理しています。
3. 翻訳は別の投稿として保存し、原文の変更を追跡する
多言語化にはPolylangを使っています。日本語と各言語の記事は別々の投稿IDを持ち、翻訳グループで関連付けます。独自プラグインもPolylangの公開APIを使って、この関係を登録します。
翻訳は、設定に応じて日本語記事の公開・予約・更新を検出し、キューへ登録します。投稿の保存処理中に翻訳APIを呼び出さず、本文のバッチや画像単位で処理を進めます。
記事間の整合性については、次のように扱っています。
| 起きたこと | 翻訳側の扱い |
|---|---|
| 翻訳中に原文や翻訳設定が変わる | 古い結果の保存を止め、変更後の内容を処理する |
| 原文の予約日時だけが変わる | 完成済みの翻訳を使い、公開日時を同期する |
| 原文が下書き・非公開・ゴミ箱へ移る | 管理対象の翻訳も下書きに戻し、公開予約を解除する |
| 原文も設定も変わっていない | 定期スキャンで翻訳を繰り返さない |
日本語記事が公開済みなら、翻訳は必要な処理が完了した言語から公開します。予約記事は原文と同じ公開日時を設定しますが、翻訳が間に合わなければ、その言語は完成後の公開になります。
翻訳文を公開前に確認したい場合は、日本語記事を余裕のある日時で予約し、完成した翻訳をプレビューする運用が必要です。
また、Growth Deskから新しく作った下書きは、自動翻訳の対象から初期状態で除外します。まず日本語で内容を検証し、翻訳する記事を選べるようにしています。
4. 閲覧時は完成HTMLをキャッシュする
公開画面では、テーマと各プラグインが出力した完成HTMLをNginxのFastCGIキャッシュへ保存します。
キャッシュがHITすれば、そのHTMLを返すためにPHP-FPMやWordPressを実行する必要がありません。MISS時やキャッシュ対象外のリクエストでは、通常どおりWordPressが処理します。Redisは、WordPressが動くときのオブジェクトキャッシュとして使っています。
この方法では、テーマの描画結果をキャッシュするので、記事ページを別のフロントエンドへ作り直す必要がありません。画像の参照、CSS・JavaScript、構造化データなども、元のHTMLに含まれる形で保持します。
ただし、HTMLを保存しても、画像やCSSのファイル自体がコピーされるわけではありません。静的ファイルの配信や、REST API、AJAXなどの動的な処理は引き続き必要です。
キャッシュする条件を絞る
対象は、共有してよい公開ページのHTMLです。ログイン中の閲覧、プレビュー、検索結果、管理画面、REST APIなどは対象から外します。未知のCookieや個人別の表示が混ざるリクエストも、無条件には共有しません。
同じ未生成URLへのリクエストが集中した場合に備えて、Nginxのfastcgi_cache_lockも使っています。個別のディレクティブの動作はNginxの公式ドキュメントで確認できます。
SWELLとの互換対応では、公開ページ用のnonceも確認対象になりました。nonceを含むページは原則として除外し、対象を確認した例外では有効期間を検証します。その互換モードは保存期間を最大600秒とし、期限切れHTMLの配信とバックグラウンド更新を無効にする条件です。
テーマ固有の処理まで確認する必要がある点は、完成HTMLをキャッシュする方式でも変わりません。
CloudflareとNginxのHITは分けて確認する
2026年10月21日に日本語トップページを取得した際は、次の応答ヘッダーを確認できました。
X-AISS-Nginx-Cache: HIT
CF-Cache-Status: DYNAMIC
この取得時点では、Cloudflareを経由し、オリジンのNginxキャッシュがHITしています。Cloudflare側でHTMLがキャッシュHITした状態とは区別して扱います。
5. キャッシュ更新は「世代」を切り替える
キャッシュは、保存後に記事が更新されたときの扱いも必要です。
Static Acceleratorでは、記事単位、アーカイブ、サイト全体の世代情報を管理し、更新時に対応する世代を切り替えます。Nginxのキャッシュキーには、URLや端末区分に加えて、実体解決した世代のパスを含めます。
たとえば、次の競合が起きたとします。
- 古い本文を使ったページ生成が始まる。
- 編集者が本文を更新し、世代を切り替える。
- 手順1の生成が、更新後に完了する。
世代をキーへ含めておけば、古い生成結果が保存されても旧世代のキーに入ります。更新後の閲覧は新しい世代を参照します。
また、全体の失効時にも、すべてのキャッシュファイルをその場で削除する必要がありません。先に参照先を切り替え、古いデータの清掃は分割して進めます。公開・更新した記事とトップページは、重複を除いたキューへ登録して事前生成します。
依存関係には限界もあります。通常の本文更新では、別の記事に表示された関連記事の抜粋などがTTLまで残る場合があります。即時反映が必要な変更では、全体の失効などを選ぶ必要があります。
6. キャッシュを使うなら、Cronの動作も確認する
WordPressのWP-Cronは、通常はページへのアクセスをきっかけに、期限が来た処理を実行します。WordPressの公式説明にも、この動作と実行が遅れる条件が記載されています。
NginxのキャッシュHITではWordPressが起動しません。アクセスが増えても、それがそのまま予約公開や翻訳の実行機会になるとは限らないため、AI Syncsではサーバー側のCronを1分間隔で動かしています。
さらに、Cronの実行確認だけでは足りないケースもありました。
2026年10月20日には、サーバーCronとワーカーが動いている一方で、予約時刻を過ぎても未公開のまま残る記事がありました。確認すると、その記事に対応するWordPress標準のpublish_future_postイベントがありませんでした。イベントが失われた具体的な原因は、取得できたログからは特定できていません。
この事象への対応として、Editorial Deskの管理対象記事に予約公開の監視処理を追加しました。
- 予約保存後に、公開イベントが存在するか確認する。
- 定期処理でも管理対象の予約記事を確認し、将来の予約でイベントがなければ登録を修復する。
- 予約時刻を過ぎた対象記事は、
wp_publish_post()を使ってWordPress標準の公開処理へ戻す。
標準の公開処理を通すことで、公開に連動する翻訳やキャッシュ失効のフックも実行されます。
この経験から、スケジューラの稼働、予約イベントの存在、記事の公開状態をそれぞれ観測するようにしました。
7. 多言語SEOは、公開済みの記事同士の関係を出力する
日本語トップはルートURL、英語版のAI Syncsは/en/というように、言語ごとのURLを持たせています。言語選択の記憶はブラウザ側で扱い、訪問者ごとに同じURLのHTMLを書き換えずに済むようにしています。
Language Switcherでは、Polylangの翻訳関係を使ってhreflangを調整します。対象は、同等の内容を持つ、公開済みでインデックス対象のページです。
たとえば日本語記事のフランス語訳がまだない場合、フランス語トップへの移動をUIで案内することはできます。しかし、そのトップページを記事の翻訳としてhreflangに登録すると、別の内容を対応付けることになります。
canonicalや構造化データなどのメタ情報も、既存のSEOプラグインとの重複出力を避けます。
確認するのは設定画面だけではありません。今回取得した日本語・英語トップでは、それぞれ自身のURLを指すcanonicalが1つありました。記事一覧の2ページ目も、トップページへ正規化せず、そのページ自身を指すcanonicalになっていました。
多言語化とキャッシュを組み合わせるときは、最終的に公開されるHTMLで、URLと言語とメタ情報が一致しているかを確認する必要があります。
8. 公開前の確認を、その原稿の内容に結び付ける
Growth Deskでは、管理対象の下書きに対して、出典URL、確認日、対象環境や条件、独自の検証内容を保存できます。
確認済みの記録は、タイトル・本文・抜粋と確認情報から計算したSHA-256ハッシュに結び付けます。確認後に本文や根拠を変えた場合はハッシュが一致しなくなり、確認済みの状態が無効になります。
これにより、「昨日は確認したが、その後に原稿が変わった」という状態を区別できます。Growth Deskが管理する未公開記事では、未確認や未記入の原稿の初回公開・予約を止めます。通常の既存投稿へ一律に適用する機能ではありません。
この確認記録は、人がどの原稿を読んだかを追跡するためのものです。事実の正しさをハッシュで判定できるわけではなく、出典や数値の確認は編集者が行います。
検証していることと、今後の課題
既存の検証では、WordPress Playgroundと実際のPolylangを使い、翻訳の関連付け、公開状態の変更、キャッシュの失効、HTMLの一致、予約公開イベントの復旧などを確認しています。AI APIを使うテストはモック応答へ置き換え、プラグイン側の処理を検証しています。
一方で、大量の記事を投入した負荷試験や、大規模な同時アクセスの試験までは行っていません。キャッシュHIT時の経路を確認できても、未生成URLへのアクセス集中やAIジョブの滞留、データベースの負荷は別に測定する必要があります。
今後は、キャッシュHIT率に加えて、記事生成・翻訳キューの待ち時間、予約公開の遅れ、言語別の翻訳品質を継続して確認したいと考えています。
公開画面はAI Syncsの記事一覧から確認できます。記事ページと言語切替を見ながら読むと、WordPressの標準投稿を中心にした構成をイメージしやすいと思います。