アイキャッチとOGP画像を分けすぎない―Astroで1枚の元画像を一覧とSNSへ再利用する
記事一覧のアイキャッチとSNS共有時のOGP画像を別々に管理せず、1枚の元画像から用途別に出力する方法を考えます。現在のゆまラボのXリンクカードとAstro画像処理を踏まえ、Frontmatter、getImage()、フォールバックまで実装前に確認します。

技術ブログにアイキャッチ画像は必要かを考えた時点では、ゆまラボの記事一覧はほぼテキストだけで成立していた。
技術記事なので、タイトル、カテゴリ、description、タグが読めれば用件は伝わる。
その考えは今も変わらない。
ただ、記事数が増え、サイト内の一覧だけでなくXへ記事を流す運用まで始めると、画像の役割が少し変わってきた。
今後やりたい作業は2つある。
「Astro+Cloudflareでサイトを作る」側では、記事一覧にアイキャッチ画像を追加したい。
「サイトを作ったら ― 運用・SEO・SNS」側では、Xへ記事URLを投稿したときに記事ごとのOGP画像を表示したい。
一見すると別の機能だが、どちらも必要としているのは「この記事を表す横長の画像」。
ここで一覧用アイキャッチとOGP用画像を別々に作り始めると、記事を書くたびに画像管理が2系統になる。
それは避けたい。
先に考えておきたいのは、1枚の元画像を記事の画像として持ち、表示場所に応じてAstroで派生画像を作る構成。
同じ画像ファイルをどこでも無加工で使い回す、という意味ではない。
元になる画像は1つ。
記事一覧では小さく最適化する。
OGPではSNS向けの横長サイズへ変換する。
必要なら記事本文の先頭にも表示する。
この設計なら、アイキャッチとOGPを別々の資産として管理せずに済む。
アイキャッチとOGP画像は役割が違う#
言葉を先に分けておく。
ゆまラボで「アイキャッチ」と呼んでいるものは、主にサイト内で記事を見分けるための画像。
例えば、
記事一覧
カテゴリ一覧
注目コンテンツ
関連記事
記事本文の冒頭
などで使う。
一方、OGP画像はHTMLの<head>へ、
<meta property="og:image" content="https://example.com/image.jpg">
のように設定し、URLが外部サービスで共有されたときに「このページを表す画像」として渡すためのもの。
Open Graph Protocolでは、基本メタデータとして、
og:title
og:type
og:image
og:url
が定義されている。
og:imageにはさらに、
og:image:type
og:image:width
og:image:height
og:image:alt
といった情報も付けられる。
ここで重要なのは、アイキャッチとOGP画像は利用される場所が違うだけで、記事を表すという役割はかなり近いこと。
サイト内では記事カードに表示する。
サイト外ではURLのプレビューに使う。
わざわざ内容の違う2枚を用意する理由がなければ、元画像を共通化できる。
今のX投稿では画像の余地が残っている#
ゆまラボはすでにXへの投稿運用を始めている。
第5回 記事をXへ流す運用を始める―手動投稿からWeb Intentで半自動化するでは、記事URLをXへ手動投稿したあと、CodexとWeb Intentを使う方式まで進めた。
リンクカード自体は表示できている。
ただ、実際の投稿画面を見ると、現在のカードはタイトルとdescriptionが中心で、左側は汎用的なアイコン表示になっている。

この状態でもリンクとしては使える。
URLだけが裸で表示されるより内容は分かりやすい。
それでも、記事一覧へアイキャッチを追加するなら、その画像をX側でも使えた方が自然。
サイト内で見た記事カードと、Xで流れてきたリンクカードが同じ画像を使えば、「この画像の記事」として認識しやすくなる。
過去にDevToolsでmetaを確認した画面でも、og:title、og:url、og:description、twitter:cardなどは入っていた。
少なくともその確認画面では、記事固有のog:imageは見えていない。

