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('
')
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. 언어별 사이트맵을 위한 서버 라우트 정의
로케일 매개변수를 기반으로 각 언어에 대한 사이트맵을 생성하는 동적 서버 라우트를 생성합니다.
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로 명명된 파일은 동적 로케일 매개변수를 허용하는 라우트를 생성합니다. 각 언어별 사이트맵은 독립적으로 생성되며 다른 언어에 영향을 주지 않고 업데이트할 수 있습니다.
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 구조에 맞게 쿼리 및 매핑 로직을 조정하십시오.