アイキャッチとOGP画像を追加する方法|同じ画像を一覧・本文・SNSで利用
AstroブログのheroImageを記事一覧と記事本文へ表示し、同じ画像から1200×630のOGP画像を生成。画像なしの記事との互換性、summary_large_image、一覧レイアウトまで確認した記録です。

記事ごとに、 「一覧用のアイキャッチを作る」 「SNS用のOGP画像も別に作る」 「本文上部にも画像を置く」 という3系統の画像管理になって困っていないでしょうか。元画像を1枚にまとめられれば、記事追加のたびに同じ画像を何度も管理する必要がなくなります。
ゆまラボでは本文のスクリーンショットは多数ありましたが、記事一覧と記事上部に共通して使う画像はなく、og:imageも未設定、Twitter Cardもsummaryの状態でした。そこでFrontmatterのheroImageを入口にして、記事一覧のサムネイル、記事本文のアイキャッチ、1200×630のOGP画像へ同じ元画像を展開する構成を実際に追加しました。
この記事では、heroImageを1か所だけ管理してサイト内表示とSNS用画像へ再利用する方法、画像のない既存記事を壊さず段階移行する方法、生成されたOGPメタ情報の確認まで紹介します。
Astroで画像を最適化する方法|WebP・srcset・遅延読み込みを実装では、記事本文の画像をsrc/assetsへ移し、WebP、srcset、sizes、loading="lazy"などを使う構成へ変更した。
ゆまラボには本文中のスクリーンショットは多いが、記事一覧や記事上部に共通して表示するアイキャッチは使っていなかった。SNSへ記事URLを流す運用についても、ブログ記事をSNSへ投稿する方法|X・Threads・Bluesky・Mastodonを比較で候補を確認した段階で、リンクを共有したときに表示する画像はまだ用意していない。
その前段では、記事のアイキャッチとOGP画像の扱いを整理する―同じ画像を無理なく再利用するとして、記事用画像とOGP画像で1枚を再利用する方向も確認した。
修正前は記事一覧も記事本文もテキスト中心#
修正前の記事一覧には、カテゴリ、公開日、タイトル、description、タグが表示されていた。
ただし、記事ごとの画像はない。

記事数が64件まで増えた状態でも一覧の構成は同じで、タイトルを読めば内容は分かるが、画像から記事を判別する要素はなかった。
画像最適化の記事本文も、タイトル、description、公開日、更新日、タグの次がそのまま目次になっていた。

FrontmatterにもheroImageはない。

今回の作業では、まず画像最適化の記事だけへアイキャッチを設定し、画像ありの記事と画像なしの記事が同時に存在する状態で確認することにした。
既存64記事へ一括で仮画像を付ける方法は採らない。画像を持つ記事だけ画像を表示し、それ以外は従来の表示を維持する。
OGPにはタイトルとdescriptionがあるが画像はなかった#
修正前の<head>も確認した。
og:type、og:url、og:title、og:descriptionはすでに出力されている。一方、og:imageはなく、Twitter Card側もsummary。

今回は、既存の画像資産をOGPへつなぐ部分を整える。
既存のmeta生成へ画像情報を追加し、記事側のアイキャッチと同じ元画像を渡せるようにする。
既存のheroImageをそのまま使う#
Codexで現在の実装を確認したところ、Content Collection側にはすでに画像用の定義があった。
heroImage: z.optional(image())
そのため、schemaへ新しいフィールドを追加する必要はなかった。
今回の変更では既存のheroImageを利用し、画像最適化記事のMarkdownへ画像を設定した。画像はsrc/assets配下に置き、Astroの画像処理へ渡す。
実装報告で変更対象になったのは次の5ファイル。
src/components/BaseHead.astro
src/layouts/SiteLayout.astro
src/layouts/ArticleLayout.astro
src/components/ArticleCard.astro
src/content/blog/web-development/operation/27-astro-image-optimization-lazy-loading.md
新しいパッケージは追加していない。package.jsonとlockファイルも変更なし。
記事詳細ではheroImageがある場合だけ画像を表示する。記事一覧も同じで、画像を持つ記事だけサムネイルを追加する構成にした。
画像なしの記事に空の画像枠や共通のダミー画像を出さない点は維持している。
記事一覧は左サムネイルの横型カードに変更した#
最初に画像を表示できる状態まで実装したあと、一覧上での画像サイズと位置も修正した。
画像が大きいままだと、1件あたりの高さが増え、これまでの一覧とはかなり違う見え方になる。そこでTOPの新着や記事一覧、カテゴリ配下の一覧で、画像がある記事は左側へ小さなサムネイルを置き、右側へ従来の情報を並べる横型に変更した。
修正後の/blog/では、画像最適化の記事だけ左に画像が付き、画像を設定していない既存記事はテキストだけのまま表示されている。

