フロントエンド 2026.01.27

Next.js App RouterでSEO対策!メタデータ設定とサイトマップ自動生成の実装術

約18分で読めます

Next.js App Routerを使ったWebサイトでのSEO対策について、メタデータ設定の実装方法とサイトマップの自動生成機能を実装例と共に詳しく解説します。

こんな悩みはありませんか?

「Next.jsでWebサイトを構築したけれど、検索エンジンでの順位が思うように上がらない...」

「メタデータやサイトマップの設定が複雑で、手動更新も面倒になってしまう...」

「App Routerに移行したいけれど、SEO対策がきちんとできるか不安...」

このような課題を抱える企業様からの相談が、最近特に増えています。実際に、神奈川県内の製造業A社様では、Next.jsで構築したコーポレートサイトの検索順位が期待値を大幅に下回り、問い合わせ数が月10件以下という状況が続いていました。

本記事では、Next.js App Routerを活用した効果的なSEO対策として、メタデータの適切な設定方法とサイトマップの自動生成について、実際のプロジェクトで使用している手法を交えながら解説します。

Next.js App RouterでSEOが難しい理由

従来のPages RouterからApp Routerへの移行により、メタデータの管理方法が大幅に変更されました。多くの開発者が戸惑う主な原因は以下の通りです。

1. ファイルベースメタデータシステムの複雑さ

App Routerでは、各ページディレクトリにlayout.tsxpage.tsxでメタデータを定義する仕組みになっています。しかし、動的なコンテンツに対応したメタデータ設定や、親子関係でのメタデータ継承が直感的ではありません。

2. サイトマップ生成の自動化困難

静的ページと動的ページが混在する現代のWebサイトでは、手動でサイトマップを管理するのは現実的ではありません。特に、CMSと連携している場合や、定期的にコンテンツが更新される場合は自動生成が必須となります。

3. パフォーマンスとSEOのバランス

Next.jsの強みであるパフォーマンス最適化機能を活用しながら、検索エンジンクローラーに適切な情報を提供する設定のバランスが重要です。

これらの課題を解決するため、弊社では段階的なアプローチでSEO対策を実装しています。

実践的なメタデータ設定の実装手順

ステップ1: 基本的なメタデータ構造の構築

まず、プロジェクトのルートレベルでベースとなるメタデータを設定します。

// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '%s | 株式会社○○',
    default: '株式会社○○ | 神奈川県のWeb制作会社'
  },
  description: '神奈川県を拠点とするWeb制作会社です。Laravel、WordPress、Next.jsを活用した高品質なWebサイトを提供いたします。',
  keywords: ['Web制作', '神奈川', 'Laravel', 'Next.js', 'WordPress'],
  authors: [{ name: '株式会社○○' }],
  creator: '株式会社○○',
  publisher: '株式会社○○',
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },
  openGraph: {
    type: 'website',
    locale: 'ja_JP',
    siteName: '株式会社○○',
    images: [
      {
        url: '/og-default.jpg',
        width: 1200,
        height: 630,
        alt: '株式会社○○のOG画像',
      },
    ],
  },
  twitter: {
    card: 'summary_large_image',
    creator: '@company_twitter',
  },
}

ステップ2: 動的メタデータ生成の実装

個別ページで動的にメタデータを生成する仕組みを構築します。実際の案件では、CMSからのデータを活用することが多いです。

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'

type Props = {
  params: { slug: string }
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  // CMSやAPIからブログ記事データを取得
  const post = await getPostBySlug(params.slug)
  
  if (!post) {
    return {
      title: 'ページが見つかりません',
    }
  }

  const publishedTime = new Date(post.publishedAt).toISOString()
  const modifiedTime = new Date(post.updatedAt).toISOString()

  return {
    title: post.title,
    description: post.excerpt || post.content.substring(0, 160) + '...',
    keywords: post.tags?.join(', '),
    authors: [{ name: post.author.name }],
    publishedTime,
    modifiedTime,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      publishedTime,
      modifiedTime,
      authors: [post.author.name],
      images: [
        {
          url: post.ogImage || '/og-default.jpg',
          width: 1200,
          height: 630,
          alt: post.title,
        },
      ],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.ogImage || '/og-default.jpg'],
    },
  }
}

ステップ3: 構造化データの実装

検索エンジンにより詳細な情報を提供するため、JSON-LDを使用した構造化データを実装します。

// components/StructuredData.tsx
interface ArticleStructuredDataProps {
  title: string
  description: string
  author: string
  publishedTime: string
  modifiedTime: string
  imageUrl: string
  url: string
}

export function ArticleStructuredData({
  title,
  description,
  author,
  publishedTime,
  modifiedTime,
  imageUrl,
  url,
}: ArticleStructuredDataProps) {
  const structuredData = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: title,
    description: description,
    image: imageUrl,
    author: {
      '@type': 'Person',
      name: author,
    },
    publisher: {
      '@type': 'Organization',
      name: '株式会社○○',
      logo: {
        '@type': 'ImageObject',
        url: '/logo.png',
      },
    },
    datePublished: publishedTime,
    dateModified: modifiedTime,
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': url,
    },
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
    />
  )
}

