ブログを書いていると、「記事を書くだけでなく、もっと多くの人に届けたい」という気持ちが生まれてきます。

Google検索からの流入を増やすのは時間がかかります。そこで目を向けたのが、Xへの定期的な投稿です。ブログ記事の要点をXで発信することで、検索以外の流入経路を作れるのではないかと考えました。

ただ、1日2回投稿を手動で続けるのは現実的ではありません。そこでXの自動投稿を仕組み化することにしました。

この記事を読み終える頃には、以下ができるようになっています。

  • X自動投稿の仕組みに登場するツール・サービスの役割を理解できる
  • 最初にはまったGitHub Actionsの時刻ズレ問題とその解決策を把握できる
  • 実装のキーワードを知ることで、Claude Codeなどを活用して自分でも仕組みを作れる

なお、実装の細かいコードはClaude Codeに相談しながら作りました。この記事では「何がどう繋がって動くか」の全体像に絞って解説します。

なぜX自動投稿をしようと思ったのか

ブログの流入経路は、現状ほぼGoogle検索一本です。SEOで順位が上がれば読者は増えますが、それには時間がかかります。

検索以外のルートとして考えたのがXです。投資・資産形成の話題は、Xでもアクティブに情報交換されています。ブログ記事の要点を短くまとめてXで発信することで、

  • ブログを知らない人にリーチできる
  • 記事へのリンクをXから踏んでもらえる

という効果が期待できます。

「1日2回投稿する」という目標を設定しましたが、毎日手動でやるのは続きません。であれば自動化してしまおう、というのが出発点です。

登場人物:どのツールが何をしているか

自動投稿の仕組みを作るにあたって、以下のツール・サービスが登場します。

X自動投稿の登場人物と役割:ブログ記事からGitHub・Cloudflare Workers・X APIを経てXに投稿される流れ

左から右へ:ブログ記事の内容がCloudflare WorkersとX APIを通じてXに届く

X API(X開発者プログラム)

Xに自動投稿するためには、X APIという「外部からXを操作するための窓口」を利用する必要があります。

X APIを使うには、Xの開発者プログラムに登録して認証キー(APIキーやアクセストークン)を取得する手順があります。料金体系はプランによって異なりますが、1日あたり数十投稿程度のライトな利用であれば費用を抑えられるプランを選べます。詳細はX開発者ポータルでご確認ください。

GitHub

ブログのソースコードをすでにGitHubで管理しているため、自動投稿のプログラム(スクリプト)もここに置くことにしました。

GitHubにはGitHub Actionsという自動化機能があり、「毎日○時にこのプログラムを実行する」という設定ができます。最初はこの機能を使って自動投稿を試みました。

Cloudflare Workers

このブログはCloudflare Pagesというサービスを使ってインターネット上に公開しています。CloudflareにはCloudflare Workersという、自分でサーバーを用意しなくてもクラウド上でプログラムを動かせる仕組みがあります。

ブログのインフラとして既に使っているCloudflareの中に、定時実行の仕組みも持てることがわかり、後にGitHub ActionsからCloudflare Workersに移行しました。

最初の設定:GitHub Actionsで試みた

まず試みたのは、GitHubのCron(定時実行)機能を使ったGitHub Actionsによる自動投稿です。

GitHub Actionsには「毎日8時と20時に実行する」といった設定を書くだけで定時実行ができる機能があります。設定ファイル(YAML形式)を書いてGitHubに置くだけなので、仕組みとしては比較的シンプルです。

しかし、実際に動かしてみると大きな問題が発覚しました。

問題:時刻のズレが大きすぎる

設定では「朝8時に投稿する」としていたにもかかわらず、実際に投稿される時刻が読めません。早ければ8時数分後、遅ければ11時になることもありました。

GitHub Actions(時刻ズレあり)とCloudflare Workers(時刻安定)の仕組み比較図

左:GitHub Actionsは混雑時に大幅な遅延が発生。右:Cloudflare Workersはほぼ予定時刻通りに安定して実行

なぜこうなるか? GitHub Actionsのcronは、GitHubのサーバが混雑しているときにキュー(順番待ち)に入ります。無料枠・共有サーバの仕様上、ズレは「仕様の範囲内」とされており、保証はありません。多くのユーザが使う時間帯(UTCで整時など)は特に遅延しやすいことが知られています。

「投稿時刻を気にしなければいい」と思えればそれまでですが、SNS投稿のエンゲージメントはタイミングに左右される面があります。朝の通勤時間帯に届けたいのに昼以降に投稿されては意図通りになりません。

改善策:Cloudflare Workersに移行する

解決策として選んだのが、GitHub ActionsからCloudflare Workersへのタイマー処理の移管です。

