シリーズ目次 前章: 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> に書いた各項目が、カードのどの部分になるのかを対応させると次のようになります。
画像を自動生成する仕組み
SatoriとSharp
記事ごとの画像は、2つのライブラリの組み合わせで作られます。
| ライブラリ | 役割 |
|---|---|
| Satori | HTMLに似た記述とCSSに似たスタイル指定から、SVG(ベクター画像)を作る |
| Sharp | SVGを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つ読み取れます。
dynamicOgImageがfalseなら、画像は1枚も作られない- 下書きの記事には作られない
ogImageを自分で指定した記事にも作られない(無駄な生成を避けている)
生成される画像には、記事タイトル、著者名、サイト名が入ります。サイズは1200×630ピクセルで、これはSNSのカード表示で標準的に使われる比率です。
サイト全体用の画像
トップページやタグ一覧など、記事以外のページ用には /og.png が作られます。担当は src/pages/og.png.ts で、こちらにはサイト名・サイト説明・ドメイン名が入ります。
生成の流れと、対象になる記事の条件をまとめると次のとおりです。
どの画像が使われるか
記事ページの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
最後の分岐が落とし穴です。dynamicOgImage を false にした状態で 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側で切り取られて妙な見た目になります。ファイルサイズも、そのまま転送されることを踏まえて抑えておいてください。
最適化の段をひとつ飛ばしている、と考えると分かりやすい。
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つは、記事を書くだけで自動的に出力されます。
記事側でできるSEOの工夫
テーマ側の仕組みは整っています。あとは記事の書き方次第です。
| 項目 | 心がけること |
|---|---|
title | 内容が一目で分かる具体的な表現にする。ページタイトルとOG画像に使われる |
description | 1〜2文で要約する。一覧・検索結果・SNSカード・RSSのすべてに出る |
| 見出し構造 | 本文にH1は書かない(タイトルがH1になる)。H2から順に使う |
| 画像の代替テキスト | 内容を説明する文を書く |
| 内部リンク | 関連記事へリンクする |
description は使われる場所が多いうえに、省略もできません。あとで書くと言って放置せず、記事を書き終えたその場で見直してください。
確認方法
生成されたOG画像は、ビルド後に直接開いて確認できます。
pnpm build
pnpm preview
記事のURLの末尾に index.png を付けると、その記事用の画像が表示されます。たとえば /posts/sample/index.png です。サイト全体用の画像は /og.png です。
日本語タイトルの記事では、フォントの都合で文字が正しく表示されないことがあります。実際に画像を開いて、タイトルが読めるか確かめてください。
要点
- 記事のOG画像はSatoriでSVGを作り、SharpでPNGに変換して1200×630ピクセルで生成される。
- 自動生成の対象は「下書きでなく、
ogImageを自分で指定していない記事」に限られる。 dynamicOgImageをfalseにする場合、public/{site.ogImage}を必ず用意しないとビルドが失敗する。site.ogImageにはpublic/直下のファイル名のみ指定でき、/や..を含むとエラーになる。- フロントマターの
ogImageは.srcがそのまま使われるだけで最適化は通らないため、寸法とファイルサイズは自分で整える。 - 記事ページには正規URL、
og:type: article、公開・更新日時、JSON-LDの構造化データが自動で出力される。
参考資料
- テンプレート同梱ソース
src/pages/posts/[...slug]/index.png.ts— 記事ごとのOG画像生成 - テンプレート同梱ソース
src/pages/og.png.ts— サイト全体用のOG画像生成 - テンプレート同梱ソース
src/utils/resolveDefaultOgImagePath.ts— 既定画像の決定と安全確認 - テンプレート同梱ソース
src/layouts/Layout.astro/PostLayout.astro— メタタグと構造化データ - Satori リポジトリ — HTMLライクな記述からのSVG生成
- Sharp 公式ドキュメント — 画像変換
- The Open Graph Protocol — OGPの仕様
- Schema.org: BlogPosting — 構造化データの語彙
シリーズ目次 前章: 09. AstroPaperの検索・RSS・サイトマップ 次章: 11. AstroPaperでUI文言の翻訳と多言語対応