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

ブログの記事を公開したあと、
「毎回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

この時点ではUTMを付けていない。
まずはXへ記事URLを出し、リンクカードまで表示されるかを確認するための投稿にした。
投稿後は、記事タイトル、description、リンクカードが表示された。

手作業でも投稿自体は難しくない。
ただ、記事が増えると毎回、
未投稿の記事を探す
投稿文を考える
記事URLをコピーする
計測用URLを作る
Xを開く
という作業が発生する。
ここをCodexから扱えるようにした。
半自動化を選んだ理由#
Codexに準備と判定を任せ、最後のポストだけ人が確認する半自動方式を選んだ。投稿直前に文章とリンクを確認できることを重視している。
Codex
↓
未投稿記事を探す
↓
投稿文を作る
↓
UTM付きURLを作る
↓
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の既定ブラウザで開いた。

投稿画面には、Codexが作成した文章と、
?utm_source=x&utm_medium=social&utm_campaign=article_publish
を含む記事URLが入っている。
Web Intentを開いただけでは投稿済みにしない#
この段階では投稿履歴は更新されない。
Web Intentを開いただけで投稿済みにすると、投稿画面を閉じただけでも投稿済み扱いになってしまう。
最後のポストは自分で実行した#
内容を確認したあと、X上で「ポストする」を押した。
公開されたPostでも、投稿文と記事のリンクカードを確認できた。

ここまでで、
未投稿記事を選ぶ
↓
投稿文を作る
↓
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のリアルタイム画面を確認すると、対象記事が表示された。

画面では、
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の起動を分けて管理し、最後の内容確認を残すことで、完全自動化にしなくても繰り返し作業を減らせた。


