如何在 Next.js (Pages Router) v16 中创建多语言站点地图

按语言组织站点地图以实现扩展

问题

站点地图可以帮助搜索引擎发现并索引网站上的页面。对于一个拥有数百或数千个每种语言页面的多语言网站来说,将每个语言版本的所有 URL 列在一个站点地图中会迅速变得难以管理。大型的单一站点地图可能会超过站点地图协议定义的 50,000 个 URL 或 50MB 的大小限制,从而使其无效。即使它们仍在限制范围内,每次某种语言的内容发生变化时重新生成和验证一个庞大的文件也是低效的。随着网站的增长并添加更多语言或页面,这种方法无法扩展。

解决方案

使用站点地图索引文件将站点地图组织成一个层次结构。索引文件列出单独的语言特定站点地图,每个站点地图包含单一语言版本的 URL。这种结构使得单个站点地图文件易于管理并符合协议限制。当某种语言的内容发生变化时,只需要重新生成该语言的站点地图。随着新语言的添加,这种方法自然扩展——每种语言都有自己的站点地图,并在索引中引用。搜索引擎首先抓取索引,然后跟随链接到各个语言的站点地图。

步骤

1. 创建站点地图索引页面

pages 目录中创建一个页面,使用 getServerSideProps 动态生成站点地图索引。

import { GetServerSideProps } from "next";

const SITE_URL = "https://example.com";
const LOCALES = ["en", "es", "fr", "de"];

function generateSitemapIndex(locales: string[]): string {
  const sitemapEntries = locales
    .map((locale) => {
      return `
<sitemap>
  <loc>${SITE_URL}/sitemap-${locale}.xml</loc>
  <lastmod>${new Date().toISOString()}</lastmod>
</sitemap>`;
    })
    .join("");

  return `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemapEntries}
</sitemapindex>`;
}

export const getServerSideProps: GetServerSideProps = async ({ res }) => {
  const sitemap = generateSitemapIndex(LOCALES);

  res.setHeader("Content-Type", "text/xml");
  res.write(sitemap);
  res.end();

  return {
    props: {},
  };
};

export default function SitemapIndex() {}

索引使用 <sitemapindex> 根元素,其中包含 <sitemap> 条目,每个条目包含一个 <loc> 子元素,指向特定语言的站点地图。getServerSideProps 函数将 Content-Type 头设置为 text/xml 并直接写入 XML 响应。

2. 创建特定语言的站点地图页面

创建一个动态路由页面,为每种语言生成单独的站点地图。

import { GetServerSideProps } from "next";

const SITE_URL = "https://example.com";

interface PageData {
  slug: string;
  lastModified: string;
}

async function getPagesByLocale(locale: string): Promise<PageData[]> {
  return [
    { slug: "about", lastModified: "2024-01-15" },
    { slug: "contact", lastModified: "2024-01-20" },
  ];
}

function generateSitemap(locale: string, pages: PageData[]): string {
  const urlEntries = pages
    .map((page) => {
      return `
<url>
  <loc>${SITE_URL}/${locale}/${page.slug}</loc>
  <lastmod>${page.lastModified}</lastmod>
</url>`;
    })
    .join("");

  return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urlEntries}
</urlset>`;
}

export const getServerSideProps: GetServerSideProps = async ({
  params,
  res,
}) => {
  const locale = params?.locale as string;

  const pages = await getPagesByLocale(locale);
  const sitemap = generateSitemap(locale, pages);

  res.setHeader("Content-Type", "text/xml");
  res.write(sitemap);
  res.end();

  return {
    props: {},
  };
};

export default function LocaleSitemap() {}

动态路由从 URL 参数中提取语言代码,并生成仅包含该语言 URL 的 XML。每个站点地图都专注于单一语言的内容。

3. 获取特定语言的内容

用实际的数据源替换占位符 getPagesByLocale 函数。

async function getPagesByLocale(locale: string): Promise<PageData[]> {
  const response = await fetch(
    `https://api.example.com/pages?locale=${locale}`,
  );
  const data = await response.json();

  return data.pages.map((page: any) => ({
    slug: page.slug,
    lastModified: page.updatedAt,
  }));
}

此函数查询您的 CMS、数据库或 API,以检索指定语言的页面。它返回结构化数据,站点地图生成器将其转换为 XML 条目。

4. 将静态页面添加到每个站点地图

将每种语言中存在的静态路由与动态内容一起包含。

function generateSitemap(locale: string, pages: PageData[]): string {
  const staticPages = [
    { slug: "", lastModified: new Date().toISOString() },
    { slug: "about", lastModified: new Date().toISOString() },
  ];

  const allPages = [...staticPages, ...pages];

  const urlEntries = allPages
    .map((page) => {
      const path = page.slug ? `/${locale}/${page.slug}` : `/${locale}`;
      return `
<url>
  <loc>${SITE_URL}${path}</loc>
  <lastmod>${page.lastModified}</lastmod>
</url>`;
    })
    .join("");

  return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urlEntries}
</urlset>`;
}

结合静态和动态页面,确保每种语言的站点地图完整。静态页面使用当前时间戳作为其最后修改日期。

5. 在 robots.txt 中引用索引

将站点地图索引的位置添加到您的 robots.txt 文件中,以便搜索引擎可以发现它。

User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml

您只需要列出索引文件;搜索引擎会自动跟随链接到各个语言的站点地图。将此文件放置在 public 目录中。