翻译页面元数据

设置本地化的<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),导出一个名为 generateMetadataasync 函数。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'], // 例如,"关于我们" 或 "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>
  );
}