第5回 記事をXへ流す運用を始める―手動投稿からWeb Intentで半自動化する

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

執筆:ゆまラボ運営者

公開:
第5回 記事をXへ流す運用を始める―手動投稿からWeb Intentで半自動化する

記事を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

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

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

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

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

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

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

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

未投稿の記事を探す
投稿文を考える
記事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は保存していなかったため、xUrlnullのままにしている。

存在しない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.mdCLAUDE.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

xXtwitterのように途中で表記を変えない。

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の既定ブラウザで開いた。

Web Intentで第27回の記事本文とUTM付きURLが自動入力されたX投稿画面

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

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

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

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

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

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

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

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

Web Intentから作成した第27回の記事投稿が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から第27回の記事リンクを実際に開いた。

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

Xの投稿から第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として記録される部分。

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

参考#