React Router v7에서 대체 언어 버전을 연결하는 방법

검색 엔진을 위한 언어 대체 버전 연결

문제

웹사이트가 여러 언어로 동일한 콘텐츠를 제공할 때, 검색 엔진은 어려움에 직면합니다. 명시적인 신호가 없으면 각 언어 버전을 별개의 관련 없는 페이지로 취급합니다. 프랑스어로 검색하는 프랑스 사용자는 프랑스어 버전이 존재함에도 불구하고 영어 버전이 더 높은 순위에 표시될 수 있습니다. 마찬가지로 영어 사용자가 독일어 페이지에 도달할 수 있습니다. 이는 검색 엔진이 /en/about/fr/about가 중복 콘텐츠를 가진 경쟁 페이지가 아니라 서로의 번역본임을 자동으로 판단할 수 없기 때문에 발생합니다.

이러한 혼란은 언어 버전 간의 순위 권한을 분산시키고 사용자 경험을 저하시킵니다. 검색 엔진은 어떤 페이지가 동일한 콘텐츠의 언어 대체 버전인지 이해하여 각 사용자의 언어 선호도와 위치에 따라 적절한 버전을 제공할 수 있도록 명시적인 메타데이터가 필요합니다.

해결 방법

각 페이지에 해당 콘텐츠의 사용 가능한 모든 언어 버전을 나열하는 hreflang 링크 태그를 추가하세요. 이러한 태그는 rel="alternate" 속성을 사용하여 링크된 페이지가 중복이 아닌 번역본임을 알립니다. 각 태그는 언어 코드를 지정하고 해당 언어 버전의 URL을 가리킵니다.

페이지 메타데이터에서 이러한 관계를 선언함으로써 검색 엔진이 사이트 구조를 이해하고 각 사용자에게 올바른 언어 버전을 제공하도록 돕습니다. 이는 검색 결과의 관련성을 향상시키고 중복 콘텐츠 페널티를 방지합니다.

단계

1. hreflang 링크를 생성하는 헬퍼 함수 만들기

hreflang 속성은 ISO 639-1 언어 코드를 사용하며 선택적으로 ISO 3166-1 Alpha 2 지역 코드가 뒤따릅니다. 페이지의 모든 언어 버전에 대한 링크 설명자를 생성하는 유틸리티를 만듭니다.

type HreflangLink = {
  tagName: "link";
  rel: "alternate";
  hrefLang: string;
  href: string;
};

export function buildHreflangLinks(
  pathname: string,
  locales: string[],
  baseUrl: string,
): HreflangLink[] {
  return locales.map((locale) => ({
    tagName: "link",
    rel: "alternate",
    hrefLang: locale,
    href: `${baseUrl}/${locale}${pathname}`,
  }));
}

이 함수는 현재 경로명, 지원되는 로케일 목록, 사이트의 기본 URL을 받아서 meta 함수가 렌더링할 수 있는 링크 설명자 배열을 반환합니다.

2. x-default 대체 링크 추가

x-default hreflang 값은 다른 페이지가 더 적합하지 않을 때 기본 페이지를 나타내며 특정 언어나 로케일을 대상으로 하지 않습니다. 지원하지 않는 언어를 사용하는 사용자를 안내하기 위해 이를 추가하세요.

export function buildHreflangLinks(
  pathname: string,
  locales: string[],
  baseUrl: string,
  defaultLocale: string,
): HreflangLink[] {
  const links = locales.map((locale) => ({
    tagName: "link",
    rel: "alternate",
    hrefLang: locale,
    href: `${baseUrl}/${locale}${pathname}`,
  }));

  links.push({
    tagName: "link",
    rel: "alternate",
    hrefLang: "x-default",
    href: `${baseUrl}/${defaultLocale}${pathname}`,
  });

  return links;
}

x-default 링크는 일반적으로 기본 언어 버전을 가리키며 언어 기본 설정이 특정 언어 버전과 일치하지 않는 사용자를 위한 대체 수단으로 사용됩니다.

3. meta 함수에서 hreflang 링크 내보내기

meta 함수는 데이터를 기반으로 link 태그를 설정할 수 있습니다. 각 라우트에 대한 hreflang 링크를 반환하는 데 사용하세요.

import type { Route } from "./+types/about";
import { buildHreflangLinks } from "~/utils/hreflang";

const SUPPORTED_LOCALES = ["en", "fr", "de", "es"];
const BASE_URL = "https://example.com";
const DEFAULT_LOCALE = "en";

export function meta({ location }: Route.MetaArgs) {
  const hreflangLinks = buildHreflangLinks(
    location.pathname,
    SUPPORTED_LOCALES,
    BASE_URL,
    DEFAULT_LOCALE,
  );

  return [
    { title: "About Us" },
    { name: "description", content: "Learn about our company" },
    ...hreflangLinks,
  ];
}

meta 함수는 tagName이 "link"로 설정된 객체를 포함할 수 있는 설명자 배열을 반환합니다. React Router는 이를 문서 head의 link 요소로 렌더링합니다.

4. 루트 레이아웃에 Meta 컴포넌트가 있는지 확인

Meta 컴포넌트는 라우트 모듈의 meta 내보내기로 생성된 모든 메타 태그를 렌더링하며 문서의 head 내부에 있어야 합니다. 루트 레이아웃에 포함되어 있는지 확인하세요.

import { Links, Meta, Outlet, Scripts } from "react-router";

export default function Root() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  );
}

Meta 컴포넌트는 3단계에서 정의한 hreflang 링크 태그를 포함하여 일치하는 라우트의 모든 메타 설명자를 집계하고 렌더링합니다.

5. 로케일 접두사가 있는 라우트의 경로명 조정

경로에 로케일이 포함된 경우(예: /en/about), hreflang 링크를 생성하기 전에 이를 제거하여 모든 언어 버전이 동일한 논리적 페이지를 가리키도록 하세요.

export function meta({ location }: Route.MetaArgs) {
  const pathWithoutLocale = location.pathname.replace(/^\/[a-z]{2}(\/|$)/, "/");

  const hreflangLinks = buildHreflangLinks(
    pathWithoutLocale,
    SUPPORTED_LOCALES,
    BASE_URL,
    DEFAULT_LOCALE,
  );

  return [{ title: "About Us" }, ...hreflangLinks];
}

이렇게 하면 /en/about, /fr/about, /de/about가 모두 동일한 기본 콘텐츠에 대한 올바른 언어별 URL을 가리키는 hreflang 링크를 생성합니다.