目次
Xポスト埋め込みを実装したサイトで、X からコピーしたURLを貼ったら埋め込みが表示されない事故があった。publish.twitter.com/oembed に投げても空応答が返っている。ブラウザでアクセスすると同じURLから正常にポストが見えるので、最初は原因がつかめなかった。
oEmbed に投げる前に x.com を twitter.com に書き換える
結論を先に書くと、リクエストURLを twitter.com ドメインに正規化してから oEmbed に渡せば動く。
const normalized = url.replace('https://x.com/', 'https://twitter.com/')
const res = await fetch(`https://publish.twitter.com/oembed?url=${normalized}`)
これだけで、X からコピーした x.com/<user>/status/<id> 形式のURLも今までどおり埋め込める。サーバー側の Markdown→HTML 変換でも、ユーザーが書いたURLをそのまま投げず、必ずこの置換を挟む。
oEmbed のURL一致は厳密
publish.twitter.com/oembed[1] は、リクエストの url パラメータをホワイトリスト形式で照合する。受理されないドメインのURLが来た場合は、エラーを返すのではなく、HTML キーが入らないJSON(実質空の応答)を返す挙動になる。
X(Twitter)は2023年以降、ユーザーから見えるドメインを x.com に切り替えている。一方で publish 側のホワイトリストは長らく twitter.com 固定のままなので、x.com の URL は対応外の扱いになる。エラーレスポンスではなく空応答を返してくる仕様のため、HTTPステータスだけ見て成功扱いしていると気付きにくい。
ブラウザのリダイレクト挙動とは無関係
ブラウザで x.com/<user>/status/<id> を開くと自動で twitter.com にリダイレクトされる場面もあるため、「oEmbed APIもリダイレクトを追ってくれるのでは」と期待しがちだが、追わない。oEmbed のサーバーは渡された url パラメータのドメインを文字列として判定して、ホワイトリスト外なら何もせず空を返す。クライアント側でリダイレクトを先回りして正規化する責務になる。
正規化を埋め込みパイプラインのどこに置くか
oEmbed を呼ぶ処理が複数の入り口にある場合(記事本文の埋め込み、コメント欄の埋め込み、サイドバーの埋め込みなど)、それぞれで x.com 対応を入れていくと抜けが出る。URLを抽出して oEmbed に投げる1関数の中で、必ず正規化してから fetch する形にしておくと安全。
このブログでは lib/api.ts の getTwitterEmbedHtml に正規化を集約している。
export const getTwitterEmbedHtml = async (twitterLink: string): Promise<string> => {
const match = /href="([^"]*)"/g.exec(twitterLink)
if (!match) return ''
const twitterUrl = match[1].replace('https://x.com/', 'https://twitter.com/')
const res = await fetch(`https://publish.twitter.com/oembed?url=${twitterUrl}&align=center`)
const json = await res.json()
return json.html
}
記事ファイルの段階で x.com を一律置換すると、ユーザーが書いた素のURLがコミット時に書き換わって執筆体験を壊すので、ストレージ側ではなく oEmbed 呼び出し直前で変換する。
入力側のURL抽出も x.com に対応させる
ユーザーが書いた Markdown から「埋め込み対象URL」を抽出する正規表現が twitter.com 固定のままだと、そもそも x.com のURLが抽出されず、oEmbed まで届かない。ホワイトリスト型の照合になっているので、抽出側の regex も (?:twitter|x)\.com のように両ドメインを受け付ける形にしておく。
const twitterLinkRegex = /href="https:\/\/(?:twitter|x)\.com\/[^"']*\/status\/(\d+)"/g
ユーザー側はXから貼ったままで書いてよくて、システム側で抽出から正規化までを吸収する分担にしておくと、執筆フローと表示フローを切り離せる。
oEmbed API - X Developer Platform (2026-05-26 アクセス) ↩︎
