SNS毎にOGP画像を変える裏技
結論
SNS毎にOGP画像を変える裏技は「SNS botのUserAgentごとに表示する画像を変える仕組みにする」です!
SNS毎にOGP画像を変える利点から実践まで順を追って説明します
OGP画像を出し分ける利点
まずOGP画像の重要性についてですが検索すると色々出てくるのですが、まとめると「適切なOGP画像が設定されているページはユーザーの目に留まりやすくなりクリックされる確率が高まる」という感じです。
これは明らかだと思います、Zennの記事もそうです。どちらが宣伝効果があるかは一目瞭然です。
OGPあり | OGPなし |
---|---|
![]() |
![]() |
商品の販売を目的としたサイトだと商品画像をOGPにしたりと色々な工夫もあります
ここにさらにどの層のユーザが見ているかを意識するとさらに先の「適切なOGP画像」の適切さが高まると考えられます。
例えばXのユーザは文章を読むのになれているのでテキスト多めの画像にする、Facebookだったら年齢層は高めでスタイリッシュなデザインが合うなど、そのSNSのユーザの特徴やそのSNS自身のデザインに合わせた画像が適しているはずです。
また、SNS毎に推奨の画像サイズ、アスペクト比があり、SNSにあったサイズに変えられるという利点もあります。
(DeepResearchに調べてもらったところ、ほとんどのSNSが1200×630が最適でそれ以外はX: 1200×628, BlueSky: 1200×627, Mastodon: 1200×675らしいです)
SNSのOGP画像の管理方法
この記事を書く中で多くのSNSについて調べた結果、SNSのOGP画像の管理方法は大きく分けて以下の2通りあることがわかりました。
通称 | 概要 | 特徴 |
---|---|---|
X(Twitter)型 | 投稿時に自サービス内にOGP画像を保存し、保存した画像を配信する | 事前に画像を検閲できる リンク元に負荷をかけない |
mixi2型 | OGP画像の生のURLでそのまま配信する | 開発工数が最小限 画像変更が即時反映される |
完全な調査はできていませんが、X(Twitter)型が多いように感じました。
ただし、X型と思われるものの中に「自サービスのProxyを介してOGP画像を取得して配信する」タイプが紛れているかもしれません...(即時反映+事前に画像を検閲できる機能を備えているのでなくはなさそうです)
実際にXでは以下のように7日間自社のサービス内にキャッシュされるようです
Content is cached by Twitter for 7 days after a link to a page with card markup has been published in a Tweet.
引用: Cards | Developer Platform
ただしこの形式の欠点としてリンク元のOGP画像変更の反映に時間がかかることがあります
(それを回避する裏技も色々ありそうです)
このZennでもhttps://embed.zenn.studio/api/optimize-og-image/xxx
のようなURLからOGP画像が配信されており、上記リンクを開いてもにリンク元にアクセスがないように見えるのでX(Twitter)型を採用しているかもしれません
(そして私の見間違えかもしれないですがZennの記事にURLを貼るとXと全く同じUser-Agent: Twitterbot/1.0
からのリクエストから来るので仕組みを一部共有している...?)
出し分けの実践
最後にSNSごとにOGP画像を出し分けるサンプルページを作ってみます!
最初に皆さんの多くが使っているであろうのX(旧Twitter), Facebook, Threads, Bluesky, mixi2, Misskey, Mastodon, Discord, SlackのUser-Agentを抽出してみます。
User-Agentの取得
アクセスされたらUser-Agentをログに残すようなサーバを起動して、実際に調べたいSNSに貼り付けてみます
まずはサーバを準備します、今回はデプロイを簡単にするためにHono+Cloudflare Workersで作りました
npm create hono@latest ua-checker
作成したtemplateのsrc/index.tsを以下に書き換えnpm run deploy
でデプロイできます!
今回はCloudflare側のログを見たかったのでwrangler.jsonc
にobservabilityを追加しておきます
import { Hono } from 'hono';
import ogpImage from './image';
const app = new Hono();
// Middleware to log requests
app.use('*', (c, next) => {
const requestUrl = new URL(c.req.url);
const sns = requestUrl.searchParams.get('sns');
const allHeader = c.req.header();
let logHeaders = '';
for (const [key, value] of Object.entries(allHeader)) {
logHeaders += `${key}: ${value} `
}
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${c.req.method} ${requestUrl.pathname} SNS: ${sns} logHeaders: ${logHeaders}`);
return next();
});
app.get('/image', (c) => {
c.header('Content-Type', 'image/png');
// OGP画像の表示に対応しているか確認するため念の為画像も表示している
// RedditやタイッツーはOGP画像表示機能がないようだった
return c.body(base64ToUint8Array(ogpImage), 200);
});
app.get('/', (c) => {
const requestUrl = new URL(c.req.url);
const sns = requestUrl.searchParams.get('sns');
const ogpImageUrl = `${requestUrl.origin}/image?sns=${sns ?? ''}`;
const html = generateHtml(ogpImageUrl);
return c.html(html);
});
function generateHtml(ogpImageUrl: string): string {
return `<!DOCTYPE html>
<html>
<head>
<meta property="og:image" content="${ogpImageUrl}" />
</head>
</html>`;
}
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "ua-checker",
"main": "src/index.ts",
"compatibility_date": "2025-05-24",
+ "observability": {
+ "enabled": true,
+ "head_sampling_rate": 1
+ }
}
これをnpm run deploy
でCloudflare Workersにデプロイして、以下のように媒体の名前をメモ代わりに変えながら、バシバシと投稿してCloudflareのログからRequest Headerを確かめてみます。
https://ua-checker.ponyo877.workers.dev?sns=platform-name
結果は以下です。
日本国内の主要なSNS/チャットのbotのUser-Agent一覧(2025年5月現在, @ponyo877調べ)
Product | User-Agent | Memo |
---|---|---|
X(Twitter) | Twitterbot/1.0 | |
Tumblr | Tumblr/14.0.835.186 | |
facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php) | 他のheaderも同じ、なのでThreadsと同じシステムを使っているっぽいので見分け得られない... | |
Threads | facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php) | 同上 |
Bluesky | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Bluesky Cardyb/1.1; +mailto:support@bsky.app) Chrome/W.X.Y.Z Safari/537.36 | 初回以降は異なるのでcache方式ではないかもしれない |
mixi2 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 | Refererがhttps://mixi.social/ なのでそれで見分けられそう |
Misskey | Mozilla/5.0 (compatible; SummalyBot/5.2.1-io.1) | |
Mastodon | Mastodon/4.3.7 (http.rb/5.2.0; +https://social.ffmuc.net/) | |
Discord | Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com) | |
Slack | Slackbot 1.0 (+https://api.slack.com/robots) | |
Line | facebookexternalhit/1.1;line-poker/1.0 |
SNS毎にOGP画像が変わるページの作成
先に取得したUser-AgentやReferer(mixi2のみ)を元にSNSのbotやユーザ参照した時に生じるGET Requestのogp:imageをSNS毎に動的に変えてみます
// Social media platform configurations
const PLATFORM_CONFIGS = [
{ userAgent: 'Twitterbot', endpoint: 'twitter' },
{ userAgent: 'Tumblr', endpoint: 'tumblr' },
{ userAgent: 'www.facebook.com', endpoint: 'meta' },
{ userAgent: 'Bluesky', endpoint: 'bluesky' },
{ userAgent: 'SummalyBot', endpoint: 'misskey' },
{ userAgent: 'Mastodon', endpoint: 'mastodon' },
{ userAgent: 'Discordbot', endpoint: 'discord' },
{ userAgent: 'Slackbot', endpoint: 'slack' },
{ userAgent: 'line-poker', endpoint: 'line' },
] as const
function detectPlatform(userAgent: string, referer: string): string {
for (const config of PLATFORM_CONFIGS) {
if (config.userAgent && userAgent.includes(config.userAgent)) {
return config.endpoint
}
}
return 'none'
}
// mixi2型のための対策
app.get('/image', (c) => {
const referer = c.req.header('Referer') || ''
if (referer.includes('mixi.social')) {
const ogpImageURL = `${R2_ENDPOINT}/mixi2.png`
return c.redirect(ogpImageURL, 302)
}
return c.redirect(`${R2_ENDPOINT}/default.png`, 302)
})
app.get('/', (c) => {
const userAgent = c.req.header('User-Agent') || ''
const referer = c.req.header('Referer') || ''
console.log(`User-Agent: ${userAgent}, Referer: ${referer}`)
const platform = detectPlatform(userAgent, referer)
let ogpImageURL = `${R2_ENDPOINT}/${platform}.png`
if (platform === 'none') {
ogpImageURL = `${SELF_ENDPOINT}/image`
}
console.log(`ogpImageURL: ${ogpImageURL}`)
const html = generateHtml(ogpImageURL)
return c.html(html)
})
export default app
先ほどと同様にこれをCloudflare Workersにデプロイして完成です。
以下のURLになります、Xに貼ればXの画像、Threadsにはればthreadsの画像、主要なSNSは大体カバーしていると思います!
https://sns-ogp-switcher.folks-chat.com
以下例(全部同じURLの投稿です!)
Misskey | Threads | mixi2 | Discord | Slack |
---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
Tumblr | Bluesky | Mastodon | Line | |
---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
今回の検証で使ったコードは以下で管理しています
まとめ
OGPの効果をより高めるために媒体毎にOGPを変える方法を紹介しました
いかがでしたでしょうか?
他にもOGPに関する記事をいくつか作っているので参照いただけたら嬉しいです。
Discussion