この先の作業は、Xの投稿処理そのものを作り直す話ではない。
記事をSNSへ流す運用を始める―X連携の前に投稿方法と候補を確認するで分けた、
投稿する
リンクを表示する
流入を計測する
のうち、「リンクを表示する」側の修正になる。
Web Intentで投稿画面を開く仕組みはそのまま。
記事ページ側が、共有されたときに使える画像情報を持つようにする。
OGP画像を入れるメリットはSNSで大きく表示することだけではない#
OGP画像の話になると、「SNSで画像が大きく出るから目立つ」という説明だけになりやすい。
もちろん、それも大きな理由。
テキストだけのタイムラインでは、横長の画像が1枚あるだけで占める面積が変わる。
ただ、ゆまラボで導入する意味はそれだけではない。
URLだけでは伝わらない記事の種類を先に見せられる#
技術記事のタイトルは長くなりやすい。
例えば、
第27回 画像表示を見直す―
Astroの画像最適化と遅延読み込みでページを軽くする
なら、タイトルを読めば内容は分かる。
ただ、タイムラインを流し見している段階では、全文を読まれないこともある。
画像の中に、
Astro
Image Optimization
srcset
WebP
のような最低限の要素があれば、詳細を読まなくても技術領域を把握しやすい。
画像にタイトル全文を入れる必要はない。
むしろ、SNSカードでは小さく表示されることもあるため、長文を画像へ書くより、記事の種類が見分けられる程度でよい。
サイト内とSNSで同じ記事だと分かる#
記事一覧で見たアイキャッチ。
Xで見たOGP画像。
記事本文の冒頭で見た画像。
ここが同じ元画像なら、表示場所が変わっても記事の印象がつながる。
デザインの統一というより、記事の識別子として使える。
ゆまラボでは技術スクリーンショットが多いので、全記事へ同じテンプレートの装飾画像を付ける必要はない。
内容を表せる実スクリーンショットや技術図がある記事は、それを元にしてもよい。
以前アイキャッチを考えた記事では、横長の画像を実際に置いて表示を確認した。

