カテゴリー: FrontEnd

Next.jsのMetadataとOGP (with AppRouter)

はじめに

今回はNext.jsでApp Routerを指定した場合のMetadataの設定方法を紹介します。合わせて、OGP画像の生成方法も紹介します。

Metadataとは?

Metadataとは、Headタグの内容を定義するもので、pages router時代には<Head>を使って定義していたものです。
pages.tsxまたはlayout.tsxに以下のように定義します。

const siteName = "サンプルWebSite";
const description = "OGPのサンプルWebSiteです。";
const url = "https://hoge.netlify.app/";

export const metadata: Metadata = {
  title: {
    default: siteName,
    template: `%s | ${siteName}`,
  },
  description,
  openGraph: {
    title: siteName,
    description,
    url,
    siteName,
    locale: "ja_JP",
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: siteName,
    description,
    site: "@hogehgoe",
    creator: "@hogehoge",
  },
  metadataBase: new URL(process.env.URL ?? "http://localhost:3000"),
};

 

基本的には、上位階層で指定したMetadataが下位階層にも適用されていきます。下位階層でこれらの値を変更したい場合は、同じようにmetadataを作成し、変更したい値のみ定義することで、上書きすることができます。
また、テンプレート機能を使用することで、下位階層から受けた値を埋め込むようなこともできます。

例えば、 子ページのタイトル | 全体のWebサイト名といったタイトルを指定したい場合、上で設定しているように  template: `%s | ${siteName}`,とすることで、実現できます。

動的に設定するには

Metadataを動的にするには、 generateMetadata()というfunctionをexportすれば、実現できます。

const apiUrl = "https://jsonplaceholder.typicode.com/posts";

type Post = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

const getPost = async (id: string) => {
  const res = await fetch(new URL(`${apiUrl}/${id}/`), { cache: "no-cache" });  // 
  return (await res.json()) as Post;
};

export async function generateMetadata({ params }: { params: { id: string } }) {
  const res = await getPost(params.id);
  return { title: res.title };
}

generateMetadataでのパラメータの取り方は、Pageと同じです。また、この中でもAPIをフェッチすることが出来るので、取得したデータをもとに、タイトルを生成することができます。

OGP画像

App Routerを使用した場合、page.tsxと同じ階層に opengraph-image.(jpg|png|svg|ts|tsx)を置いておくと、page.tsxがレンダリングされる際に、自動的にmetaタグ og:imageが挿入されます。
(
jpg, png, svgの場合は、配置した画像がそのままOGP画像として使用されます。

今回は、「tsx」で動的に生成させるため、 opengraph-image.tsxを作成します。

import { ImageResponse } from "next/server";
import { getPost } from "@/lib/posts";

// Edge Functionとして動作する
export const runtime = "edge";
export const revalidate = 0; // 数値の場合は秒数となる

export const size = {
  width: 1200,
  height: 630,
};
export const contentType = "image/png";

export default async function og({ params }: { params: { id: string } }) {
  const res = await getPost(params.id);
  return new ImageResponse(
    (
      <div
        style={{
          display: "flex",
          backgroundColor: "white",
          width: "100%",
          height: "100%",
        }}
      >
        <div
          style={{ width: "100px", height: "100%", backgroundColor: "#03a9f4" }}
        />
        <div
          style={{
            margin: "40px",
            fontSize: "40px",
          }}
        >
          {res.title}
        </div>
        <div
          style={{
            position: "absolute",
            left: "140px",
            bottom: "40px",
            fontSize: "30px",
          }}
        >
          日本語テキストです。
        </div>
      </div>
    ),
    {
      ...size,
    },
  );
}

 

この状態でページを開いてみると、以下のようにmetaタグが挿入されています。

このURLを開いてみると、生成された画像を確認することができます。

カスタムフォント

カスタムフォントを使用するには、大きく分けて次の2つの方法があります。

  • next/fontのようなライブラリを使用してフォントファイルを内包する
  • OGP画像生成時に、毎回Google Fontsからダウンロードする

前者のほうが、OGP画像を生成するコストが低くなりますが、フォントファイルを内包する必要があるため、ビルド後のバンドルサイズが大きくなってしまいます。
これは、Edge Functionを使用する場合に問題があります。
例えば、Vercel場合、Edge FunctionのCode sizeはhobbyで1MB、Enterpriseでも4MBとなっています。(netlifyは20MB)
特にVercelのEdge Fcuntionを使用する場合には、フォントをそのまま埋め込むのは無理があるため、必要な文字のみに絞る「サブセット化」が必要になります。(サブセットフォントメーカーなどを使用するようです。)

それに対し、後者は、ランタイムでフォントファイルをダウンロードしに行くため、効率が良いとは言えませんが、デプロイできない問題は解消できます。
この実装方法はいくつかありますが、こちらの記事が一番参考になります。

netlifyへデプロイ

今回はnetlifyにデプロイしてみます。

netlifyへデプロイすると、自動的にEdge Functionに登録され、実際にOGP画像が生成されます。
https://joyful-paletas-efc0cf.netlify.app/posts/1/

OGP確認ツール上でも、Metaタグに設定された内容や、OGP画像が反映されていることが確認できます。

さいごに

様々なサービスでメタタグやOGP画像が読み込まれることから、パブリックサイトではほぼ必須の実装になると思いました。

おすすめ書籍

カイザー

シェア
執筆者:
カイザー

最近の投稿

Goの抽象構文木でコードを解析する

はじめに Goでアプリケーショ…

1日 前

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

4週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前