Astroサイトに動的なOGP/Twitterカードメタタグを追加する方法

ブログ記事などをSNSでシェアした際に、適切なタイトル、説明、画像が表示されるように、OGP (Open Graph Protocol) やX(Twitter)用のメタタグを設定します。

ページの内容に合わせてこれらの情報を動的に変える方法を説明します。

**前提:**

* サイト共通のレイアウトコンポーネント（例: `src/layouts/BaseLayout.astro`）を使用している。
* `astro.config.mjs` にサイトの公開URLが `site` オプションとして設定されている（例: `site: 'https://your-domain.com'`）。

## AstroにOGPタグを入れる手順

### 1.レイアウトコンポーネントを編集

ページ共通で使用しているレイアウトファイル(例: `src/layouts/BaseLayout.astro`)を開きます。

フロントマター (`---` 内) で、各ページから受け取る情報（タイトル、説明、OGP画像パスなど）をPropsとして定義します。また、絶対URLを生成する処理を追加します。

```typescript
---
// src/layouts/BaseLayout.astro
import type { CollectionEntry } from 'astro:content'; // ブログ記事の場合など

// ページから受け取るPropsの型定義
interface Props {
  title: string;
  description: string;
  ogImage?: string | ImageMetadata; // 画像パス(string) または importした画像メタデータ
  ogType?: 'website' | 'article'; // ページタイプ (デフォルトは website)
  // 必要に応じて他のPropsを追加 (例: publishedTime など)
}

// Propsを受け取り、デフォルト値を設定
const {
  title = 'サイトのデフォルトタイトル',
  description = 'サイトのデフォルト説明文',
  ogImage, // 画像データ or パス
  ogType = 'website' // デフォルトは 'website'
} = Astro.props;

// サイトのベースURLを取得 (astro.config.mjs の site が必要)
const siteUrl = Astro.site;
if (!siteUrl) {
  console.warn("astro.config.mjs の 'site' オプションが設定されていません。絶対URLが生成できません。");
}

// 現在ページの絶対URLを生成
const pageUrl = siteUrl ? new URL(Astro.url.pathname, siteUrl).toString() : Astro.url.pathname;

// OGP画像の絶対URLを生成
let absoluteOgImageUrl: string | undefined = undefined;
if (ogImage) {
  if (typeof ogImage === 'string') {
    // 文字列パスの場合 (例: '/images/default-ogp.png' や Markdownからの相対パス)
    // siteUrl があれば絶対URLに、なければそのまま使う (相対パスは機能しない可能性あり)
    absoluteOgImageUrl = siteUrl ? new URL(ogImage, siteUrl).toString() : ogImage;
  } else if (typeof ogImage === 'object' && 'src' in ogImage) {
    // import した画像メタデータの場合 (ImageMetadata)
    // .src にはビルド後のパスが入るので、絶対URLにする
    absoluteOgImageUrl = siteUrl ? new URL(ogImage.src, siteUrl).toString() : ogImage.src;
  }
} else {
  // OGP画像が指定されなかった場合のデフォルト画像 (任意)
  // 例: public/default-ogp.png を用意しておく
   if (siteUrl) {
     absoluteOgImageUrl = new URL('/default-ogp.png', siteUrl).toString();
   }
}

// サイト固有の情報
const siteName = "あなたのサイト名";
const twitterHandle = "@your_twitter_handle"; // あなたのTwitterハンドル (任意)
---
```
`<head>` タグの中に、OGPタグとTwitterカードタグを追加し、`content` 属性にPropsや生成した変数 `{}` を使って動的に値を設定します。

