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

第27回 画像表示を見直す―Astroの画像最適化と遅延読み込みでページを軽くするでは、記事本文の画像をsrc/assetsへ移し、WebP、srcset、sizes、loading="lazy"などを使う構成へ変更した。
画像の配信方法を見直した後に残ったのが、記事そのものを表す画像。
ゆまラボには本文中のスクリーンショットは多いが、記事一覧や記事上部に共通して表示するアイキャッチは使っていなかった。SNSへ記事URLを流す運用についても、記事をSNSへ流す運用を始める―X連携の前に投稿方法と候補を確認するで候補を確認した段階で、リンクを共有したときに表示する画像はまだ用意していない。
その前段では、記事のアイキャッチとOGP画像の扱いを整理する―同じ画像を無理なく再利用するとして、記事用画像とOGP画像を別々に管理するのではなく、1枚を再利用する方向も確認した。
今回はその構成を実際のAstroサイトへ追加する。
修正前は記事一覧も記事本文もテキスト中心#
修正前の記事一覧には、カテゴリ、公開日、タイトル、description、タグが表示されていた。
ただし、記事ごとの画像はない。

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

FrontmatterにもheroImageはない。

今回の作業では、まず第27回だけへアイキャッチを設定し、画像ありの記事と画像なしの記事が同時に存在する状態で確認することにした。
既存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を利用し、第27回のMarkdownへ画像を設定した。画像はsrc/assets配下に置き、Astroの画像処理へ渡す。
実装報告で変更対象になったのは次の5ファイル。
src/components/BaseHead.astro
src/layouts/SiteLayout.astro
src/layouts/ArticleLayout.astro
src/components/ArticleCard.astro
第27回のMarkdown
新しいパッケージは追加していない。package.jsonとlockファイルも変更なし。
記事詳細ではheroImageがある場合だけ画像を表示する。記事一覧も同じで、画像を持つ記事だけサムネイルを追加する構成にした。
画像なしの記事に空の画像枠や共通のダミー画像を出さない点は維持している。
記事一覧は左サムネイルの横型カードに変更した#
最初に画像を表示できる状態まで実装したあと、一覧上での画像サイズと位置も修正した。
画像が大きいままだと、1件あたりの高さが増え、これまでの一覧とはかなり違う見え方になる。そこでTOPの新着や記事一覧、カテゴリ配下の一覧で、画像がある記事は左側へ小さなサムネイルを置き、右側へ従来の情報を並べる横型に変更した。
修正後の/blog/では、第27回だけ左に画像が付き、直前の記事はテキストだけのまま表示されている。

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

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

今回の第27回では、画像最適化と遅延読み込みを扱った内容に合わせて作成した画像を使用している。
本文中の実スクリーンショットとは役割を分けた。本文画像は作業の証拠、アイキャッチは記事の入口として使う。
PCでは問題ないのにスマホで画像表示が止まる―重い画像が与える影響と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共有時の画像まで同じ入口から扱える状態になった。


