🛰️

APIで取得したデータをもとに、動的にメタ情報を設定する方法(Next.js App Router)

2023/12/24に公開

📌 はじめに

こんにちは!@Ryo54388667です!☺️

普段は都内でフロントエンドエンジニアとして業務をしてます!
主にTypeScriptやNext.jsといった技術を触っています。

Next.jsのApp Router構成でAPI経由のデータを利用して動的にメタ情報(titleやdescriptionなど)を設定する方法について紹介していきます。

📌 行いたいこと

/articles/xxxxx この末尾の情報を元にメタ情報を設定するケースがあります。Webメディアでは要件として組み込まれることが多いのではないでしょうか。従来のPages RouterではNext.jsのライブラリの一つであるHeadコンポーネントを利用して設定していました。一方、新しいインターフェースであるApp Router ではHeadコンポーネントは利用できなくなりました。

「じゃあ、どうやって動的なデータを設定するんだろう?」
ということでApp Routerの場合に、API経由で取得したデータでメタ情報を設定する方法を試してみました👌

📌 具体的な方法

今回は以下のパッケージとバージョンを使用します。

パッケージ バージョン
next 13.5.6

title, descriptionについて

メタ情報の設定について、ドキュメントを確認すると以下の方法が書かれています。
https://nextjs.org/docs/app/api-reference/functions/generate-metadata

import { Metadata } from 'next'
 
// either Static metadata
export const metadata: Metadata = {
  title: '...',
}
 
// or Dynamic metadata
export async function generateMetadata({ params }) {
  return {
    title: '...',
  }
}

layout.tsxもしくはpage.tsxでmetadata,generateMetadataのような予約語を利用するようですね!静的なメタ情報はmetadata、動的なものはgenerateMetadataで設定する、 といった使い分けです。注意すべきは、この設定ができるのはServer Components のみ、という点 ですね。

今回はAPI経由で動的にメタ情報を設定する方法を検証したいので、generateMetadataを利用します。
具体的に、どのように設定ができるかというと、generateMetadataメソッドの引数にアクセス時のURLの末尾(dynamic routing のslug、例えば[id].tsx ならid)が渡されます。
今回は、下記のコードのように、jsonplaceholderで取得した投稿データを元に、メタ情報が動的に変更されているか確認します。

export const generateMetadata = async ({ params }: { params: { id: string } }): Promise<Metadata> => {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
  const data = (await res.json()) as Post;

  return {
    title: `${params.id}: ${data.title}`,
    description: `ディスクリプション:${params.id} ${data.body}`,
  };
}

API経由で動的にメタ情報を設定できてそうです!👌

アイコンについて

アイコンについては2つの設定方法があります。
1つ目は先ほど紹介したlayout.tsxかpage.tsxで設定する方法です。
ファビコンを動的に変更するケースはそれほど多くはないと思いますが、下記の方法で対応できます。

//  layout.tsx OR page.tsx
export const generateMetadata = async ({
  params,
}: {
  params: { id: string };
}): Promise<Metadata> => {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${params.id}`
  );
  const data = (await res.json()) as Post;

  const imageData = await fetch(`https://xxxxxxx.xxxx/${params.id}`).then(
    async (res) => await res.json()
  );

  return {
    title: `${data.id}: ${data.title}`,
    description: `ディスクリプション:${data.id} ${data.body}`,
    icons: { // <=== ここで設定します!💡💡💡
      icon: [
        {
          url: imageData.path,
	  href: imageData.path,
        },
      ],
    },
  };
};

余談ですが、ダークモードのファビコンの対応もこちらの方法でできます!

//  layout.tsx OR page.tsx
export const generateMetadata = async ({
  params,
}: {
  params: { id: string };
}): Promise<Metadata> => {

// (…略)

  return {
    title: `${data.id}: ${data.title}`,
    description: `ディスクリプション:${data.id} ${data.body}`,
    icons: { 
      icon: [
        {
        media: '(prefers-color-scheme: light)', // <=== ここで設定します!💡💡💡
        url: imageData.light,
	href: imageData.path,
      },
      {
        media: '(prefers-color-scheme: dark)', // <=== ここで設定します!💡💡💡
        url: imageData.dark,
	href: imageData.path,
      },
      ],
    },
  };
};

