0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js App Routerでのnext-intlを用いた多言語Sitemap実装

Posted at

Next.js App Routerでのnext-intlを用いた多言語Sitemap実装

はじめに

Next.js App Routerを使用した多言語サイトの開発において、最初に以前使用したnext-sitemapではうまくいかず、最終的にApp Router標準のSitemap生成にした経緯について記す。

開発環境

- Next.js 14.x(App Router)
- TypeScript 5.x
- next-intl 3.x
- next-sitemap 4.x

要件定義

  • /en, /jaなどが多言語に対応したSitemap生成
  • 言語別の適切なalternate links設定
  • 検索エンジン最適化(SEO)に準拠したXML形式
  • ページの優先度と更新頻度の適切な設定

next-sitemapでの実装と課題

初期実装

// next-sitemap.config.js
module.exports = {
  siteUrl: 'https://example.com',
  generateRobotsTxt: true,
  exclude: ['/_not-found', '/404', '/500'],
  generateIndexSitemap: false,
  
  transform: async (config, path) => {
    return {
      loc: path,
      changefreq: 'daily',
      priority: 0.7,
      lastmod: new Date().toISOString(),
    }
  }
}

発生した技術的課題

1.言語別alternate linksの生成エラー

/ja/aboutのあとに/en/aboutが続いている

<!-- 誤った生成例 -->
<url>
  <loc>https://example.com/en/about</loc>
  <xhtml:link rel="alternate" hreflang="ja" 
    href="https://example.com/ja/about/en/about"/>
</url>

2.サイトマップインデックスの構造問題

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<!-- 空のインデックスが生成される -->
</sitemapindex>

解決までの試行錯誤

npm run build時の出力から、静的生成(SSG)と動的生成(Dynamic)のページを確認できる:

bashCopyRoute (app)
┌ ○ /_not-found 
├ ● /[locale]   
├   ├ /en
├   └ /ja
├ ● /[locale]/about   
├   ├ /en/about
├   └ /ja/about
├ ƒ /[locale]/blog/[id]
├ ● /[locale]/contact
├   ├ /en/contact
├   └ /ja/contact
└ ● /[locale]/products
    ├ /en/products
    └ /ja/products

○  (Static)   prerendered as static content
●  (SSG)      prerendered as static HTML (uses getStaticProps)
ƒ  (Dynamic)  server-rendered on demand

これらの情報を踏まえて試行錯誤を行った。

1. transformアプローチでの試み

// next-sitemap.config.js
module.exports = {
  transform: async (config, path) => {
    const parts = path.split('/').filter(Boolean);
    const locale = parts[0];
    
    if (!['en', 'ja'].includes(locale)) {
      return null;
    }
    
    return {
      loc: `${config.siteUrl}${path}`,
      changefreq: 'weekly',
      priority: 0.7,
      alternateRefs: [{
        href: `${config.siteUrl}/${locale === 'en' ? 'ja' : 'en'}${path}`,
        hreflang: locale === 'en' ? 'ja' : 'en'
      }]
    }
  }
}

このアプローチでは、alternateRefsのURLに余分なパスが追加される問題(/ja/about/en/about)を解決できなかった。

2. additionalPathsアプローチ

module.exports = {
  additionalPaths: async (config) => {
    const urls = [];
    const locales = ['en', 'ja'];
    const routes = [
      { path: '', priority: 1.0 },
      { path: '/about', priority: 0.8 },
      { path: '/contact', priority: 0.8 },
      { path: '/products', priority: 0.8 }
    ];
    
    locales.forEach(locale => {
      routes.forEach(({ path, priority }) => {
        urls.push({
          loc: `/${locale}${path}`,
          changefreq: 'weekly',
          priority,
          alternateRefs: [/* ... */]
        });
      });
    });
    return urls;
  }
}

このアプローチでは、手動でのURL管理が必要となり、メンテナンス性の低下を招いた。

これらの試行錯誤を経て、next-sitemapでの解決がうまく行かず、App Router標準機能への移行を決定した。

App Router標準機能による解決策

next-intl/examples/example-app-router/src/app
/sitemap.ts
を参考にした。

// app/sitemap.ts
import { MetadataRoute } from 'next'
mport { routing } from "@/i18n/routing";

const host = process.env.NEXT_PUBLIC_HOST || "http://localhost:3000";

export default function sitemap(): MetadataRoute.Sitemap {
    const baseUrl = 'https://example.com'
  
    const routes = [
        { path: '', priority: 1.0 },
        { path: '/about', priority: 0.8 },
        { path: '/contact', priority: 0.8 },
        { path: '/products', priority: 0.8 }
    ]

    return routes.flatMap((route) =>
        routing.locales.map((locale) => ({
            url: `${host}/${locale}${route === "/" ? "" : route}`,
            alternates: {
                languages: Object.fromEntries(
                    routing.locales.map((altLocale) => [altLocale, `${host}/${altLocale}${route === "/" ? "" : route}`])
                ),
            },
        }))
    );
}

上記をsrc/app/sitemap.tsに配置することで、Next.jsが自動的にsitemap.xmlを生成する。
たとえば、開発環境のlocalhost:3000/sitemap.xmlにアクセスすれば自動生成されたSitemapを見ることが可能である。

実装手法の比較分析

next-sitemap

長所:

  • robots.txtの自動生成機能
  • 豊富な設定オプション
  • サイトマップインデックス生成

短所:

  • 多言語対応時の設定複雑化
  • App Routerとの互換性問題
  • デバッグ困難性

App Router標準機能

長所:

  • TypeScript完全対応
  • App Router統合の容易さ
  • シンプルな実装構造
  • デバッグの容易性

短所:

  • robots.txt個別実装の必要性
  • 限定的な設定オプション
  • サイトマップインデックス機能欠如

実装選択の判断基準

next-sitemap

  • Pages Router使用環境
  • 複雑なサイトマップ要件
  • 単一言語サイト

App Router標準機能

  • App Router使用環境
  • 多言語サイト要件
  • TypeScript活用環境

結論

Next.js App Routerを使用した多言語サイトのSitemap実装において、標準機能の活用が最適解となった。
特にTypeScriptサポートと実装の単純さが開発効率を向上させる要因となっている。
多言語サイトでのSEO対応において、App Router標準機能の採用を推奨したいと個人的に思う。

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?