ホームサンドボックスショーケースアプリ文書ブログ
    • English英語
      EN
    • Русскийロシア語
      RU
    • 日本語日本語
      JA
    • françaisフランス語
      FR
    • 한국어韓国語
      KO
    • 中文中国語
      ZH
    • Españolスペイン語
      ES
    • Deutschドイツ語
      DE
    • العربيةアラビア語
      AR
    • Italianoイタリア語
      IT
    • British Englishイギリス英語
      EN-GB
    • Portuguêsポルトガル語
      PT
    • हिन्दीヒンディー語
      HI
    • Türkçeトルコ語
      TR
    • polskiポーランド語
      PL
    • Indonesiaインドネシア語
      ID
    • Tiếng Việtベトナム語
      VI
    • Українськаウクライナ語
      UK
    /
    Alt+←
    国際化とは?
    SEOと国際化
    ガイド
    • next-i18nextによるi18n
    • next-intlによるi18n
    Intlayerをあなたのソリューションで使う
    • next-i18nextを自動化
    • react-i18nextを自動化
    • next-intlを自動化
    • react-intlを自動化
    • vue-i18nを自動化
    比較
    • next-i18next vs next-intl vs Intlayer
    • react-i18next vs react-intl vs Intlayer
    ドキュメンテーション
    1. Blog
    2. Blog seo i18n nextjs
    Creation:2025-09-28Last update:2025-09-28
    このドキュメントをあなたの好きなAIアシスタントに参照してください
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    このページとあなたの好きなAIアシスタントを使ってドキュメントを要約します

    このページのコンテンツはAIを使用して翻訳されました。

    英語の元のコンテンツの最新バージョンを見る
    Edit this doc

    If you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.

    GitHub link to the documentation
    Copy

    Copy doc Markdown to clipboard

    Next.jsにおけるSEOとi18n:翻訳だけでは不十分

    開発者が国際化(i18n)を考えるとき、最初の反応はしばしば「コンテンツを翻訳すること」です。しかし、多くの人は国際化の主な目的が、あなたのウェブサイトを世界により見えるようにすることだということを忘れがちです。 多言語対応のNext.jsアプリが、検索エンジンに対して異なる言語バージョンのクロールや理解の方法を伝えなければ、あなたの多くの努力は見過ごされてしまうかもしれません。

    このブログでは、なぜi18nがSEOの強力な武器となるのか、そしてnext-intl、next-i18next、Intlayerを使ってNext.jsで正しく実装する方法を探っていきます。


    なぜSEOとi18nが重要なのか

    言語を追加することは単なるUXの向上だけではありません。それはオーガニックな可視性を高める強力な手段でもあります。理由は以下の通りです:

    1. より良い発見性: 検索エンジンはローカライズされたバージョンをインデックスし、ユーザーが母国語で検索した際にそれらをランク付けします。
    2. 重複コンテンツの回避: 適切なカノニカルタグと代替タグが、クローラーにどのページがどのロケールに属するかを伝えます。
    3. より良いUX: 訪問者はすぐにサイトの適切なバージョンにアクセスできます。
    4. 競争優位性: 多言語SEOを適切に実装しているサイトは少ないため、あなたのサイトは際立つことができます。

    Next.jsにおける多言語SEOのベストプラクティス

    すべての多言語アプリが実装すべきチェックリストはこちらです:

    • <head>内にhreflangメタタグを設定する
      Googleが各言語のバージョンを理解するのに役立ちます。

    • 翻訳されたすべてのページをsitemap.xmlにリストアップする
      xhtmlスキーマを使用して、クローラーが代替ページを簡単に見つけられるようにします。

    • robots.txtでプライベート/ローカライズされたルートを除外する
      例:/dashboard、/fr/dashboard、/es/dashboardなどがインデックスされないようにします。

    • ローカライズされたリンクを使用する
      例:デフォルトの/aboutではなく、<a href="/fr/about">À propos</a>のようにリンクします。

    これらは簡単なステップですが、スキップすると可視性を失う可能性があります。


    実装例

    開発者はしばしばロケール間でページを適切に参照することを忘れがちなので、さまざまなライブラリでどのように機能するかを見てみましょう。

    next-intl

    src/app/[locale]/about/layout.tsx
    コードをコピー

    コードをクリップボードにコピー

    import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations, unstable_setRequestLocale } from "next-intl/server";function localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;const t = await getTranslations({ locale, namespace: "about" });const url = "/about";const languages = Object.fromEntries(  locales.map((l) => [l, localizedPath(l, url)]));return {  title: t("title"),  description: t("description"),  alternates: {    canonical: localizedPath(locale, url),    languages: { ...languages, "x-default": url },  },};}// ... ページの残りのコード
    src/app/sitemap.ts
    コードをコピー

    コードをクリップボードにコピー

    import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const formatterLocalizedPath = (locale: string, path: string) =>locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;export default function sitemap(): MetadataRoute.Sitemap {const aboutLanguages = Object.fromEntries(  locales.map((l) => [l, formatterLocalizedPath(l, "/about")]));return [  {    url: formatterLocalizedPath(defaultLocale, "/about"),    lastModified: new Date(),    changeFrequency: "monthly", // 毎月の更新頻度    priority: 0.7, // 優先度    alternates: { languages: aboutLanguages }, // 言語別の代替URL  },];}
    src/app/robots.ts
    コードをコピー

    コードをクリップボードにコピー

    import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const withAllLocales = (path: string) => [path,...locales.filter((l) => l !== defaultLocale).map((l) => `/${l}${path}`), // デフォルトロケール以外のパスを追加];export default function robots(): MetadataRoute.Robots {const disallow = [  ...withAllLocales("/dashboard"), // /dashboard へのアクセス禁止  ...withAllLocales("/admin"), // /admin へのアクセス禁止];return {  rules: { userAgent: "*", allow: ["/"], disallow },  host: origin,  sitemap: `${origin}/sitemap.xml`,};}

    next-i18next

    i18n.config.ts
    コードをコピー

    コードをクリップボードにコピー

    export const locales = ["en", "fr"] as const;export type Locale = (typeof locales)[number];export const defaultLocale: Locale = "en";/** デフォルトロケールでない場合はロケールをパスの前に付ける */export function localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}/** 絶対URLヘルパー */const ORIGIN = "https://example.com";export function abs(locale: string, path: string) {return `${ORIGIN}${localizedPath(locale, path)}`;}
    src/app/[locale]/about/layout.tsx
    コードをコピー

    コードをクリップボードにコピー

    import type { Metadata } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;// 正しいJSONファイルを動的にインポートするconst messages = (await import(`@/../public/locales/${locale}/about.json`))  .default;const languages = Object.fromEntries(  locales.map((l) => [l, localizedPath(l, "/about")]));return {  title: messages.title,  description: messages.description,  alternates: {    canonical: localizedPath(locale, "/about"),    languages: { ...languages, "x-default": "/about" },  },};}export default async function AboutPage() {return <h1>About</h1>;}
    src/app/sitemap.ts
    コードをコピー

    コードをクリップボードにコピー

    import type { MetadataRoute } from "next";import { locales, defaultLocale, abs } from "@/i18n.config";export default function sitemap(): MetadataRoute.Sitemap {const languages = Object.fromEntries(  locales.map((l) => [l, abs(l, "/about")]));return [  {    url: abs(defaultLocale, "/about"),    lastModified: new Date(),    changeFrequency: "monthly",    priority: 0.7,    alternates: { languages },  },];}
    src/app/robots.ts
    コードをコピー

    コードをクリップボードにコピー

    import type { MetadataRoute } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";const ORIGIN = "https://example.com";const expandAllLocales = (path: string) => [localizedPath(defaultLocale, path),...locales  .filter((l) => l !== defaultLocale)  .map((l) => localizedPath(l, path)),];export default function robots(): MetadataRoute.Robots {const disallow = [  ...expandAllLocales("/dashboard"),  ...expandAllLocales("/admin"),];return {  rules: { userAgent: "*", allow: ["/"], disallow },  host: ORIGIN,  sitemap: `${ORIGIN}/sitemap.xml`,};}

    Intlayer

    src/app/[locale]/about/layout.tsx
    コードをコピー

    コードをクリップボードにコピー

    import { getIntlayer, getMultilingualUrls } from "intlayer";import type { Metadata } from "next";import type { LocalPromiseParams } from "next-intlayer";export const generateMetadata = async ({params,}: LocalPromiseParams): Promise<Metadata> => {const { locale } = await params;const metadata = getIntlayer("page-metadata", locale);/** * 各ロケールのすべてのURLを含むオブジェクトを生成します。 * * 例: * ```ts *  getMultilingualUrls('/about'); * *  // 戻り値 *  // { *  //   en: '/about', *  //   fr: '/fr/about', *  //   es: '/es/about', *  // } * ``` */const multilingualUrls = getMultilingualUrls("/about");return {  ...metadata,  alternates: {    canonical: multilingualUrls[locale as keyof typeof multilingualUrls],    languages: { ...multilingualUrls, "x-default": "/about" },  },};};// ... ページの残りのコード
    src/app/sitemap.ts
    コードをコピー

    コードをクリップボードにコピー

    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [{  url: "https://example.com/about",  alternates: {    languages: { ...getMultilingualUrls("https://example.com/about") },  },},];
    src/app/robots.ts
    コードをコピー

    コードをクリップボードにコピー

    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";// すべての多言語URLを取得する関数const getAllMultilingualUrls = (urls: string[]) =>urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);// robots.txtの設定を生成const robots = (): MetadataRoute.Robots => ({rules: {  userAgent: "*", // すべてのユーザーエージェントに適用  allow: ["/"], // ルートパスは許可  disallow: getAllMultilingualUrls(["/dashboard"]), // /dashboard以下の多言語URLはクロール禁止},host: "https://example.com", // ホスト名sitemap: `https://example.com/sitemap.xml`, // サイトマップのURL});export default robots;
    Intlayerは、サイトマップ用の多言語URLを生成するためのgetMultilingualUrls関数を提供しています。

    結論

    Next.jsでi18nを正しく実装することは、単にテキストを翻訳するだけでなく、検索エンジンやユーザーがどのバージョンのコンテンツを提供すべきかを正確に認識できるようにすることです。 hreflang、サイトマップ、robotsルールの設定が、翻訳を真のSEO価値に変える鍵となります。

    next-intlやnext-i18nextは、この連携を実現するための堅実な方法を提供しますが、通常はロケール間で一貫性を保つために多くの手動設定が必要です。

    ここでIntlayerが真価を発揮します:

    getMultilingualUrlsのような組み込みヘルパーが付属しており、hreflang、サイトマップ、robotsの統合をほぼ手間なく実現します。

    メタデータはJSONファイルやカスタムユーティリティに散在するのではなく、一元管理されます。

    Next.jsのために最初から設計されているため、設定のデバッグに費やす時間を減らし、リリースに集中できます。

    単に翻訳するだけでなく、多言語SEOをスムーズに拡大することを目指すなら、Intlayerは最もクリーンで将来性のあるセットアップを提供します。

    国際化とは?
    Alt+→

    このページについて

      ディスカッションは匿名で、よくある問題に対処するために定期的に確認されます。機能のアイデア、ドキュメントへのフィードバック、Intlayerに関することなど、お気軽にお聞かせください, このフィードバックをロードマップの作成と製品の改善に活用しています。

      import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations, unstable_setRequestLocale } from "next-intl/server";function localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;const t = await getTranslations({ locale, namespace: "about" });const url = "/about";const languages = Object.fromEntries(  locales.map((l) => [l, localizedPath(l, url)]));return {  title: t("title"),  description: t("description"),  alternates: {    canonical: localizedPath(locale, url),    languages: { ...languages, "x-default": url },  },};}// ... ページの残りのコード
      import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const formatterLocalizedPath = (locale: string, path: string) =>locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;export default function sitemap(): MetadataRoute.Sitemap {const aboutLanguages = Object.fromEntries(  locales.map((l) => [l, formatterLocalizedPath(l, "/about")]));return [  {    url: formatterLocalizedPath(defaultLocale, "/about"),    lastModified: new Date(),    changeFrequency: "monthly", // 毎月の更新頻度    priority: 0.7, // 優先度    alternates: { languages: aboutLanguages }, // 言語別の代替URL  },];}
      import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const withAllLocales = (path: string) => [path,...locales.filter((l) => l !== defaultLocale).map((l) => `/${l}${path}`), // デフォルトロケール以外のパスを追加];export default function robots(): MetadataRoute.Robots {const disallow = [  ...withAllLocales("/dashboard"), // /dashboard へのアクセス禁止  ...withAllLocales("/admin"), // /admin へのアクセス禁止];return {  rules: { userAgent: "*", allow: ["/"], disallow },  host: origin,  sitemap: `${origin}/sitemap.xml`,};}
      export const locales = ["en", "fr"] as const;export type Locale = (typeof locales)[number];export const defaultLocale: Locale = "en";/** デフォルトロケールでない場合はロケールをパスの前に付ける */export function localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}/** 絶対URLヘルパー */const ORIGIN = "https://example.com";export function abs(locale: string, path: string) {return `${ORIGIN}${localizedPath(locale, path)}`;}
      import type { Metadata } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;// 正しいJSONファイルを動的にインポートするconst messages = (await import(`@/../public/locales/${locale}/about.json`))  .default;const languages = Object.fromEntries(  locales.map((l) => [l, localizedPath(l, "/about")]));return {  title: messages.title,  description: messages.description,  alternates: {    canonical: localizedPath(locale, "/about"),    languages: { ...languages, "x-default": "/about" },  },};}export default async function AboutPage() {return <h1>About</h1>;}
      import type { MetadataRoute } from "next";import { locales, defaultLocale, abs } from "@/i18n.config";export default function sitemap(): MetadataRoute.Sitemap {const languages = Object.fromEntries(  locales.map((l) => [l, abs(l, "/about")]));return [  {    url: abs(defaultLocale, "/about"),    lastModified: new Date(),    changeFrequency: "monthly",    priority: 0.7,    alternates: { languages },  },];}
      import type { MetadataRoute } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";const ORIGIN = "https://example.com";const expandAllLocales = (path: string) => [localizedPath(defaultLocale, path),...locales  .filter((l) => l !== defaultLocale)  .map((l) => localizedPath(l, path)),];export default function robots(): MetadataRoute.Robots {const disallow = [  ...expandAllLocales("/dashboard"),  ...expandAllLocales("/admin"),];return {  rules: { userAgent: "*", allow: ["/"], disallow },  host: ORIGIN,  sitemap: `${ORIGIN}/sitemap.xml`,};}
      import { getIntlayer, getMultilingualUrls } from "intlayer";import type { Metadata } from "next";import type { LocalPromiseParams } from "next-intlayer";export const generateMetadata = async ({params,}: LocalPromiseParams): Promise<Metadata> => {const { locale } = await params;const metadata = getIntlayer("page-metadata", locale);/** * 各ロケールのすべてのURLを含むオブジェクトを生成します。 * * 例: * ```ts *  getMultilingualUrls('/about'); * *  // 戻り値 *  // { *  //   en: '/about', *  //   fr: '/fr/about', *  //   es: '/es/about', *  // } * ``` */const multilingualUrls = getMultilingualUrls("/about");return {  ...metadata,  alternates: {    canonical: multilingualUrls[locale as keyof typeof multilingualUrls],    languages: { ...multilingualUrls, "x-default": "/about" },  },};};// ... ページの残りのコード
      import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [{  url: "https://example.com/about",  alternates: {    languages: { ...getMultilingualUrls("https://example.com/about") },  },},];
      import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";// すべての多言語URLを取得する関数const getAllMultilingualUrls = (urls: string[]) =>urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);// robots.txtの設定を生成const robots = (): MetadataRoute.Robots => ({rules: {  userAgent: "*", // すべてのユーザーエージェントに適用  allow: ["/"], // ルートパスは許可  disallow: getAllMultilingualUrls(["/dashboard"]), // /dashboard以下の多言語URLはクロール禁止},host: "https://example.com", // ホスト名sitemap: `https://example.com/sitemap.xml`, // サイトマップのURL});export default robots;