Skip to content
Cloud AI エンジニア入門ガイド
Go back

10. AstroPaperでOG画像の自動生成とSEO

この章の全体像として、OGPとは、自動生成、使われる画像、SEOの仕組みの4つを番号順に並べ、共有と検索の見え方を整えられるようになることを示した図

シリーズ目次 前章: 09. AstroPaperの検索・RSS・サイトマップ 次章: 11. AstroPaperでUI文言の翻訳と多言語対応

Table of contents

Open Table of contents

この章のねらい

記事のURLをSNSに貼ると、タイトルと画像が付いたカードが出ます。あれを制御しているのがOGP(Open Graph Protocol)です。AstroPaperは、この画像を記事ごとに自動生成します。仕組みと、テーマに組み込まれているSEO対応を見ていきます。

OGPとは

OGPは、このページのタイトルはこれ、説明はこれ、画像はこれ、という情報をHTMLの <head> にメタタグとして書いておく取り決めです。SNSやチャットツールは、リンクが貼られるとこれを読んでカードを組み立てます。

AstroPaperの src/layouts/Layout.astro には、次のタグが出力されています。

<meta property="og:type" content="website" />
<meta property="og:site_name" content={site.title} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:image" content={socialImageURL} />src/layouts/Layout.astro

X(旧Twitter)向けのタグも合わせて出力されます。

<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={socialImageURL} />src/layouts/Layout.astro

summary_large_image は、大きな画像付きのカードで表示するという指定です。

<head> に書いた各項目が、カードのどの部分になるのかを対応させると次のようになります。

headに書かれたog:titleやog:imageなどのメタタグを、SNS側が読み取って画像付きのカードを組み立てることを左右に並べて示した図

画像を自動生成する仕組み

SatoriとSharp

記事ごとの画像は、2つのライブラリの組み合わせで作られます。

ライブラリ役割
SatoriHTMLに似た記述とCSSに似たスタイル指定から、SVG(ベクター画像)を作る
SharpSVGをPNG(写真形式の画像)に変換する

SNSはSVGを扱えないことが多いため、最後にPNGへ変換しています。

生成しているコード

記事用の画像は src/pages/posts/[...slug]/index.png.ts が作ります。ファイル名が .png.ts なのは、PNGを返すルートだからです。

export async function getStaticPaths() {
  if (!config.features.dynamicOgImage) {
    return [];
  }

  const posts = await getCollection("posts").then(p =>
    p.filter(({ data }) => !data.draft && !data.ogImage)
  );

  return posts.map(post => ({
    params: { slug: getPostSlug(post.id, post.filePath) },
    props: post,
  }));
}src/pages/posts/[...slug]/index.png.ts

ここから3つ読み取れます。

  1. dynamicOgImagefalse なら、画像は1枚も作られない
  2. 下書きの記事には作られない
  3. ogImage を自分で指定した記事にも作られない(無駄な生成を避けている)

生成される画像には、記事タイトル、著者名、サイト名が入ります。サイズは1200×630ピクセルで、これはSNSのカード表示で標準的に使われる比率です。

サイト全体用の画像

トップページやタグ一覧など、記事以外のページ用には /og.png が作られます。担当は src/pages/og.png.ts で、こちらにはサイト名・サイト説明・ドメイン名が入ります。

生成の流れと、対象になる記事の条件をまとめると次のとおりです。

記事の情報からSatoriがSVGを作りSharpがPNGへ変換する流れと、自動生成の対象になる3つの条件を示した図

どの画像が使われるか

記事ページのOG画像は、次の順で決まります。

フロントマターの指定と設定から使用されるOG画像が決まる流れを示した図

記事ページ側の処理は次のようになっています。

let ogImageUrl: string | undefined;

if (typeof initOgImage === "string") {
  ogImageUrl = initOgImage;
} else if (initOgImage?.src) {
  ogImageUrl = initOgImage.src;
}

if (!ogImageUrl && config.features.dynamicOgImage) {
  const postUrl = getPostUrl(post.id, post.filePath, locale).replace(/\/+$/, "");
  ogImageUrl = `${postUrl}/index.png`;
}src/pages/posts/[...slug]/index.astro

記事以外のページでは、src/utils/resolveDefaultOgImagePath.ts が判定します。

if (config.features.dynamicOgImage) {
  return existsInPublic(filename)
    ? getAssetPath(filename)
    : getAssetPath("og.png");
}

if (!existsInPublic(filename)) {
  throw new Error(`AstroPaper: missing public/${filename}. ...`);
}src/utils/resolveDefaultOgImagePath.ts

最後の分岐が落とし穴です。dynamicOgImagefalse にした状態で public/{site.ogImage} がないと、ビルドが落ちます。自動生成を切るなら、既定画像を先に置いてください。

安全のための制限

同じファイルの冒頭には、次の検査があります。

if (filename.includes("..") || filename.includes("/") || filename.includes("\\")) {
  throw new Error(`site.ogImage must be a single filename in public/ ...`);
}src/utils/resolveDefaultOgImagePath.ts

../ を含む値を弾いています。設定値経由で public/ の外を参照させないためです。site.ogImage にはファイル名だけを書いてください。

