X投稿をWeb Intentで半自動化する―Codexで未投稿管理・UTM・GA4確認まで

ゆまラボの記事をXへ流す運用を、手動投稿からWeb Intentを使った半自動方式へ進めました。未投稿記事の管理、UTM付きURL、Codexからの投稿画面起動、実投稿、Google Analyticsのリアルタイム確認まで記録します。

公開:更新:
X投稿をWeb Intentで半自動化する―Codexで未投稿管理・UTM・GA4確認まで

ブログの記事を公開したあと、

「毎回X用の投稿文を考えるのが面倒」
「どの記事を投稿済みなのか分からなくなる」
「Xからアクセスされたか確認したい」
「でも、そのためだけにX APIを使った仕組みまで作るのは大げさ」

と感じることがあります。

ゆまラボでも記事数が増えるにつれて、記事を公開してからXへ投稿するまでの作業が少しずつ面倒になってきました。

そこで実際に試したのが、CodexとXのWeb Intentを組み合わせた半自動化です。

Codexには、まだXへ投稿していない記事の抽出、投稿文の作成、UTM付きURLの生成、X投稿画面を開くところまで任せます。

最後の「ポストする」だけは自分で確認して押します。

完全自動投稿にはしていません。

実際に手動投稿から始め、Web Intentによる投稿、投稿済み記事の管理、UTM、Google Analyticsでのリアルタイム確認まで試しました。

この記事では、X APIを使わずにX投稿を半自動化した方法と、実際に運用して決めたルールをまとめます。

ブログ記事をSNSへ投稿する方法|X・Threads・Bluesky・Mastodonを比較で、SNS連携を「投稿」「リンク表示」「流入計測」に分けて確認した。

最初は普通にXへ投稿した#

半自動化する前に、現在のやり方で1件投稿した。

対象にしたのは、CodexのLunaとAstraを比較した記事。

https://bringain.com/blog/codex-luna-astra-astro-comparison/

投稿文は短くした。

CodexでLunaとAstraをAstroサイト改修に使って比較しました。

https://bringain.com/blog/codex-luna-astra-astro-comparison/

#Codex #AI #Astro

XへLunaとAstraの比較記事を手動投稿する直前の画面

この時点ではUTMを付けていない。

まずはXへ記事URLを出し、リンクカードまで表示されるかを確認するための投稿にした。

投稿後は、記事タイトル、description、リンクカードが表示された。

Xへ手動投稿したLunaとAstra比較記事が公開された画面

手作業でも投稿自体は難しくない。

ただ、記事が増えると毎回、

未投稿の記事を探す
投稿文を考える
記事URLをコピーする
計測用URLを作る
Xを開く

という作業が発生する。

ここをCodexから扱えるようにした。

半自動化を選んだ理由#

Codexに準備と判定を任せ、最後のポストだけ人が確認する半自動方式を選んだ。投稿直前に文章とリンクを確認できることを重視している。

Codex
↓
未投稿記事を探す
↓
投稿文を作る
↓
UTM付きURLを作る
↓
Web Intentを開く
↓
人が内容を確認
↓
投稿

Codexで未投稿記事を抽出し、Web Intentを開き、人が最終投稿する流れ

※ 記事内の図は内容を整理するために作成した概念図です。各社の公式資料や実際の製品画面ではありません。

Web Intentを使う#

今回使うのはXのWeb Intent。

基本のURLは次のもの。

https://x.com/intent/tweet

投稿文と記事URLを渡すと、Xの投稿画面を開ける。

X APIのように投稿処理そのものを実行するわけではないため、最後はX上で内容を確認して自分で投稿する。

今回欲しかった流れにはこちらの方が合っていた。

投稿直前に人が文章とリンクを確認できるため、今回の運用にはこの方式が合っていた。

投稿済みの記事は別ファイルで管理する#

記事のFrontmatterにはXの投稿状態を追加しなかった。

サイト表示に使う情報と、SNS運用の記録を分けるため、投稿済み記事は次へ保存する形にした。

data/sns/x-posted.json

最初に手動投稿した記事は次の状態で登録した。

{
  "version": 1,
  "posts": [
    {
      "articleUrl": "https://bringain.com/blog/codex-luna-astra-astro-comparison/",
      "status": "posted",
      "postedAt": "2026-09-12",
      "xUrl": null
    }
  ]
}

実際のX投稿URLは保存していなかったため、xUrlはnullのままにしている。

存在しないURLは補わない。

Codex用の投稿スクリプトを作る#

Codexへ実装を依頼した結果、追加した中心ファイルは次の2つ。

scripts/x-post.mjs
data/sns/x-posted.json

x-post.mjsでは、次の処理を扱う。

  • X未投稿の記事を探す
  • 対象記事の本文とmetadataを確認する
  • 記事内容に合わせて投稿文を作る
  • UTM付きURLを作る
  • Web Intent URLを作る
  • Windowsの既定ブラウザで開く
  • 人が投稿したと明示した場合だけ履歴を更新する

さらにAGENTS.mdとCLAUDE.mdへ運用手順を追加した。

新しいCodexチャットでも、

最新記事をX投稿用に開いて
直近10件のうち、Xへ投稿していない記事を出して

といった指示から同じ運用へ入れるようにしている。

package.json、Content Collection schema、既存記事のFrontmatter、Google Analytics設定は変更していない。追加パッケージもない。

X用URLにはUTMを付ける#

