TanStack Start v1でページメタデータを翻訳する方法
検索とソーシャルメディア向けにメタデータを翻訳
問題
ページメタデータ(タイトルと説明)は、ブラウザのタブ、ブックマーク、検索結果、ソーシャルメディアのプレビューに表示されます。メタデータの言語がページコンテンツの言語と一致しない場合、ユーザーはページを閲覧する前に不快な不一致を経験します。スペイン語のページに英語のタイトルが検索結果に表示されることは、ローカライゼーションの品質が低いことを示します。検索エンジンはこの不一致をランキングシグナルとして解釈し、言語固有の検索結果での可視性を低下させる可能性があります。
この不一致は、発見段階でのユーザーエクスペリエンスを損ないます。ユーザーは好みの言語で検索する際、メタデータが一致することを期待しており、不一致は最初のクリック前に信頼を損ないます。
解決策
ページコンテンツと同じ翻訳リソースを使用してページメタデータを翻訳します。ルート設定でhead関数を定義し、翻訳された文字列にアクセスして、ローカライズされたタイトルと説明のメタデータを返します。これにより、ブラウザのクローム、検索結果、レンダリングされたページに表示される内容の一貫性が保証されます。
head関数内でreact-intlのメッセージフォーマットを使用することで、メタデータは翻訳ワークフローと同期し、ロケールが変更されると自動的に更新されます。
手順
1. Reactコンポーネント外でメッセージをフォーマットするヘルパーを作成
head関数は、フックが使用できないReactコンポーネントツリーの外で実行されます。react-intlのcreateIntlを使用してメッセージをフォーマットするユーティリティを作成します。
import { createIntl, createIntlCache } from "react-intl";
const cache = createIntlCache();
export function formatMetadataMessage(
locale: string,
messages: Record<string, string>,
id: string,
values?: Record<string, string | number>,
): string {
const intl = createIntl({ locale, messages }, cache);
return intl.formatMessage({ id }, values);
}
このヘルパーは、head関数などのReact以外のコンテキストで使用するために、オンデマンドでintlインスタンスを作成します。
2. メタデータ翻訳キーを定義
ローカライズされたメタデータが必要な各ページの翻訳ファイルに、タイトルと説明のキーを追加します。
export const enMessages = {
"page.home.title": "Welcome to Our Site",
"page.home.description": "Discover amazing content in your language",
"page.about.title": "About Us",
"page.about.description": "Learn more about our mission and team",
};
export const esMessages = {
"page.home.title": "Bienvenido a Nuestro Sitio",
"page.home.description": "Descubre contenido increíble en tu idioma",
"page.about.title": "Acerca de Nosotros",
"page.about.description": "Conoce más sobre nuestra misión y equipo",
};
これらのキーは、コンポーネントの翻訳と同じパターンに従い、すべてのローカライズされたコンテンツを1か所にまとめます。
3. ルートにhead関数を追加する
createFileRouteのheadオプションを使用して、翻訳されたメタデータを返します。ルートパラメータまたはローダーデータから現在のロケールにアクセスし、ヘルパーを使用してメッセージをフォーマットします。
import { createFileRoute } from "@tanstack/react-router";
import { formatMetadataMessage } from "../utils/formatMetadataMessage";
import { enMessages, esMessages } from "../i18n/messages";
const messagesByLocale = {
en: enMessages,
es: esMessages,
};
export const Route = createFileRoute("/$locale/about")({
head: ({ params }) => {
const locale = params.locale || "en";
const messages = messagesByLocale[locale] || messagesByLocale.en;
return {
meta: [
{
title: formatMetadataMessage(locale, messages, "page.about.title"),
},
{
name: "description",
content: formatMetadataMessage(
locale,
messages,
"page.about.description",
),
},
],
};
},
component: AboutPage,
});
function AboutPage() {
return <div>About content</div>;
}
head関数は、ルートマッチング中に実行され、TanStack Startがドキュメントヘッドにレンダリングするメタデータオブジェクトを返します。
4. 動的メタデータにローダーデータを使用する
メタデータが取得したデータに依存する場合、head関数内でloaderDataにアクセスし、動的コンテンツと翻訳されたテンプレートを組み合わせます。
import { createFileRoute } from "@tanstack/react-router";
import { formatMetadataMessage } from "../utils/formatMetadataMessage";
import { enMessages, esMessages } from "../i18n/messages";
const messagesByLocale = {
en: enMessages,
es: esMessages,
};
export const Route = createFileRoute("/$locale/posts/$postId")({
loader: async ({ params }) => {
const post = await fetchPost(params.postId);
return { post };
},
head: ({ params, loaderData }) => {
const locale = params.locale || "en";
const messages = messagesByLocale[locale] || messagesByLocale.en;
const { post } = loaderData;
return {
meta: [
{
title: formatMetadataMessage(locale, messages, "page.post.title", {
title: post.title,
}),
},
{
name: "description",
content: post.excerpt,
},
],
};
},
component: PostPage,
});
function PostPage() {
const { post } = Route.useLoaderData();
return <article>{post.content}</article>;
}
ローダーはhead関数が実行される前にデータを取得するため、翻訳されたメタデータテンプレートに動的な値を補間できます。