記事ごとに画像を指定する

自動生成ではなく、自分で用意した画像を使いたい場合はフロントマターに書きます。

ogImage: "./assets/cover.png"

src/ 側に置いた画像を相対パスで指定すると、ビルド時にハッシュ付きのパス(/_astro/cover.xxxxxxxx.png)へ解決され、それがOGメタタグに入ります。外部のURLをそのまま書くこともできます。

ogImage: "https://example.com/images/cover.png"

ここは誤解されやすいのですが、この経路ではAstroの画像最適化がかかりません。記事ページの実装は、フロントマターから受け取った ImageMetadata.src をそのままメタタグへ渡しているだけです。

if (typeof initOgImage === "string") {
  ogImageUrl = initOgImage;
} else if (initOgImage?.src) {
  ogImageUrl = initOgImage.src;
}src/pages/posts/[...slug]/index.astro

<Image><Picture>getImage() を通していないため、リサイズもWebP変換も入らず、置いた画像がそのまま配信されます。OG画像はSNS側が取得するもので、そもそも <img> として描画されないため、これ自体は妥当な実装です。

その代わりサイズと形式は自分で整えてください1200×630ピクセル前後が目安です。極端に縦長や横長だと、SNS側で切り取られて妙な見た目になります。ファイルサイズも、そのまま転送されることを踏まえて抑えておいてください。

最適化の段をひとつ飛ばしている、と考えると分かりやすい。

フロントマターに書いたogImageがAstroの画像最適化を経ずにそのままメタタグへ渡ること、寸法と容量を自分で整える必要があることを示した図

SEOのための仕組み

正規URL(canonical)

同じ内容が複数のURLで見られる状態は、検索評価が分散する原因になります。それを防ぐのが正規URLの宣言です。

const {
  canonicalURL = new URL(Astro.url.pathname, Astro.site).href,
} = Astro.props;src/layouts/Layout.astro

既定では、site.url と現在のパスから自動で組み立てられます。他サイトからの転載記事などでは、フロントマターの canonicalURL で上書きできます。

構造化データ

記事ページには、検索エンジンが内容を機械的に理解するためのデータが埋め込まれます。

const structuredData = {
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  headline: title ?? site.title,
  image: ogImage,
  ...(pubDatetime && { datePublished: pubDatetime.toISOString() }),
  ...(modDatetime && { dateModified: modDatetime.toISOString() }),
  author: [
    {
      "@type": "Person",
      name: site.author,
      ...(site.profile && { url: site.profile }),
    },
  ],
};src/layouts/PostLayout.astro

これは JSON-LD という形式で、<script type="application/ld+json"> としてHTMLに書き出されます。"@type": "BlogPosting" と宣言しておけば、これはブログ記事で公開日はこれ、著者はこの人、という情報が正確に伝わります。

site.profile を設定しておくと、著者情報にプロフィールURLが加わります。

記事ページ専用のメタ情報

PostLayout.astro は、記事ページ向けに og:type を上書きし、日時のメタタグを追加します。

<meta property="og:type" content="article" />
<meta property="article:published_time" content={...} />
<meta property="article:modified_time" content={...} />src/layouts/PostLayout.astro

一覧ページは website、記事ページは article と区別されます。

Google Search Consoleの確認

サイトの所有者確認に使うメタタグは、設定から出力できます。

site: {
  googleVerification: "<your-verification-value>",
},astro-paper.config.ts

環境変数 PUBLIC_GOOGLE_SITE_VERIFICATION からも読み込まれるため、値をリポジトリに含めたくない場合はそちらを使ってください。

ここまでの4つは、記事を書くだけで自動的に出力されます。

正規URL、構造化データ、記事ページ専用のメタ情報、Search Consoleの確認タグという4つの仕組みが自動で出力されることを並べた図

記事側でできるSEOの工夫

テーマ側の仕組みは整っています。あとは記事の書き方次第です。

項目心がけること
title内容が一目で分かる具体的な表現にする。ページタイトルとOG画像に使われる
description1〜2文で要約する。一覧・検索結果・SNSカード・RSSのすべてに出る
見出し構造本文にH1は書かない(タイトルがH1になる)。H2から順に使う
画像の代替テキスト内容を説明する文を書く
内部リンク関連記事へリンクする

description は使われる場所が多いうえに、省略もできません。あとで書くと言って放置せず、記事を書き終えたその場で見直してください。

確認方法

生成されたOG画像は、ビルド後に直接開いて確認できます。

pnpm build
pnpm preview

記事のURLの末尾に index.png を付けると、その記事用の画像が表示されます。たとえば /posts/sample/index.png です。サイト全体用の画像は /og.png です。

日本語タイトルの記事では、フォントの都合で文字が正しく表示されないことがあります。実際に画像を開いて、タイトルが読めるか確かめてください。

要点

参考資料


シリーズ目次 前章: 09. AstroPaperの検索・RSS・サイトマップ 次章: 11. AstroPaperでUI文言の翻訳と多言語対応


Share this post:

Previous Post
11. AstroPaperでUI文言の翻訳と多言語対応
Next Post
09. AstroPaperの検索・RSS・サイトマップ