Google AnalyticsでX経由のアクセスを後から判別するため、Web Intentへ渡す記事URLにはUTMを付ける。

固定した値は次の3つ。

utm_source=x
utm_medium=social
utm_campaign=article_publish

通常の記事URLは投稿履歴の管理に使う。

https://bringain.com/blog/27-astro-image-optimization-lazy-loading/

Xへ出すときだけ、次のURLを使う。

https://bringain.com/blog/27-astro-image-optimization-lazy-loading/?utm_source=x&utm_medium=social&utm_campaign=article_publish

x、X、twitterのように途中で表記を変えない。

Web Intentで画像最適化の記事を開いた#

実装後、最新のX未投稿記事として選ばれたのは画像最適化の記事だった。

Astroで画像を最適化する方法|WebP・srcset・遅延読み込みを実装

Codexが記事本文とmetadataを読み、次の投稿文を作った。

Astroの本文画像を標準の画像処理へ移し、WebP・srcset・sizes・width/height・loading=lazyを追加。実ファイル278件、本文参照281件を対象に、PCとスマホ表示まで確認した記録です。

この文章とUTM付きURLからWeb Intentを生成し、Windowsの既定ブラウザで開いた。

Web Intentで画像最適化の記事本文とUTM付きURLが自動入力されたX投稿画面

投稿画面には、Codexが作成した文章と、

?utm_source=x&utm_medium=social&utm_campaign=article_publish

を含む記事URLが入っている。

Web Intentを開いただけでは投稿済みにしない#

この段階では投稿履歴は更新されない。

Web Intentを開いただけで投稿済みにすると、投稿画面を閉じただけでも投稿済み扱いになってしまう。

最後のポストは自分で実行した#

内容を確認したあと、X上で「ポストする」を押した。

公開されたPostでも、投稿文と記事のリンクカードを確認できた。

Web Intentから作成した画像最適化の記事投稿がXで公開された画面

ここまでで、

未投稿記事を選ぶ
↓
投稿文を作る
↓
UTM URLを作る
↓
X投稿画面を開く

まではCodex側へ任せられるようになった。

最後の投稿操作だけはX上で行う。

投稿済み履歴を更新する場合も、実際に投稿したことを確認した後に行う。

Codexが報告した更新コマンドは次の形。

node scripts/x-post.mjs mark-posted <slug> --posted-at YYYY-MM-DD --x-url https://x.com/...

XのPost URLが分からない場合は、存在しないURLを作らずnullのまま扱う。

ビルドは成功、verifyは既存の重複slugで止まった#

実装後の確認結果は次の通り。

npx astro sync --force
→ 成功

npm run build
→ 成功
→ 196ページ生成
→ Pagefind 62ページ
→ 内部リンク 6,741件
→ 壊れたリンク 0件

git diff --check
→ 成功

一方、

node scripts/verify.mjs

は、今回のX投稿機能とは別の既存問題で失敗した。

62 !== 63

重複しているslugはclaude-fable-5-1-engineer-viewで、次の2ファイルが対象。

src/content/blog/claude-fable-5-1-engineer-view.md
src/content/blog/ai/development-support/claude-fable-5-1-engineer-view.md

今回はX投稿運用の追加なので、この重複slugは変更していない。

XのPostから記事を開き、GA4リアルタイムまで確認した#

公開したXのPostから画像最適化の記事リンクを実際に開いた。

その直後にGoogle Analyticsのリアルタイム画面を確認すると、対象記事が表示された。

Xの投稿から画像最適化の記事を開いた後、Google Analyticsリアルタイムで対象ページを確認した画面

画面では、

Astroで画像を最適化する方法|WebP・srcset・遅延読み込みを実装

が表示回数2として確認できた。

これで、Xの投稿から記事を開いた後に、Google Analytics側でも対象ページへのアクセスが届くところまでは確認できた。

ただし、このリアルタイム画面だけでは、今回付けた、

source   x
medium   social
campaign article_publish

までを確認したことにはしない。

UTMによる流入元の確認は、通常レポートのトラフィック獲得へ反映された後に見る。

実際に運用して決めたルール#

今回の作業で、X投稿は次の流れになった。

記事を公開
↓
Codexが未投稿記事を確認
↓
記事内容から投稿文を作成
↓
X用UTM URLを生成
↓
Web IntentでX投稿画面を開く
↓
自分で内容を確認してポスト
↓
投稿後だけ投稿済み履歴を更新

実際に画像最適化の記事でWeb Intentを開き、そのままXへ投稿できた。

さらに、公開されたPostから記事を開き、Google Analyticsのリアルタイム画面で対象ページへのアクセスも確認した。

運用ルールは次のとおり。

  • 完全自動投稿にはしない
  • 投稿直前に人間が確認する
  • Intentを開いただけでは投稿済みにしない
  • SNS状態を記事Frontmatterへ混ぜない
  • X投稿URLが分からなければnullのままにする
  • 未投稿判定は管理ファイルを基準にする

現時点で未確認なのは、通常レポート側でx / social / article_publishとして記録される部分。

そこは反映後のデータで確認する。

実際にやって分かったこと#

準備部分をCodexへ任せ、公開操作は人が担当する方式なら、記事数が増えたゆまラボでも扱いやすかった。未投稿記事の判定、投稿文、UTM URL、Web Intentの起動を分けて管理し、最後の内容確認を残すことで、完全自動化にしなくても繰り返し作業を減らせた。

参考#