Next.jsで動的にメタ情報設定

# はじめに
ブログサイトを作る過程で学んだことを、備忘録目的で投稿しています。
自身は駆け出しエンジニアであり、React自体がほぼ初学者のため、誤った認識・理解をしている可能性があります。
万が一参考にする場合は、上記の点を考慮した上でご一読ください。
また、スタイリングについては割愛しています。

この記事は、過去に投稿した[Next.js × microCMSでブログサイト作成した際に学んだことを書き留める](https://qiita.com/miyaayim_eng/items/62f4267280a9f3cfaa4d)のプロジェクトを元に説明しています。

# 目的
各ページにメタ情報を追加する、記事詳細ページなど動的ページにも対応する。

# 作業環境
Windows10 Pro x64
Node.js: 20.14.0
npm: 10.3.0
Next.js: 14.2.3（App Router使用）

# 手順

## 静的なメタ情報を設定
`layout.jsx`で基本形を設定します。

静的なメタ情報には`export const metadata`を使用します。
まずは結論から。
```/src/app/layout.jsx
import "./globals.scss";

// metadataオブジェクトの基本形を設定
export const metadata = {
  // Vercel以外のホスティングサービスを利用する場合はmetadataBaseを設定する必要がある
  metadataBase: new URL("https://example-domain.com"),

  title: {
    default: "サイトタイトル",
    template: `%s | サイトタイトル`,
  },
  description: "一覧ページのディスクリプション",
  alternates: {
    canonical: "/",
  },
  openGraph: {
    title: {
      default: "サイトタイトル",
      template: `%s | サイトタイトル`,
    },
    description: "一覧ページのディスクリプション",
    url: `https://example-domain.com`,
    siteName: "サイトタイトル",
    locale: "ja_JP",
    type: "website",
    images: "/opengraph-image.png",
  },
  twitter: {
    card: "summary_large_image",
    images: "/twitter-image.png",
  },
};

export default function RootLayout({ children }) {
  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  );
}

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/a79b8a46-abff-14f1-6d2d-77b8dd7dc28c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/ed8b1fd5-5cdf-6ff1-12b9-edc3ca5c5603.png)



`template`を使用することで、トップページでは「サイトタイトル」のみ、仮想ページでは「ページタイトル | サイトタイトル」のようにすることが可能です。
```jsx
title: {
  default: "サイトタイトル",
  template: `%s | サイトタイトル`,
},
```

`opengraph-image.png`と`twitter-image.png`はappディレクトリ直下に配置しています。
`twitter`の設定を行わなかった場合は、`openGraph`と同じ画像設定になります。

`metadataBase`は、Vercel以外のホスティングサービスを利用する場合に必要とのことです。
設定しなかった場合、`localhost`になってしまうらしいです。（未検証）


## 動的なメタ情報を設定
記事詳細ページのメタ情報を設定します。
まずは結論から。
```/src/app/articles/[slug]/page.jsx
export async function generateMetadata({ params }) {
  const pageUrl = `articles/${params.slug}/`;

  // 記事詳細を取得
  const queries = { fields: "title" };
  const article = await getArticlesDetail(params.slug, queries);

  const metadata = {
    title: article.title,
    description: "詳細ページのディスクリプション",
    alternates: {
      canonical: `${pageUrl}`,
    },
    openGraph: {
      title: article.title,
      description: "詳細ページのディスクリプション",
      url: `https://example-domain.com/${pageUrl}`,
      siteName: "サイトタイトル",
      locale: "ja_JP",
      type: "website",
      images: "/opengraph-image.png",
    },
    twitter: {
      card: "summary_large_image",
      images: "/twitter-image.png",
    },
  };

  return metadata;
}

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/48788e8f-6c4a-a3f1-f7bd-fcd72e5d77f0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/c3babe7f-9a3d-7f29-e0bb-75f83e8fb44f.png)


異なる箇所を解説します。
まずはURLが異なるので、`params`から現在のコンテンツIDを取得し、そこから`pageUrl`を設定します。
次に、`title`にページタイトルを表示させたいので、記事タイトルをAPIから取得し、`title`で使用します。
後は、`description`に好きな内容を入力します。
残りの箇所は全て同じになります。


# さいごに
今回はわかりやすいように重複する内容も毎回記述していますが、実際には保守性を高めるために一括管理するファイルを準備し、そこから`import`して使用するのがよいと思います。

# 参考
https://zenn.dev/ryota_09/articles/06ec306f0ef9ee

https://zenn.dev/nino/books/30e21d37af73b5/viewer/meta

https://nextjs.org/docs/app/building-your-application/optimizing/metadata

https://nextjs.org/docs/app/api-reference/functions/generate-metadata

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image