如何在 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. 创建特定语言的 sitemap 页面
创建一个动态路由页面,为每种语言生成独立的 sitemap。
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 参数中提取 locale,并生成仅包含该语言 URL 的 XML。每个 sitemap 都专注于单一语言的内容。
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,以获取指定 locale 的页面。它返回结构化数据,sitemap 生成器会将其转换为 XML 条目。
4. 向每个 sitemap 添加静态页面
在动态内容之外,包含每种语言下都存在的静态路由。
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>`;
}
将静态和动态页面结合,确保每个语言 sitemap 完整。静态页面的最后修改时间使用当前时间戳。
5. 在 robots.txt 中引用索引
将 sitemap 索引的位置添加到 robots.txt 文件,便于搜索引擎发现。
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
你只需列出索引文件,搜索引擎会自动跟踪到各语言的 sitemap。请将此文件放在 public 目录下。