```html
---
// --- 上記のフロントマター ---
---
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <link rel="icon" href="/favicon.ico" />
  <title>{title}</title>

  {/* 基本的なメタタグ */}
  <meta name="description" content={description} />

  {/* OGPタグ (Facebook, etc.) */}
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />
  <meta property="og:url" content={pageUrl} />
  <meta property="og:site_name" content={siteName} />
  {absoluteOgImageUrl && <meta property="og:image" content={absoluteOgImageUrl} />}
  <meta property="og:type" content={ogType} />
  {/* 必要ならロケールなども追加 <meta property="og:locale" content="ja_JP" /> */}

  {/* Twitterカードタグ */}
  <meta name="twitter:card" content="summary_large_image" /> {/* 大きな画像付きカード */}
  {twitterHandle && <meta name="twitter:site" content={twitterHandle} />}
  <meta name="twitter:title" content={title} />
  <meta name="twitter:description" content={description} />
  {absoluteOgImageUrl && <meta name="twitter:image" content={absoluteOgImageUrl} />}

  {/* ページ固有のhead要素を追加するためのスロット (任意) */}
  {/* <slot name="head" /> */}
</head>
<body>
  <slot />
</body>
</html>
```

### 2.ページファイルから Props を渡す

**`.astro` ページの場合 (例: `src/pages/index.astro`)**:
レイアウトコンポーネントを呼び出す際に、必要な情報をPropsとして渡します。OGP画像は `import` して `.src` を渡すのが推奨です。
```astro
---
// src/pages/index.astro
import BaseLayout from '../layouts/BaseLayout.astro';
import heroImage from '../assets/images/home-hero.jpg'; // OGP用画像をインポート
---
<BaseLayout
  title="ようこそ！My Astro Blogへ"
  description="Astroとコンテンツコレクションで作るブログサイトのトップページです。"
  ogImage={heroImage} {/* インポートした画像オブジェクトを渡す */}
  ogType="website"
>
  {/* ... ページコンテンツ ... */}
</BaseLayout>
```

* **Markdown/MDX ページの場合 (例: `src/content/blog/my-first-post.md`)**:
ファイルの先頭にある **フロントマター** に必要な情報を記述します。これらは自動的にレイアウトコンポーネントのPropsとして渡されます。画像パスは **Markdownファイルからの相対パス** で記述します。
```markdown
---
title: '私の最初の投稿'
pubDate: 2025-04-14
description: 'Astroブログの最初の記事です。OGP設定も行いました。'
tags: ["Astro", "Blog"]
ogImage: '../../assets/images/post1-ogp.png' # Markdownからの相対パス
ogType: 'article' # ブログ記事なので article を指定
layout: '../../layouts/BlogPostLayout.astro' # 記事用レイアウトを指定 (例)
---

記事本文...
```

*(注意: 上記の場合、`BlogPostLayout.astro` が `BaseLayout.astro` を内部で使っていて、Propsを適切に引き継ぐように作られている必要があります。または、`BlogPostLayout.astro` 側で直接メタタグを設定しても構いません)*

### 3.`astro.config.mjs` を確認・設定

プロジェクトルートの `astro.config.mjs` を開き、`site` オプションが正しく設定されているか確認します。これが設定されていないと、絶対URLが正しく生成されません。
```javascript
import { defineConfig } from 'astro/config';

export default defineConfig({
  site: 'https://あなたのサイトのドメイン.com', // ← これを設定
  // ... 他の設定 ...
});
```

### 開発サーバーでの確認と注意点

* 開発サーバーを起動 (`npm run dev` など) し、各ページにアクセスします。
* ブラウザの開発者ツールを開き、「Elements」タブ（または「ソースを表示」）で `<head>` 内の `<meta>` タグを確認します。`og:title`, `og:description`, `og:image`, `twitter:image` などの `content` 属性が、そのページの内容に合わせて正しく設定されているか確認してください。
* `og:url`, `og:image`, `twitter:image` の値が **絶対URL** (`https://...`) になっているか確認します (`site` 設定が反映されているか)。

実際にシェアしたときの表示を確認するには、Facebook シェアデバッガー や Twitter Card Validator などの公式ツール、もしくは[Hey Meta](https://www.heymeta.com/)のようなツールを使います。

## まとめ
これで、SNSでシェアされたときに、各ページの内容に合った魅力的なプレビューが表示されるようになりました。