TanStack Start v1에서 대체 언어 버전을 연결하는 방법
검색 엔진을 위한 언어 대체 버전 연결
문제
웹사이트가 여러 언어로 동일한 콘텐츠를 제공할 때, 검색 엔진은 기본적으로 각 언어 버전을 별도의 페이지로 취급합니다. 이러한 버전들을 연결하는 명시적인 신호가 없으면, 검색 엔진은 /en/about와 /fr/about가 경쟁하는 중복 콘텐츠가 아니라 동일한 콘텐츠의 번역본임을 이해할 수 없습니다. 이러한 분산은 언어 버전 간에 랭킹 권한을 분할하고 제공 문제를 발생시킵니다. 프랑스어를 사용하는 사용자가 프랑스어 번역이 존재함에도 불구하고 검색 결과에서 영어 버전이 더 높게 랭크되는 것을 볼 수 있습니다. 검색 엔진은 언어 변형 간의 관계를 이해하고 사용자의 언어 선호도와 위치에 따라 적절한 버전을 제공하기 위해 명시적인 메타데이터가 필요합니다.
솔루션
각 페이지의 사용 가능한 모든 언어 버전을 선언하는 hreflang 링크 요소를 문서 헤드에 추가하세요. 이러한 링크는 검색 엔진에 어떤 URL이 다른 언어로 동일한 콘텐츠를 포함하는지 알려주어, 랭킹 신호를 통합하고 사용자에게 올바른 버전을 제공할 수 있도록 합니다. 각 페이지는 자신에 대한 참조를 포함하여 모든 언어 대안을 나열하며, 검색 엔진이 번역 구조를 이해하는 데 사용하는 양방향 관계를 생성합니다. 이 메타데이터는 프레임워크의 헤드 관리 시스템을 사용하여 라우트별로 추가되며, 모든 언어 변형에 대한 URL을 구성하는 데 필요한 현재 라우트 매개변수에 액세스할 수 있습니다.
단계
1. 언어 대체 URL을 생성하는 헬퍼 생성
라우터의 buildLocation 메서드는 라우트 매개변수로부터 전체 URL을 구성하며, 이를 사용하여 각 언어 버전에 대한 URL을 생성합니다.
import { AnyRouter } from "@tanstack/react-router";
export function buildLanguageAlternates(
router: AnyRouter,
currentPath: string,
currentLang: string,
availableLanguages: string[],
) {
return availableLanguages.map((lang) => {
const location = router.buildLocation({
to: currentPath,
params: { lang },
});
return {
lang,
href: `${location.pathname}${location.search}${location.hash}`,
};
});
}
이 함수는 현재 라우트 경로를 가져와서 각 언어 코드를 라우트 매개변수에 대입하여 대체 URL을 생성합니다.
2. 사용 가능한 언어 정의
애플리케이션이 지원하는 모든 언어를 나열하는 구성 파일을 생성합니다.
export const AVAILABLE_LANGUAGES = ["en", "fr", "de", "es"];
export const DEFAULT_LANGUAGE = "en";
이 중앙 집중식 목록은 모든 페이지에 대한 hreflang 링크를 생성하는 데 사용됩니다.
3. 라우트의 head 함수에 hreflang 링크 추가
head 함수는 matches, params, loaderData를 포함한 컨텍스트를 수신하며, 이를 통해 현재 언어 매개변수와 라우터 인스턴스에 액세스할 수 있습니다.
import { createFileRoute } from "@tanstack/react-router";
import { buildLanguageAlternates, AVAILABLE_LANGUAGES } from "../i18n-config";
export const Route = createFileRoute("/$lang/about")({
head: ({ params }) => {
const alternates = buildLanguageAlternates(
Route.router,
"/$lang/about",
params.lang,
AVAILABLE_LANGUAGES,
);
return {
links: alternates.map((alt) => ({
rel: "alternate",
hreflang: alt.lang,
href: alt.href,
})),
};
},
component: AboutPage,
});
function AboutPage() {
return <div>About page content</div>;
}
hreflang 속성은 ISO 639-1 언어 코드를 사용하며, 각 링크는 다른 언어로 된 동일한 페이지를 가리킵니다.
4. 폴백 동작을 위한 x-default hreflang 추가
x-default hreflang 속성은 일치하는 언어가 없을 때 기본 페이지를 나타냅니다.
export const Route = createFileRoute("/$lang/about")({
head: ({ params }) => {
const alternates = buildLanguageAlternates(
Route.router,
"/$lang/about",
params.lang,
AVAILABLE_LANGUAGES,
);
const defaultUrl = alternates.find((alt) => alt.lang === "en");
return {
links: [
...alternates.map((alt) => ({
rel: "alternate",
hreflang: alt.lang,
href: alt.href,
})),
{
rel: "alternate",
hreflang: "x-default",
href: defaultUrl?.href || alternates[0].href,
},
],
};
},
component: AboutPage,
});
x-default 링크는 언어 기본 설정이 선언된 대체 언어와 일치하지 않는 사용자를 위한 폴백 URL을 제공합니다.
5. 매개변수가 있는 동적 라우트에 적용
언어 외에 추가 동적 세그먼트가 있는 라우트의 경우, 대체 언어를 구성할 때 해당 매개변수를 포함합니다.
export const Route = createFileRoute("/$lang/posts/$postId")({
head: ({ params }) => {
const alternates = AVAILABLE_LANGUAGES.map((lang) => {
const location = Route.router.buildLocation({
to: "/$lang/posts/$postId",
params: { lang, postId: params.postId },
});
return {
lang,
href: `${location.pathname}${location.search}${location.hash}`,
};
});
return {
links: [
...alternates.map((alt) => ({
rel: "alternate",
hreflang: alt.lang,
href: alt.href,
})),
{
rel: "alternate",
hreflang: "x-default",
href:
alternates.find((a) => a.lang === "en")?.href || alternates[0].href,
},
],
};
},
});
각 페이지는 자신을 포함한 모든 언어 버전을 참조해야 하며, 이를 통해 검색 엔진이 해당 콘텐츠의 전체 번역 세트를 이해할 수 있습니다.
6. hreflang 구현 확인
head 함수는 HeadContent 컴포넌트에 의해 렌더링되는 link 요소를 반환합니다. 렌더링된 HTML을 검사하여 링크가 문서 head에 나타나는지 확인합니다.
<link rel="alternate" hreflang="en" href="/en/about" />
<link rel="alternate" hreflang="fr" href="/fr/about" />
<link rel="alternate" hreflang="de" href="/de/about" />
<link rel="alternate" hreflang="es" href="/es/about" />
<link rel="alternate" hreflang="x-default" href="/en/about" />
페이지 간의 양방향 링크는 검색 엔진이 지역화된 버전 간의 관계를 이해하도록 보장하여, 각 사용자에게 가장 적절한 버전을 제공할 수 있게 합니다.