サイトマップ自動生成の実装

Next.js App Routerでは、sitemap.tsファイルを使用してサイトマップを動的に生成できます。

// app/sitemap.ts
import type { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://example.com'
  
  // 静的ページ
  const staticPages: MetadataRoute.Sitemap = [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 1.0,
    },
    {
      url: `${baseUrl}/about`,
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.8,
    },
    {
      url: `${baseUrl}/services`,
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.8,
    },
  ]

  // 動的ページ(ブログ記事など)
  const posts = await getAllPosts()
  const postPages: MetadataRoute.Sitemap = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: 'weekly',
    priority: 0.6,
  }))

  // サービスページなど
  const services = await getAllServices()
  const servicePages: MetadataRoute.Sitemap = services.map((service) => ({
    url: `${baseUrl}/services/${service.slug}`,
    lastModified: new Date(service.updatedAt),
    changeFrequency: 'monthly',
    priority: 0.7,
  }))

  return [...staticPages, ...postPages, ...servicePages]
}

よくある失敗パターンと対処法

失敗パターン1: メタデータの重複や不整合

症状: 親コンポーネントと子コンポーネントでメタデータが競合し、予期しない内容が表示される。

原因: App Routerのメタデータ継承ルールを理解せずに実装している。

対処法:

// ❌ 悪い例 - レイアウトで完全なメタデータを定義
export const metadata: Metadata = {
  title: '固定タイトル',
  description: '固定説明文'
}

// ✅ 良い例 - テンプレートを活用
export const metadata: Metadata = {
  title: {
    template: '%s | サイト名',
    default: 'デフォルトタイトル | サイト名'
  }
}

失敗パターン2: サイトマップの更新忘れ

症状: 新しいページを追加しても検索エンジンにインデックスされない。

原因: 手動でサイトマップを管理しており、更新が漏れている。

対処法: CIパイプラインにサイトマップ検証を組み込む。

# package.jsonに追加
{
  "scripts": {
    "sitemap:validate": "node scripts/validate-sitemap.js",
    "build": "next build && npm run sitemap:validate"
  }
}

失敗パターン3: OG画像の設定漏れ

症状: SNSでシェアされた際に適切な画像が表示されない。

対処法: 動的OG画像生成を実装する。

// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/server'

export const runtime = 'edge'

export async function generateImageMetadata({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug)
  
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 48,
          background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          color: 'white',
          padding: '40px',
          textAlign: 'center',
        }}
      >
        {post.title}
      </div>
    ),
    {
      width: 1200,
      height: 630,
    }
  )
}

実装後の成果と継続的な改善

前述のA社様では、これらの対策実施後、以下のような具体的な成果が得られました。

  • 検索順位: 主要キーワードで圏外→3ページ目→1ページ目へ向上
  • オーガニック流入: 月間500PV→2,800PVと約5倍に増加
  • 問い合わせ数: 月8件→41件と5倍以上に改善
  • サイトマップ管理工数: 月2時間→0時間(完全自動化)

ただし、SEO対策は一度の実装で完了するものではありません。継続的な監視と改善が重要です。

定期的なチェックポイント

Google Search Consoleでクリックスルーレートをチェックし、タイトルやdescriptionの改善点を洗い出す
新しいコンテンツが適切にサイトマップに含まれているか確認
Google Search Consoleの「拡張」セクションでエラーがないか確認
Core Web Vitalsの指標をチェックし、SEOに影響する要因を早期発見

無料相談受付中

お気軽にご相談ください(初回無料)

詳しく見る

まとめと次のステップ

Next.js App Routerを使用したSEO対策では、メタデータの適切な設定とサイトマップの自動生成が成功の鍵となります。特に重要なのは、動的コンテンツに対応した柔軟な仕組みづくりと、継続的な監視・改善体制の構築です。

今回ご紹介した手法により、多くの企業様で検索順位の向上と問い合わせ数の増加を実現しています。ただし、実装には技術的な専門知識と継続的なメンテナンスが必要です。

まずは以下のステップから始めることをおすすめします:

神奈川でNext.jsを活用したSEO対策をお考えの企業様や、現在のWebサイトの検索順位にお悩みの方は、ぜひ一度ご相談ください。弊社では、20年以上の実績に基づいた効果的なSEO戦略をご提案いたします。技術的な実装から運用まで、トータルでサポートいたします。

この記事をシェア

この記事の内容でお困りですか?

無料でご相談いただけます

Webサイトの改善、システム開発、AI導入など、 お気軽にご相談ください。初回相談は無料です。

無料相談してみる
AIに無料相談