如何在 TanStack Start v1 中创建多语言网站地图
按语言组织网站地图以实现扩展
问题
站点地图可以帮助搜索引擎发现并抓取网站上的所有页面。一个拥有数百或数千个页面的多语言网站会迅速生成庞大的站点地图。将每种语言的所有 URL 列在一个文件中会变得难以管理,并可能超过站点地图协议定义的 50,000 个 URL 或 50MB 的大小限制。当您更新某种语言的结构时,必须重新生成并重新验证整个文件。大型的单一站点地图难以维护,处理速度慢,并且在添加语言或内容时无法扩展。
解决方案
将您的站点地图拆分为多个文件,并使用站点地图索引文件一次提交多个站点地图。在 /sitemap.xml 创建一个顶级站点地图索引,指向单独的语言特定站点地图,例如 /sitemap-en.xml 和 /sitemap-es.xml。站点地图索引文件的 XML 格式与常规站点地图类似,并由站点地图协议定义。这种方法使单个文件易于管理,允许您独立更新每种语言,并且在添加新语言或页面时具有良好的扩展性。
步骤
1. 创建一个生成站点地图 XML 的辅助工具
构建一个实用函数,从 URL 条目数组生成有效的站点地图 XML。
export function generateSitemapXML(urls: Array<{ loc: string; lastmod?: string; changefreq?: string; priority?: number }>): string {
const entries = urls.map(url => {
let entry = ` <url>
<loc>${url.loc}</loc>`
if (url.lastmod) entry += `
<lastmod>${url.lastmod}</lastmod>`
if (url.changefreq) entry += `
<changefreq>${url.changefreq}</changefreq>`
if (url.priority !== undefined) entry += `
<priority>${url.priority}</priority>`
entry += `
</url>`
return entry
}).join('\n')
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${entries}
</urlset>`
}
此函数接受一个 URL 对象数组,并返回一个具有所需命名空间和结构的正确格式的 XML 字符串。
2. 创建一个辅助工具来生成站点地图索引 XML
构建第二个实用函数,用于生成指向多个子站点地图的站点地图索引。
export function generateSitemapIndexXML(sitemaps: Array<{ loc: string; lastmod?: string }>): string {
const entries = sitemaps.map(sitemap => {
let entry = ` <sitemap>
<loc>${sitemap.loc}</loc>`
if (sitemap.lastmod) entry += `
<lastmod>${sitemap.lastmod}</lastmod>`
entry += `
</sitemap>`
return entry
}).join('
')
return `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${entries}
</sitemapindex>`
}
站点地图索引用 <sitemapindex> 根标签,并为每个站点地图包含一个 <sitemap> 条目,每个父标签都有一个 <loc> 子条目。
3. 为主站点地图索引定义一个服务器路由
在 /sitemap.xml 创建一个服务器路由,返回列出所有语言特定站点地图的站点地图索引。
import { createFileRoute } from "@tanstack/react-router";
import { generateSitemapIndexXML } from "~/utils/sitemap";
const SUPPORTED_LOCALES = ["en", "es", "fr", "de"];
const BASE_URL = "https://example.com";
export const Route = createFileRoute("/sitemap")({
server: {
handlers: {
GET: async () => {
const sitemaps = SUPPORTED_LOCALES.map((locale) => ({
loc: `${BASE_URL}/sitemap-${locale}.xml`,
lastmod: new Date().toISOString().split("T")[0],
}));
const xml = generateSitemapIndexXML(sitemaps);
return new Response(xml, {
headers: {
"Content-Type": "application/xml",
"Cache-Control": "public, max-age=3600",
},
});
},
},
},
});
此路由生成一个索引,指向每种语言的一个站点地图,并以 XML 格式提供,同时设置适当的缓存头。
4. 为语言特定的站点地图定义服务器路由
创建一个动态服务器路由,根据 locale 参数为每种语言生成一个站点地图。
import { createFileRoute } from "@tanstack/react-router";
import { generateSitemapXML } from "~/utils/sitemap";
const BASE_URL = "https://example.com";
async function getUrlsForLocale(locale: string) {
return [
{ loc: `${BASE_URL}/${locale}`, changefreq: "daily", priority: 1.0 },
{
loc: `${BASE_URL}/${locale}/about`,
changefreq: "monthly",
priority: 0.8,
},
{
loc: `${BASE_URL}/${locale}/contact`,
changefreq: "monthly",
priority: 0.8,
},
];
}
export const Route = createFileRoute("/sitemap-$locale")({
server: {
handlers: {
GET: async ({ params }) => {
const { locale } = params;
const urls = await getUrlsForLocale(locale);
const xml = generateSitemapXML(urls);
return new Response(xml, {
headers: {
"Content-Type": "application/xml",
"Cache-Control": "public, max-age=3600",
},
});
},
},
},
});
服务器路由支持与 TanStack Router 相同的动态路径参数,因此以 $locale 命名的文件会创建一个接受动态 locale 参数的路由。每种语言特定的站点地图是独立生成的,可以在不影响其他语言的情况下更新。
5. 从数据源获取 URL
将占位符函数 getUrlsForLocale 替换为从您的数据库、CMS 或路由定义中获取实际 URL 的逻辑。
async function getUrlsForLocale(locale: string) {
const pages = await db.page.findMany({
where: { locale, published: true },
select: { slug: true, updatedAt: true },
});
return pages.map((page) => ({
loc: `${BASE_URL}/${locale}/${page.slug}`,
lastmod: page.updatedAt.toISOString().split("T")[0],
changefreq: "weekly",
priority: 0.7,
}));
}
此示例查询数据库中给定语言的已发布页面,并将其映射为带有元数据的网站地图条目。根据您的数据模型和 URL 结构调整查询和映射逻辑。