ページメタデータの翻訳

ローカライズされた<title>タグと<description>タグの設定

問題

ユーザーがスペイン語でページを閲覧すると、表示されているコンテンツはすべて正しく翻訳されています。しかし、ブラウザのタブや検索エンジンの結果スニペットには、依然として英語のタイトルと説明が表示されています。このメタデータの不一致は、ユーザー体験を混乱させ、検索で無関係な情報を表示することでSEOに悪影響を与えます。

解決策

Next.jsのgenerateMetadata関数をページやレイアウト内で使用します。このサーバーサイド関数は、langパラメータに基づいて正しい翻訳を読み込み(コンポーネントと同じ辞書読み込みロジックを使用)、ローカライズされたタイトルと説明を含む動的なmetadataオブジェクトを返すことができます。

手順

1. 辞書を読み込む関数を作成する

サーバー上でフラットな翻訳ファイルを読み込む方法が必要です。このためのヘルパー関数を作成します。

// app/get-dictionary.ts
import 'server-only';

type Messages = Record<string, string>;

const dictionaries: { [key: string]: () => Promise<Messages> } = {
  en: () => import('@/dictionaries/en.json').then((module) => module.default),
  es: () => import('@/dictionaries/es.json').then((module) => module.default),
};

export const getDictionary = async (lang: string) => {
  const load = dictionaries[lang];
  if (load) {
    return load();
  }
  return dictionaries.en();
};

2. ページにメタデータを定義する

ページファイル(例:app/[lang]/about/page.tsx)で、generateMetadataというasync関数をエクスポートします。Next.jsはページをレンダリングする際に自動的にこの関数を呼び出します。

// app/[lang]/about/page.tsx
import { getDictionary } from '@/app/get-dictionary';
import type { Metadata } from 'next';

type Props = {
  params: { lang: string };
};

// このメタデータを生成する関数
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  // このページの辞書を読み込む
  const dict = await getDictionary(params.lang);

  return {
    title: dict['about.title'], // 例:「About Us」または「Sobre Nosotros」
    description: dict['about.description'],
  };
}

// ページコンポーネントの残りの部分
export default function AboutPage() {
  return (
    <div>
      {/* ページコンテンツ */}
      <h1>...</h1>
    </div>
  );
}

3. ルートレイアウトでタイトルテンプレートを設定する

サイト名をすべてのタイトルで繰り返すことを避けるために、ルートレイアウトでテンプレートを設定できます。

// app/[lang]/layout.tsx
import { getDictionary } from '@/app/get-dictionary';
import type { Metadata } from 'next';

type Props = {
  params: { lang: string };
  children: React.ReactNode;
};

// レイアウトでもメタデータを生成できます
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const dict = await getDictionary(params.lang);

  return {
    // これは基本タイトルとテンプレートを提供します
    title: {
      default: dict['site.name'], // 例:「My Awesome Site」
      template: `%s | ${dict['site.name']}`, // 例:「About Us | My Awesome Site」
    },
    description: dict['site.description'],
  };
}

export default async function RootLayout({ children, params }: Props) {
  // ... レイアウトの残りの部分(プロバイダーの読み込みなど)
  return (
    <html lang={params.lang}>
      <body>{children}</body>
    </html>
  );
}