こちらを参考にしました。
https://stackoverflow.com/questions/76977678/how-to-dynamically-change-favicon-for-dark-and-light-mode-in-next-js-13-or-14

2つ目はicon.tsxというファイルで設定する方法です。
favicon.tsx, icon.tsx, apple-icon.tsx で設定できるようです。詳しくは下記をみてください。
https://nextjs.org/docs/app/api-reference/file-conventions/metadata/app-icons#image-files-ico-jpg-png

下記のように設定します。

// icon.tsx... page.tsxと同階層
import { ImageResponse } from "next/server";

// Image metadata
export const size = {
  width: 32,
  height: 32,
};
export const contentType = "image/png";

// Image generation
export default async function Icon({ params }: { params: { id: string } }) {
  const iconData = await fetch(`https://xxxxxxx.xxxx/${params.id}`).then(
    async (res) => await res.json()
  );

  return new ImageResponse(
    (
      // ImageResponse JSX element
      <div
        style={{
          fontSize: 24,
          background: "black",
          width: "100%",
          height: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          color: "white",
        }}
      >
        <img src={iconData.path} /> 
      </div>
    ),
    // ImageResponse options
    {
      
      // For convenience, we can re-use the exported icons size metadata
      // config to also set the ImageResponse's width and height.
      ...size,
    }
  );
}

OG画像について

OG画像についてもアイコン同様2つの設定方法があります。
1つ目は先ほど紹介したlayout.tsxかpage.tsxで設定する方法です。
OG画像については、動的に変更したいケースは多いのではないでしょうか。

下記は設定例です。

export const generateMetadata = async ({ params }: { params: { id: string } }): Promise<Metadata> => {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${params.id}`
  );
  const data = (await res.json()) as Post;

  const imageData = await fetch(`https://xxxxxxx.xxxx/${params.id}`).then(
    async (res) => await res.json()
  );

  return {
    title: `${data.id}: ${data.title}`,
    description: `ディスクリプション:${data.id} ${data.body}`,
    openGraph: { // <=== ここで設定します!💡💡💡
      title: `${data.id}: ${data.title}`,
      description: `ディスクリプション:${data.id} ${data.body}`,
      images: [ 
        {
          url: imageData.path,
          width: 1200,
          height: 630,
          alt: imageData.alt
        },
      ],
    },
  };
}

2つ目はopengraph-image.tsxというファイルで設定する方法です。

詳しくはこちらをみてください。
https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image

下記のように設定します。

import { ImageResponse } from "next/server";

// Image metadata
export const alt = “image alt” ;
export const size = {
  width: 1200,
  height: 630,
};

export const contentType = "image/png";

// Image generation
export default async function Image({ params }: { params: { id: string } }) {
   const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${params.id}`
  );
  const data = (await res.json()) as Post;

  const imageData = await fetch(`https://xxxxxxx.xxxx/${params.id}`).then(
    async (res) => await res.json()
  );

  return new ImageResponse(
    (
      // ImageResponse JSX element
      <div
        style={{
          width: "100%",
          height: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        <img src={imageData.path} />
      </div>
    )
  , // ImageResponse options
    {
      // For convenience, we can re-use the exported opengraph-image
      // size config to also set the ImageResponse's width and height.
      ...size
    });
}

📌 まとめ

  • layout.tsx か page.tsx内でgenerateMetadataを利用して設定する。
  • icon.tsx, opengraph-image.tsx ファイルで設定する。

ページ固有の情報が引数に渡されるので、それを利用してAPI経由で動的なメタ情報を設定することができます。設計の参考になれば幸いです😊

最後まで読んでいただきありがとうございます!
気ままにつぶやいているので、気軽にフォローをお願いします!🥺

https://twitter.com/Ryo54388667/status/1733434994016862256

Discussion