投稿スクリプト自体(どんな文章をXに送るか)はGitHubで管理しつつ、定時実行するタイマーはCloudflare Workersに任せるという構成に変えました。

Cloudflare WorkersにはCron Triggersという機能があり、ほぼ予定時刻通りに安定して実行されます(GitHub Actionsのような数十分〜数時間レベルの遅延は発生しません)。また、このブログのサーバと同じCloudflareのインフラ上で動くため、設定の一元管理という副次的なメリットもあります。

構成の全体像(改善後)

ここで正確に説明すると、Cloudflare Workersは「投稿コードをそのまま実行する」のではなく、定刻になったらGitHub ActionsをAPI経由で起動するタイマーとして機能しています。実際の投稿処理はGitHub Actions上のプログラムが担当し、APIキーもGitHubの暗号化された変数管理機能(GitHub Secrets)で安全に保管されています。

  1. Cloudflare Workers(Cron Triggers):朝・夜の指定時刻に起動し、GitHubのAPIを叩いてGitHub Actionsを起動する
  2. GitHub Actions:起動されたら投稿キューの先頭を読み取り、X APIで投稿する
  3. X API:リクエストを受け取りXに投稿する
  4. X:フォロワーのタイムラインに届く

「タイマーの信頼性はCloudflare Workersに任せ、実処理はGitHub Actionsが行う」という役割分担です。

GitHub ActionsとCloudflare WorkersでCronの「確かさ」がこれほど違うとは、やってみるまで気づきませんでした。

セキュリティ面で見た「GitHubに寄せる」設計

「タイマー(Cloudflare Workers)→実処理(GitHub Actions)→投稿(X API)」という役割分担になっていますが、「なぜCloudflare WorkersからX APIを直接叩かないのか」と疑問に思う方もいるかもしれません。

理由の一つはセキュリティです。

X APIで投稿するには4種類の認証情報(APIキー・シークレット・アクセストークン・アクセストークンシークレット)が必要です。この情報が漏洩すると、第三者があなたのXアカウントに自由に投稿できてしまいます。

GitHubにはGitHub Secretsという機能があり、APIキーなどの機密情報を暗号化して保管できます。保管した値はGitHub Actionsの実行時にのみ取り出せる環境変数として使われ、コードには一切出てきません。リポジトリを見ても、キーの中身は確認できません。

さらに、実行ログ・変更履歴・APIキーの管理をGitHub一か所に集約できるというメリットがあります。

確認したいこと 確認できる場所
過去の実行が成功したか GitHub Actions の実行履歴
投稿内容・投稿日時 data/x-post-history.yaml(コミット履歴)
コードの変更履歴 git log
認証情報の管理場所の設計図:Cloudflare WorkersはGitHub接続キーのみ保持、GitHubがX APIの4つの認証情報を暗号化して保管

Cloudflare WorkersにはGitHub起動キーのみ、X APIの鍵はGitHub側だけで管理する

Cloudflare Workersを「タイマー専用」に限定することで、Workers側にはGitHubへのアクセスキー(PAT)だけを持たせれば済み、X APIの認証情報はGitHub側のみで管理できます。「何がどこにあるか」が整理され、万が一のときの影響範囲を絞り込みやすくなります。

補足:記事が実際に公開されたタイミングでキュー登録する

投稿タイミングの問題が解決したところで、次に考えたのが「何を投稿するか」の管理です。

なぜ「キュー(投稿待ちリスト)」という仕組みにしたのか

Xへの投稿目的はブログの流入経路を増やすこと。具体的には、新着記事が公開されたらXでお知らせするという使い方を想定していました。

最初に思い浮かぶ実装は「記事が公開されたら即座にXへ投稿する」です。しかしこれだと、記事を連続して公開したときに立て続けにXへ投稿されてしまい、フォロワーのタイムラインに一気に流れ込む形になります。

そこで採用したのが、投稿候補を一度リストに溜めておき、朝と夜の決まった時間に1件ずつ取り出して投稿するという仕組みです。記事の公開ペースに関係なく、投稿は1日2回・1回につき1件という一定のペースで届けられるため、フォロワーへの露出がバラけて継続的にリーチできます。

この「投稿候補を溜めておくリスト」が投稿キュー(投稿待ちリスト)です。投稿する記事の場所を列挙した管理ファイルを用意し、Cloudflare Workersに呼び出されたGitHub Actionsがその先頭から1件ずつ取り出して投稿する、という先入れ先出し方式です。

# 投稿待ちリストのイメージ
queue:
  - post_path: content/posts/記事A.md
    added_at: '2026-04-29T11:00:00+09:00'
  - post_path: content/posts/記事B.md
    added_at: '2026-04-30T09:00:00+09:00'

