ページのタイトルや説明文(<title> や <meta name="description">)は、検索結果の見え方や SNS でのシェア表示に大きく影響します。Next.js の App Router では、こうしたメタ情報を Metadata API で宣言的に設定できます。<head> を自分で書く必要はなく、ファイルから metadata をエクスポートするだけです。この記事では、静的な metadata の書き方、URL に応じて変える generateMetadata、共通設定の継承、OGP(SNS 用の画像・情報)の設定までを初心者向けに解説します。前提として App Router を使っていることとします。
目次
metadata オブジェクトで基本情報を設定する
もっとも簡単なのは、layout.tsx や page.tsx から metadata という名前のオブジェクトをエクスポートする方法です。Next.js がこれを読み取り、自動で <head> 内に対応するタグを出力します。
import type { Metadata } from 'next';
// このページのメタ情報を宣言する
export const metadata: Metadata = {
title: '運営者について',
description: '当サイトの運営者と目的を紹介するページです。',
};
export default function AboutPage() {
return <h1>運営者について</h1>;
}
これだけで、このページには <title>運営者について</title> と <meta name="description" content="..."> が出力されます。Metadata 型を付けておくと、指定できるプロパティを補完でき、書き間違いも防げます。値が固定のページは、この静的な metadata で十分です。
URL に応じてメタ情報を変える(generateMetadata)
ブログ記事のように、ページごとにタイトルが変わる場合は、固定の metadata では対応できません。そこで使うのが generateMetadata 関数です。動的ルーティングの params を受け取れるので、記事のデータをもとにタイトルや説明文を組み立てられます。
import type { Metadata } from 'next';
// params からデータを取り、メタ情報を動的に生成する
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const res = await fetch(`https://example.com/api/posts/${slug}`);
const post = await res.json();
return {
title: post.title,
description: post.summary,
};
}
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>記事: {slug}</h1>;
}
generateMetadata は async にできるので、記事データを取得してから title や description を返せます。これで /blog/hello と /blog/world で別々のタイトルが付き、検索結果にも記事ごとの正しいタイトルが表示されます。静的な metadata と generateMetadata は同じファイルで併用できません。値が固定なら前者、URL によって変わるなら後者、と使い分けます。
共通設定とタイトルのテンプレート
メタ情報は、上位の layout.tsx で設定したものが下位のページに引き継がれます。サイト全体で共通の設定はルートの app/layout.tsx に置くとよいでしょう。特に便利なのが title.template で、各ページのタイトルに自動でサイト名を付け足せます。
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: {
default: 'My Blog', // タイトル未指定のページで使われる
template: '%s | My Blog', // %s に各ページのタイトルが入る
},
description: 'Web 開発の学習ブログです。',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body>{children}</body>
</html>
);
}
template: '%s | My Blog' を設定しておくと、あるページで title: '運営者について' と書くだけで、実際のタイトルは 運営者について | My Blog になります。%s の部分に各ページのタイトルが差し込まれる仕組みです。トップページなど個別のタイトルを持たないページには default の値が使われます。サイト名を毎回手で付ける手間が省け、表記も統一できます。
OGP(SNS シェア用)の情報を設定する
SNS でリンクを共有したときに表示されるタイトル・説明・画像は、OGP(Open Graph Protocol)というメタ情報で決まります。Metadata API では openGraph プロパティで指定できます。
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'My Blog',
description: 'Web 開発の学習ブログです。',
openGraph: {
title: 'My Blog',
description: 'Web 開発の学習ブログです。',
images: ['/ogp.png'], // SNS で表示される画像
type: 'website',
},
};
openGraph を設定すると、<meta property="og:title"> などのタグが自動で出力されます。images にサムネイル画像を指定しておくと、シェア時に画像付きのカードで表示され、クリック率の向上が期待できます。記事ごとに画像を変えたいときは、generateMetadata の中で openGraph.images を組み立てればOKです。
タイトルが反映されないと感じたとき
Metadata API でつまずきやすいのが、クライアントコンポーネントに metadata を書いてしまうケースです。metadata や generateMetadata は、ファイル先頭に 'use client' が付いたコンポーネントでは機能しません。メタ情報の設定はサーバーコンポーネント('use client' の付いていないファイル)で行う必要があります。
もし対象のページがクライアントコンポーネントになっている場合は、メタ情報だけを親の layout.tsx(サーバーコンポーネント)に切り出すか、ページ本体をサーバーコンポーネントにして、インタラクティブな部分だけを子のクライアントコンポーネントに分ける構成にします。「メタ情報はサーバー側で設定する」と覚えておくと、この種のトラブルを避けられます。
まとめ
Next.js App Router の Metadata API を使うと、<head> を直接書かずにタイトルや説明文を設定できます。値が固定のページは metadata オブジェクトをエクスポートし、URL によって変わるページは generateMetadata 関数で params をもとに動的に生成します(両者は同一ファイルで併用不可)。ルートの layout.tsx に共通設定を置き、title.template(%s | サイト名)を使えばサイト名を自動付与できます。SNS 向けには openGraph でタイトル・説明・画像を設定しましょう。なお metadata はサーバーコンポーネントでのみ有効なので、'use client' のファイルには書かない点に注意してください。まずは固定ページに metadata を1つ追加して、ブラウザのタブや検索結果での見え方を確認してみましょう。