Next.js の generateMetadata で記事ごとのSEOメタデータを設定

Next.jsSEO開発記録投稿日: 2026-04-05

これまで全ページのサイトタイトルとディスクリプションが同じ内容になっていたため、記事ページだけ動的に生成するよう対応しました。 学習ノート公開時の記事や、GitHubのReadme内で「今後の実装予定」としていた機能です。

本来はシステム公開前に実装すべき必須機能だと思いますが、開発中は学習の妨げになりそうなノイズを極限まで減らしたかったことと、段々と仕上がっていくのが嬉しくて、つい気持ちが先走ってしまった感じです。(GA4のトラッキングコードもそう)

app/posts/[slug]/page.tsx

import type { Metadata } from 'next'

type Props = {
  params: Promise<{ slug: string }>
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const post = await prisma.post.findFirst({
    where: { slug, status: "published" },
  })
  if (post === null) {
    return {}
  }
  return {
    title: `${post.title} | 人間学習ノート`,
    description: `${post.content.slice(0, 120)}`
  }
}

layout.tsxmetadata はサイト全体のデフォルト値として機能し、generateMetadata を定義したページだけ上書きされる仕組み。 Next.js App Router では generateMetadataをエクスポートすることでメタデータを動的に生成してくれるようです。

ディスクリプションに関しては新しくデータベースを用意するのも、毎回内容を考えるのも面倒だったので、運用効率を重視した結果、記事本文の120文字を.sliceで取得することにしました。

記事が見つからない場合は空オブジェクト {} を返して、layout.tsx のデフォルト値にフォールバックさせています。

こんな感じ。

実装後のメタ

マークダウンの記述がそのまま見えていますね…。 対処法はありそうですが色々と考えた末、記事の書き始めにマークダウン記号を使わないという、書き方の工夫で対応することにしました。