ブログ公開と同時にキュー登録する仕組み

「記事が公開されたら投稿キューに追加する」という仕組みを作ろうとしたとき、ブログの予約投稿機能との不整合が問題になりました。

このブログには「予約投稿」の機能があります。記事ファイルの冒頭に公開日時を書いておくと、その日時まで本番サイトに表示されない仕組みです(毎時0分の自動サイト更新で公開)。このブログの公開フローについては別記事で詳しく解説しています

ここで不整合が生まれます。ブログを本番公開フォルダ(GitHubのpostsフォルダ)にアップロードした瞬間に投稿キューへ登録してしまうと、記事がまだ公開前なのにXへの告知が先に出てしまうリスクがあります。たとえば翌朝公開予定の記事を夜にアップロードしたとき、その夜のうちにXへお知らせが届いてしまう、という状況です。

この問題への対処として、キュー登録のタイミングを「アップロードした瞬間」ではなく「記事が実際に公開された瞬間」に変えました。具体的には以下の2段階で制御しています。

① アップロード時:記事の公開日時をチェックしてスキップ

本番公開フォルダにブログ記事が追加されたとき、GitHub Actionsのワークフローが記事ファイルの冒頭に書かれた公開日時を確認します。公開日時が未来の場合はキュー登録をスキップします。実際の登録は②の自動サイト更新が行います。

② 毎時0分の自動サイト更新:公開になった記事を検知して投稿リストに登録

毎時0分の自動更新のタイミングで、公開フォルダにある記事を確認します。公開日時が現在時刻以前の記事を見つけたら投稿待ちリストへ追記します。すでにXに投稿した記事が再び登録されることはありません。

ブログ公開からX投稿までの流れ:アップロード後に公開日時チェック→自動サイト更新で検知→キュー登録→投稿の流れ

pushした時点ではスキップし、記事が実際に公開されたタイミングでキューに登録される

この仕組みにより、「記事が公開される前にXへの告知が出てしまう」という事態を防ぎつつ、公開後は自動でXへの投稿が予約されます。

実際に起きた失敗:過去記事が一括で登録されてしまった

cronによるキュー登録の仕組みを導入した直後、ダッシュボードを見たら過去記事が数十件、一気にXの告知キューに入っていたという事態が発生しました。

原因はcronのスキャン範囲にありました。

一括登録が発生した原因:全記事スキャン→全記事が条件を満たす→未投稿と判定→数十件が一括登録

投稿済み履歴ファイルに記録がなかった過去記事がすべて「未投稿」と判断された

投稿スクリプトには「投稿待ちリストにある」「投稿済み履歴ファイルにある」記事を重複して登録しない仕組みがあります。ただし、この仕組みを導入する以前の記事は投稿済み履歴ファイルに記録がなく、すべて「未投稿」と扱われてしまいました。

対処として2点修正しました。

まず、投稿待ちリストに登録されていた記事を手動でクリアしました。次に、cronのキュー登録条件を変更しました。

キュー登録条件の修正前後:修正前は全過去記事が対象、修正後は直近2時間以内のみ対象

時間窓を設けることで、過去記事の一括登録を防ぐ

cronは1時間ごとに動くため、ちょうど1時間の幅があれば十分ですが、GitHub Actionsの起動遅延(数分〜十数分)を考慮して2時間に設定しています。重複登録は投稿済み履歴ファイルの確認で防がれるため、2時間の幅があっても問題ありません。

この修正により、「cronが初めて走ったときに全過去記事がキューに入る」という事態は起きなくなりました。仕組みを導入するタイミングによっては、投稿済み履歴ファイルに記録が残っていない記事が存在することがあります。時間窓フィルタはそのような場合の保険としても機能します。

投稿文はブログ記事の設定に書いておくだけ

「X投稿の文章はどこで管理するのか」というと、実はブログ記事ファイルの冒頭に書いている設定がそのまま使われます。

記事ファイルの冒頭(frontmatter)に書いたdescriptiontagsが、X投稿文の材料になります。

---
title: "記事のタイトル"
date: 2026-05-01T08:00:00+09:00
description: "ここに書いたテキストがX投稿の本文になります。"
tags: ["ハッシュタグ1", "ハッシュタグ2", "ハッシュタグ3"]
---

生成される投稿文の構成はおおむね以下の通りです。

ブログ記事のfrontmatter(descriptionとtags)がX投稿文に自動変換される仕組みの図解

descriptionが本文に、tagsがハッシュタグに自動変換される