この状態なら、既存記事へ画像を一括追加しなくても段階的に移行できる。
カテゴリ・サブカテゴリ側でも同じ表示を確認した。

画像はカードの主役にしすぎず、記事を見分ける補助として使う。タイトル、description、タグを読む流れは残している。
記事本文にはタイトル情報の下へアイキャッチを表示した#
記事詳細では、タイトル、description、公開日、更新日、タグの後にアイキャッチが入るようになった。

この画像最適化記事では、内容に合わせて作成したアイキャッチを使用している。
本文中の実スクリーンショットとは役割を分けた。本文画像は作業の証拠、アイキャッチは記事の入口として使う。
スマホで画像表示が止まる原因は?Astroの画像最適化で改善した方法でも画像サイズの影響を確認しているため、アイキャッチだけ別経路で無制限に大きな画像を配る構成にはしない。
heroImageから1200×630のOGP画像を作る#
OGP側はBaseHead.astroで処理する。
Codexの実装報告では、記事へ設定された同じheroImageをgetImage()へ渡し、OGP用として1200×630のPNGを生成する方式になった。
処理の関係は次の形になる。
MarkdownのheroImage
↓
記事詳細のアイキャッチ
↓
記事一覧のサムネイル
↓
BaseHeadでOGP用1200×630 PNGを生成
↓
og:image / twitter:image
アイキャッチ用とOGP用で、内容が同じ画像ファイルを2枚管理する必要はない。
Astro側で用途に応じた出力を作り、元画像は1つにする。
修正後の<head>では、次の項目を実画面で確認できた。
og:image
og:image:type = image/png
og:image:width = 1200
og:image:height = 630
twitter:card = summary_large_image
twitter:image

og:imageとtwitter:imageは同じ/_astro/...pngを参照している。
画像ありの記事ではsummary_large_imageを使用し、画像なしの記事は従来どおり画像metaを出さずに動作することも実装時に確認している。
OGPのundefined出力は実装中に修正した#
最初の実装では、OGP用のmetaへ未定義値が入り得る状態が一度発生した。
ここは途中で修正し、画像URLや画像情報をいったん確定した値として扱ってからmetaへ渡す形に変更した。
今回の記事ではその途中状態を完成形として扱わない。最終確認した画面ではog:image、twitter:imageとも実URLが入り、サイズも1200×630で出力されている。
buildと内部リンクチェックまで確認した#
実装後のローカルビルドは成功した。
201 pages built
6,982 internal links checked
broken links: 0
既存の重複slugや未登録タグに関する警告は残っているが、今回追加したアイキャッチ・OGP処理が原因のビルド失敗はない。
PCとスマホについても生成HTMLのレスポンシブ属性を確認し、画像あり・画像なしの両方でページ生成できる状態になった。
今回はローカルでの実装と確認まで。commit、push、デプロイはこの作業では行っていない。
heroImageをサイト内とOGPで共用#
記事へ設定する画像はheroImageへ一本化した。
記事詳細では大きなアイキャッチとして表示し、記事一覧では小さなサムネイルとして使う。OGPでは同じ元画像から1200×630のPNGを生成し、og:imageとtwitter:imageへ設定する。
画像がない既存記事は従来表示を維持するため、全記事へ一度に画像を用意する必要もない。
これで、今後作る記事では1枚のアイキャッチを用意すれば、サイト内の一覧、記事本文、SNS共有時の画像まで同じ入口から扱える状態になった。