この時点では「技術ブログに必須ではないが、あったほうがいいかも」という程度の判断。
X運用が始まると、画像の使い道が記事一覧だけではなくなる。
1枚作るコストに対して、利用できる場所が増える。
ここは導入を考える理由として大きい。
OGP側で画像の情報を明示できる#
Open Graph Protocolではog:imageだけでなく、画像の幅、高さ、MIME type、altを指定できる。
例えば、
<meta property="og:image" content="https://bringain.com/_astro/example.jpg">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Astroの画像最適化を説明する図">
という形。
1200 × 630はOpen Graph Protocolが必須サイズとして定めているわけではない。
ここではSNS用に使いやすい横長画像の一例として扱う。
サイズを決めることより、画像URLを明示し、何の画像なのかをaltまで持てることの方が重要。
OGP側の情報を記事データから一貫して作れれば、
タイトルは記事A
画像は記事B
descriptionだけ更新前
のようなずれを減らせる。
OGPを入れればSEO順位が上がる、という扱いにはしない#
OGP画像は検索順位を直接上げるために追加するものではない。
目的はURLが外部で共有されたときの表現を制御すること。
ゆまラボではSearch ConsoleやSEOの作業も進めているが、OGP画像を「SEO対策」として導入するつもりはない。
SNS上で記事を見つけやすくする。
記事の内容を共有前に伝える。
サイト内のアイキャッチと同じ資産を利用する。
この3つで十分意味がある。
1枚の画像をそのまま配るのではなく、元画像を共通化する#
「アイキャッチとOGPで同じ画像を使う」と書くと、
1枚のPNGを作る
↓
記事一覧でもそのPNG
↓
記事本文でもそのPNG
↓
OGPでもそのPNG
のように見える。
今回考えているのは少し違う。
1枚の元画像
│
├─ 記事一覧用
│ 小さな表示サイズ
│ responsive image
│
├─ 記事本文用
│ 本文幅
│ lazy loading
│
└─ OGP用
横長の固定出力
絶対URLをmetaへ設定
共通化するのは元データ。出力は用途ごとに変える。
この方が、以前実装したAstroの画像最適化とも噛み合う。
第27回 画像表示を見直す―Astroの画像最適化と遅延読み込みでページを軽くするでは、本文画像をpublic/から直接配る方式から、src/assets側の画像をAstroで処理する方式へ変更した。
その結果、WebP、srcset、sizes、width、height、loading="lazy"を使えるようになった。
Astroの現在の公式ドキュメントでも、src/に置いたローカル画像は変換・最適化の対象になる。
逆にpublic/内の画像は、そのままコピーされ、Astroの画像最適化は行われない。
せっかくここまで画像処理をAstroへ寄せたので、アイキャッチだけ昔のようにpublic/images/へ戻したくない。
元画像もsrc/assetsへ置く方が自然。
Frontmatterには画像を1つだけ持たせたい#
まだ実装前なので、項目名は確定していない。
ただ、データの持ち方としてはかなり単純にできる。
例えば候補は、
---
title: '記事タイトル'
description: '記事の説明'
cover: '../../../../assets/images/blog/.../cover.png'
coverAlt: '記事内容を表す画像の説明'
---
のような形。
AstroのContent Collectionsには、ローカル画像をFrontmatterから扱うためのimage() schema helperがある。
現在のschemaへ追加するなら、概念としては、
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string(),
cover: image().optional(),
coverAlt: z.string().optional(),
})
のようにできる。
実際には現在のゆまラボのschemaへ合わせて追加する必要がある。
ここで、
eyecatch:
ogImage:
の2項目を最初から作る必要はなさそう。
同じ記事なのに、
eyecatchは新しい画像
ogImageは古い画像
という状態を自分で作れるようにしてしまう。
記事を表す元画像という意味でcoverのような1項目を持ち、表示側が用途に合わせて変換する方が管理しやすい。
将来、本当にOGP専用画像が必要な記事が出たら、その時にoverride用の項目を追加すればよい。
最初から例外用の仕組みまで持つ必要はない。
記事一覧はAstroのImage処理をそのまま使える#
一覧側は比較的分かりやすい。
記事データからcoverを取り、
<Image
src={post.data.cover}
alt={post.data.coverAlt}
width={360}
height={189}
fit="cover"
/>
のように表示する。
現在のAstroにはresponsive imageの仕組みがあり、layoutを使えばsrcsetやsizesも生成できる。
今のゆまラボは画像最適化を実装済みなので、一覧でも「元画像をそのまま縮小表示する」方向へ戻す必要はない。
一覧ではカードサイズへ合わせた画像を生成する。
スマホでは小さい候補を使う。
PCでは必要な幅を使う。
記事数が増えても、元画像を1枚ずつ持つだけでよい。
OGP用はgetImage()で別サイズを作れる#
OGP側はHTML本文に直接画像を表示するわけではない。
<head>のmetaへ、外から取得できる画像URLを渡す必要がある。
AstroにはgetImage()がある。
公式ドキュメントでも、getImage()はHTMLへ直接<Image>を置く以外の用途で変換済み画像を生成したい場合に使えると説明されている。
例えば将来の実装候補は、
---
import { getImage } from 'astro:assets';
const ogImage = await getImage({
src: post.data.cover,
width: 1200,
height: 630,
fit: 'cover',
format: 'jpg',
});
const ogImageUrl = new URL(ogImage.src, Astro.site).href;
---
<meta property="og:image" content={ogImageUrl} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content={post.data.coverAlt} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImageUrl} />
という形が考えられる。
このコードをそのまま現在のレイアウトへ入れる、という意味ではない。
実装時には、今のSiteLayoutや記事レイアウトがmetaをどこで生成しているかを確認し、そこへ合わせる。
ポイントは、
post.data.cover
が1つあれば、
記事一覧
OGP
X Card
で同じ元画像を参照できること。
getImage()の出力には生成画像のsrcが含まれるため、それをサイトURLと組み合わせて絶対URLにする。
SNSからアクセスする画像なので、最終的には本番HTMLで、
https://bringain.com/...
から始まる取得可能なURLになっていることを確認する必要がある。
画像サイズは1つの表示枠だけに合わせない#
一覧とOGPの共通化で困るのがアスペクト比。
記事一覧を16:9にしたい。
OGPはもう少し横長にしたい。
こういう差は普通に出る。
そこで「全表示箇所を完全に同じ比率にする」より、中央に主要情報を置いた横長の元画像を作る方が使いやすい。
例えば元画像をSNSでも使いやすい横長サイズで作り、
中央:
記事を識別する主要要素
左右:
切れても困らない背景・余白
にする。
一覧ではfit="cover"で少し切る。
OGPでは横長全体を使う。
スマホの小さいカードでも中央部分は残る。
画像の端ぎりぎりへ、
記事タイトル
重要なコード
重要な数字
を配置しない。
これだけでも再利用しやすさはかなり変わる。
技術記事の場合、画像内へ記事タイトルを全部書く必要もない。
タイトルはHTML側にもOGP側にも別途存在する。
画像では、
Astro
OGP
X Card
程度の短いラベルや、実際の画面・構成図を見せる方が使いやすい。
技術記事は「専用の装飾画像を毎回作る」にしない#
ここは以前のアイキャッチ考察から変えたくない部分。
記事を書くたびに、
本文完成
↓
SNS映えする画像を新規制作
↓
文字を配置
↓
デザインを調整
↓
ようやく公開
という運用にはしたくない。
ゆまラボは技術記録が中心。
エラー画面、設定画面、実装結果、比較画面、技術図など、すでに記事内容を表す画像が存在することが多い。
その場合は、その画像を元にする。
ただし、DevToolsを画面全体で撮ったスクリーンショットのように、小さくすると何も読めない画像をそのままOGPへ使うのも違う。
必要なら、
実スクリーンショットを必要箇所だけ切り出す
技術図を横長に再配置する
背景に余白を足す
短い技術名だけ添える
程度の加工をする。
「アイキャッチ画像を作る」より、
記事を代表できる元画像を1枚決める
と考えた方がゆまラボには合う。
記事本文に画像がない記事だけ、共通テンプレートから簡単なカバーを作ればよい。
全記事を同じデザインへ無理に揃える必要はない。
画像がない過去記事はフォールバックで始められる#
もう一つ避けたいのが、アイキャッチ対応を始めるために過去記事を全部直すこと。
記事数はすでに増えている。
全Markdownへ一気にcoverを追加し、全記事用の画像を作るとなると、それだけで別の大きな作業になる。
最初は、
coverあり
→ 記事固有の画像
coverなし
→ ゆまラボ共通のデフォルト画像
でよい。
新規記事からcoverを付ける。
重要な過去記事やXへ再投稿したい記事だけ、あとから追加する。
これなら既存記事のFrontmatterを大量に変更せず始められる。
X側も同じ。
記事固有の画像があればそれを使う。
なければデフォルトOGP。
リンクカードが画像なしに戻るのではなく、最低限ゆまラボの共通画像は出せる。
次の実装では2本の連載が同じ画像データを使う#
ここまで考えると、これから予定している2つの作業は別々に見えて、実際には同じ土台を使う。
「Astro+Cloudflareでサイトを作る」側では、
Content Collectionへcoverを追加
↓
記事一覧へ画像を表示
↓
PC・スマホでカード表示を確認
↓
画像最適化を確認
を行う。
「サイトを作ったら ― 運用・SEO・SNS」側では、
同じcoverを取得
↓
OGP用サイズへ変換
↓
og:imageへ設定
↓
twitter:imageへ設定
↓
X Web Intentで実投稿
↓
リンクカードに画像が出ることを確認
を行う。
X投稿の仕組みそのものは、すでにWeb Intent方式で動いている。
変わるのは、投稿URLをXが読みに来たときに返すmeta。
サイト一覧のアイキャッチ対応が先に入れば、OGP側はその画像データを利用できる。
逆に、両方で別の画像項目を作ってしまうと、このつながりが消える。
だから実装前の段階で、
記事を表す画像は1項目
表示先ごとにAstroで派生画像を作る
という方針を決めておきたい。
これは画像を1枚減らすだけの話ではない。
記事一覧、記事本文、OGP、X投稿という、今まで別々に進めてきた部分をFrontmatterの1つの情報へ集約することになる。
サイト制作側で作った画像が、そのまま運用・SNS側でも使える。
ここまで来ると、アイキャッチは単なる飾りではなく、記事データの一部として扱った方が分かりやすい。
実装時に確認したいこと#
次の連載作業では、まだ決めていない部分を実際のコードで確認する。
特に見るのは、
現在のContent Collection schemaへ画像項目をどう追加するか
画像パスを現在のsrc/assets構成とどう合わせるか
記事一覧の画像比率をどうするか
既存記事のfallbackをどこで決めるか
getImage()で作ったOGP画像URLが本番から取得できるか
og:imageのwidth / height / altまで出力できるか
Xで画像付きリンクカードになるか
このあたり。
見た目だけなら記事一覧へ画像を付ける作業で終わる。
ただ、今のゆまラボではその先にX投稿がある。
画像最適化もすでに入っている。
アイキャッチについて考えた記事もある。
それぞれ個別に作った機能が、ようやく同じ画像データへつながる段階に来た。
記事ごとに横長画像を1枚持つ。
その1枚を、Astroが表示先に合わせて使う。
一覧では軽いサムネイル。
SNSではOGP画像。
必要なら本文でも使う。
この構成なら、画像を増やすほど管理が増える状態を避けながら、サイト内とサイト外の両方で記事を見せやすくできる。