記事を書く段階でX投稿を意識し、descriptionを140文字以内に収まるよう書いておくことで、そのまま投稿文として使えます。ブログ記事に丁寧なdescriptionを書く習慣が、そのままXの投稿品質に直結するという仕組みです。

実装のキーワード(Claude Codeを使えばできる)

細かい実装はClaude Codeに相談しながら進めました。「何を作ればいいか」を理解した上でClaude Codeに依頼すると、コードを書いてもらいながら仕組みを動かすことができます。

実装を進める際に登場するキーワードをまとめておきます。これらを知っておくと、Claude Codeに相談するときの言語化がしやすくなります。

キーワード 意味
X API v2 XをプログラムからWrite操作するためのAPI(第2世代)
OAuth 1.0a X APIの認証方式。APIキー・シークレット・アクセストークンの4値で認証する
GitHub Actions GitHubの自動化機能。YAMLで条件・処理を定義する
workflow_dispatch GitHub Actionsを外部APIから手動/プログラムで起動するトリガー
Cloudflare Workers Cloudflareのサーバーレス実行環境。JavaScriptで動く
Cron Triggers(Cloudflare) Cloudflare Workersの定時実行機能。UTCで設定。GitHub Actionsのような大幅な時刻ズレは発生しない
Wrangler Cloudflare Workersをパソコンのコマンド操作で管理するツール。作ったプログラムをCloudflareに送り込むときに使う
FIFO キュー(YAML) 投稿待ちリスト。先に追加した記事から順番に投稿される
paths フィルター GitHub Actionsの起動条件の絞り込み。特定のフォルダ内のファイルが変更されたときだけ動くよう設定できる
GitHub Secrets APIキーなどの機密情報を暗号化して管理する仕組み。Actions内で環境変数として使える

Claude Codeへの依頼例としては、次のような形が有効です。

「Cloudflare WorkersのCron Triggersを使って、毎日7時30分にGitHub ActionsをworkflowDispatchで起動するスクリプトを作ってください。GitHub APIのトークンはCloudflareの環境変数から読み込む形にしてください。起動されたGitHub Actionsは、data/x-queue.yamlの先頭エントリを読み取ってX APIで投稿し、投稿後はキューから削除してコミットする処理を行います。」

このような依頼文を出せれば、実装の骨格はClaude Codeが書いてくれます。

仕組みは動かしながら育てるもの:監視を続けることが大切

「設定すれば後は自動で動く」と思えますが、実際には一度作って完成にはなりません。

この記事自体がその証拠です。仕組みを実際に動かしていく中で、次々と問題が出てきました。

  • GitHub ActionsのCronで時刻がズレる → Cloudflare Workersへ移行
  • 過去記事が数十件、一括で投稿待ちリストに登録される → 時間窓フィルタの追加
  • 朝の本投稿とバックアップ枠で二重投稿が発生する → 重複チェックロジックの修正

特に二重投稿の問題は「特定の条件が重なったときだけ起きる」という性質で、最初は原因の特定にも時間がかかりました。こうした問題は、動かしてみて初めて気づくものがほとんどです。

Xの投稿結果を確認する習慣が大切

実際のところ、確認として一番手軽で確実なのはXのタイムラインを定期的に目で見ることです。

時刻が意図通りか・投稿文に問題がないか・同じ記事が重複していないか——これらはXを見るだけで気づけます。「自動投稿が止まっていた」「同じ記事が2回投稿されていた」といった問題も、Xを普段から確認していれば早期に発見できます。

「自動化 = 放置できる」ではなく、「自動化 = 作業は減るが、結果の確認は必要」というのが率直な実感です。

まとめ

  • 目的:ブログ記事をXで定期発信するため、1日2回の自動投稿を仕組み化した
  • タイマーの問題:最初に使ったGitHub Actionsは混雑時に投稿時刻が数時間ズレることがあり、Cloudflare Workersに乗り換えて解決した
  • 役割分担:定時に動かすタイマーはCloudflare Workers、実際の投稿処理とAPIキーの管理はGitHubにまとめている
  • 投稿文の準備:記事を書くときにdescriptionを140文字以内で書いておくだけで、そのままX投稿文になる
  • 実際に起きた失敗:仕組みを入れた直後に過去記事が数十件まとめて投稿キューに入ってしまい、対象を直近2時間以内の記事に絞ることで修正した
  • 確認の大切さ:自動化しても定期的にXのタイムラインを目で確認することで、問題を早く見つけられる

「自動化したい」という気持ちはあっても、どのツールが何をするのかが見えていないと最初の一歩が踏み出しにくいものです。この記事の図解で全体像が掴めた方は、ぜひClaude Codeを使った実装も試してみてください。

何かの参考になれば幸いです。