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

記事をSNSへ流す運用を始める―X連携の前に投稿方法と候補を確認するで、SNS連携を「投稿」「リンク表示」「流入計測」に分けて確認した。
そのあと実際の運用方法を決めた。
X APIから自動投稿する構成にはせず、XのWeb Intentを使う。
Codexには、まだXへ流していない記事を探し、投稿文とUTM付きURLを作り、Xの投稿画面を開くところまで任せる。最後の「ポスト」は自分で押す。
まず手動投稿を試し、その後に同じ作業を半自動化した。
最初は普通に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から扱えるようにした。
X APIは使わずWeb Intentを使う#
今回使うのはXのWeb Intent。
基本のURLは次のもの。
https://x.com/intent/tweet
投稿文と記事URLを渡すと、Xの投稿画面を開ける。
X APIのように投稿処理そのものを実行するわけではないため、最後はX上で内容を確認して自分で投稿する。
今回欲しかった流れにはこちらの方が合っていた。
Codexへ指示
↓
X未投稿の記事を探す
↓
記事内容から投稿文を作る
↓
UTM付きURLを作る
↓
Web Intentを開く
↓
自分で内容を確認
↓
ポスト
投稿済みの記事は別ファイルで持つ#
記事の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設定は変更していない。追加パッケージもない。
SNSは既存のサブカテゴリをそのまま使う#
今回の記事では、
category: 'site-operation'
subcategory: 'sns'
series: 'site-operation-seo-sns'
seriesOrder: 5
を使う。
Codexでsrc/data/taxonomy.tsを確認すると、snsはすでに登録済みで、表示名はSNS・外部発信になっていた。
そのためtaxonomyは変更していない。
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で第27回の記事を開いた#
実装後、最新のX未投稿記事として選ばれたのは第27回だった。
第27回 画像表示を見直す―Astroの画像最適化と遅延読み込みでページを軽くする
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を開いただけで投稿済みにすると、投稿画面を閉じただけでも投稿済みになってしまうためだ。
最後のポストは自分で実行した#
内容を確認したあと、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から第27回の記事リンクを実際に開いた。
その直後にGoogle Analyticsのリアルタイム画面を確認すると、対象記事が表示された。

画面では、
第27回 画像表示を見直す―Astroの画像最適化と遅延読み込みでページを軽くする
が表示回数2として確認できた。
これで、Xの投稿から記事を開いた後に、Google Analytics側でも対象ページへのアクセスが届くところまでは確認できた。
ただし、このリアルタイム画面だけでは、今回付けた、
source x
medium social
campaign article_publish
までを確認したことにはしない。
UTMによる流入元の確認は、通常レポートのトラフィック獲得へ反映された後に見る。
Web Intentから投稿まで確認#
今回の作業で、X投稿は次の流れになった。
記事を公開
↓
Codexが未投稿記事を確認
↓
記事内容から投稿文を作成
↓
X用UTM URLを生成
↓
Web IntentでX投稿画面を開く
↓
自分で内容を確認してポスト
↓
投稿後だけ投稿済み履歴を更新
実際に第27回の記事でWeb Intentを開き、そのままXへ投稿できた。
さらに、公開されたPostから記事を開き、Google Analyticsのリアルタイム画面で対象ページへのアクセスも確認した。
現時点で未確認なのは、通常レポート側でx / social / article_publishとして記録される部分。
そこは反映後のデータで確認する。


