hreflang 구현 방법: 단계별 안내

HTML head 태그, HTTP Link 헤더와 XML 사이트맵 주석의 세 hreflang 방식 모두를 위한 코드, 문법 규칙, 대규모 CMS 자동화와 실제로 올바르게 배포되었는지 확인하는 검증 절차입니다.

이 페이지의 근거 신호 1개

hreflang 추가 방법은 정확히 세 가지입니다. head의 HTML link 태그, PDF 같은 비HTML 파일용 HTTP Link 응답 헤더, 대규모에 가장 좋은 XML 사이트맵의 xhtml:link 항목입니다. 하나를 선택하세요. Google은 동등하게 취급하며 결합해도 이점이 없습니다. 모두 자기 참조(각 페이지가 자신을 나열)와 상호 참조(A가 B를 가리키면 B도 A를 가리켜야 하며 아니면 그 쌍이 무시됨)의 두 규칙을 따릅니다. 코드는 ISO 639-1 언어에 선택적인 ISO 3166-1 alpha-2 지역을 더하며 x-default는 대체 경로입니다. 하나의 기준 데이터에서 생성하고 소스 보기만이 아닌 렌더링된 head를 검증하세요. 제 374,756개 도메인 연구에서 hreflang 설정의 67% 이상에 문제가 하나 이상 있었습니다.

TL;DR — 세 방식 중 하나를 고르세요. <head>의 HTML <link> 태그, PDF 같은 비HTML 파일의 유일한 선택지인 HTTP Link: 헤더, 또는 XML 사이트맵의 <xhtml:link> 항목입니다. 자동 생성 파일 하나로 QA하기 쉬운 사이트맵이 대규모에 가장 좋습니다. Google은 세 방식이 동등하고 결합해도 이점이 없다고 합니다. 모두 자기 참조 + 상호 참조, 절대 URL, ISO 639-1 언어 + 선택적 ISO 3166-1 alpha-2 지역 코드를 따릅니다. 수동 hreflang은 시간이 지나면 어긋나므로 하나의 기준 데이터에서 전체를 생성하세요. 이후 렌더링된 head를 검증하고 전체 클러스터의 상호 참조를 크롤링하며 URL이 바뀔 때마다 재검사하세요. 지역에 따라 크롤러를 자동 리디렉션하지 마세요.

코드를 쓰기 전 결정할 세 가지

1. 구현 방식. Google은 성능이 아닌 편의의 선택이라고 명시합니다. “The three methods are equivalent from Google’s perspective and you can choose the method that’s the most convenient for your site.” (번역) 「Google 관점에서 세 방식은 동등하므로 사이트에 가장 편리한 방식을 선택할 수 있습니다.」 제 대략적인 기준은 다음과 같습니다.

이 주장에 대한 근거 Google supports equivalent HTML, HTTP-header, and sitemap methods for declaring localized versions; HTTP headers can be used for non-HTML files such as PDFs. 범위: Google Search hreflang implementation methods. 신뢰도: 높음 · 검증일: Google: Localized versions
  • URL이 적고 정적이거나 단순한 사이트이며 로케일이 적음 → HTML <link> 태그.
  • 비HTML 자산(PDF, 문서) → HTTP Link: 헤더. PDF에는 <head>가 없으므로 다른 선택지가 없습니다.
  • 로케일이 많거나 사이트맵 파이프라인이 이미 있거나 headless/JAMstack 구성 → 프로그래밍 방식으로 생성하는 XML 사이트맵.

의사결정 트리 탭에서 이를 실제 흐름도로 살펴봅니다.

2. 하나의 기준 데이터. 어떤 방식이든 주석은 한곳에서 생성해야 합니다. 데이터베이스의 로케일/번역 테이블, CMS 관계 필드, 작은 사이트라면 스프레드시트 하나입니다. 최악의 방식은 페이지별로 태그를 수동 유지하는 것입니다. 한 로케일의 템플릿이 달라지는 순간 반환 링크가 빠지고 쌍이 제외됩니다.

3. 방식을 결합하지 마세요. 세 방식 모두 실행할 수는 있습니다. 하지만 Google은 이점이 없다고 하며 방식이 늘 때마다 세 사본이 서로 불일치할 지점도 늘어납니다.

이 주장에 대한 근거 Google requires fully qualified alternate URLs and reciprocal links, and recommends including each page itself in its alternate set. 범위: Google Search hreflang rules shared by all delivery methods. 신뢰도: 높음 · 검증일: Google: Hreflang guidelines

Google에 나온 그대로의 문법은 다음과 같습니다.

<link rel="alternate" hreflang="lang_code" href="url_of_page" />

대체 경로가 있는 다언어·다지역 클러스터의 완전한 예입니다.

<link rel="alternate" hreflang="en-us" href="https://example.com/us/" />
<link rel="alternate" hreflang="en-gb" href="https://example.com/uk/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

그 블록을 클러스터의 모든 페이지, 즉 미국·영국·독일어·홈페이지에 그대로 넣습니다. 각 페이지가 자신의 자기 참조 줄을 포함하며 이것이 상호 참조를 충족합니다.

배치 위치는 엄격한 규칙입니다. Google은 “The <link> tags must be inside a well-formed <head> section of the HTML.” (번역) 「<link> 태그는 올바르게 구성된 HTML <head> 안에 있어야 합니다.」라고 합니다. 문제 해결 조언도 “If in doubt, paste code from your rendered page into an HTML validator to ensure that the links are inside the <head> element.” (번역) 「확실하지 않다면 렌더링된 페이지의 코드를 HTML 검증기에 붙여 넣어 링크가 <head> 요소 안에 있는지 확인하세요.」입니다.

대부분의 가이드가 빠뜨리는 실패 유형 때문에 생각보다 중요합니다. hreflang 태그가 <head>에서 밀려나 <body> 안에 들어가면 유효하지 않습니다. 저는 Pubcon Vegas 2019 발표 에서 이를 직접 지적했습니다. 태그가 body에 유효하게 존재할 수 있다면 다른 사이트의 대체 버전을 탈취할 수 있습니다. 잘못되거나 삽입된 <p> 태그 또는 iframe이 <head>를 조기에 닫으면 이후 hreflang을 포함한 모든 내용이 <body> 안에 렌더링됩니다. 소스에는 태그가 있지만 조용히 무효화됩니다. 가장 빠른 디버깅은 브라우저 DOM 중단점입니다. 렌더링된 DOM에서 태그가 실제 어디에 놓이는지 보고 <head>를 깨뜨린 마크업을 역추적하세요.

HTML 태그가 적합한 경우는 로케일 수를 관리할 수 있고 페이지별 <link> 블록이 커지지 않는 중소 사이트입니다. 로케일이 수십 개면 모든 페이지에 큰 마크업 블록이 붙습니다. 대형 사이트가 사이트맵 방식을 선호하는 이유 중 하나입니다.

같은 <link> 요소에서 hreflang과 다른 alternate 속성을 혼용하지 마세요. Google 지침은 hreflang을 담은 <link rel="alternate">에 media처럼 무관한 대체 속성을 같이 넣지 말라고 명시합니다. 같은 URL에 언어/지역 대체 버전과 미디어 쿼리 대체 버전이 모두 필요하다면 하나로 결합하지 말고 별도의 <link> 요소 두 개를 사용하세요. 섞으면 Google이 어느 쪽으로도 파싱하지 못하는 태그가 되기 쉽습니다.

같은 정보를 HTML 대신 HTTP 응답으로 보냅니다. 문법은 다음과 같습니다.

Link: <https://example.com/file.pdf>; rel="alternate"; hreflang="en",
      <https://de-ch.example.com/file.pdf>; rel="alternate"; hreflang="de-ch"

각 URL의 꺾쇠 괄호, 세미콜론으로 구분된 속성, 대체 버전 사이의 쉼표에 주의하세요. 헤더의 모든 URL은 쉼표로 이어 붙입니다.

필요한 경우: 비HTML 리소스입니다. PDF, .doc, 직접 제공하는 이미지에는 <link> 태그를 담을 <head>가 없으므로 hreflang을 붙이는 유일한 방법은 헤더입니다.

설정 시 고려 사항: 헤더는 서버/CDN 계층에서 설정합니다. Apache Header 지시어, Nginx add_header, Cloudflare/Fastly/CloudFront 응답 헤더 규칙 또는 애플리케이션 응답입니다. 문서별 마크업이 아닌 인프라에서 설정하므로 상호 참조가 깨지기 쉽습니다. 영어 PDF와 독일어 PDF 헤더는 보통 별도로 설정하므로 각각 자신을 포함한 전체를 나열하는지 직접 확인해야 합니다. HTML/사이트맵 방식과 같은 로케일 테이블에서 헤더를 생성하세요.

유지할 불변 조건: 모든 대체 버전 응답은 매번 동일한 전체 집합을 담아야 합니다. 영어 PDF 헤더가 독일어 버전을 나열하는 것만으로는 부족합니다. 독일어 PDF 응답도 자신과 다른 모든 대체 버전의 정확히 같은 집합을 반환해야 합니다. 크롤러가 처음 접하는 응답만이 아니라 매번 그래야 합니다. 헤더는 한 번 설정하고 잊는 것이 아니라 생성 산출물로 취급하세요.

대규모에서는 보통 올바른 선택입니다. 클러스터 전체가 자동 생성 파일 하나 또는 몇 개에 있고 각 페이지의 <head>를 부풀리지 않습니다. 관계 그래프 전체가 한곳에 있으므로 QA하기도 가장 쉽습니다. 문법은 다음과 같습니다.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
  xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://www.example.com/english/page.html</loc>
    <xhtml:link rel="alternate" hreflang="de"
                href="https://www.example.de/deutsch/page.html"/>
    <xhtml:link rel="alternate" hreflang="en"
                href="https://www.example.com/english/page.html"/>
  </url>
  <url>
    <loc>https://www.example.de/deutsch/page.html</loc>
    <xhtml:link rel="alternate" hreflang="de"
                href="https://www.example.de/deutsch/page.html"/>
    <xhtml:link rel="alternate" hreflang="en"
                href="https://www.example.com/english/page.html"/>
  </url>
</urlset>

사이트맵 방식에서 실수하는 두 가지:

  1. 네임스페이스 선언은 필수입니다. <urlset>의 xmlns:xhtml="http://www.w3.org/1999/xhtml"은 선택적인 장식이 아닙니다. 빠지면 파일의 모든 <xhtml:link>가 유효하지 않습니다.
  2. 각 <url> 블록은 완결되어야 합니다. 위의 두 <url> 블록 모두 자신의 <loc>를 포함해 두 대체 버전을 나열합니다. 각 항목은 자기 참조와 모든 대체 버전의 전체 집합을 담습니다. 사이트맵의 상호 참조는 “각 URL 블록이 자신을 포함한 클러스터의 모든 URL을 나열한다”는 뜻입니다.

생성기를 과하게 설계하지 않도록 두 동작을 알아 두세요. <url> 안의 <xhtml:link> 자식 요소 순서는 Google에 중요하지 않으므로 정렬에 시간을 낭비하지 마세요. 또 이 주석들은 독립된 <url> 항목이 아니라 자식이므로 파일당 50,000개 URL 제한에 포함되지 않습니다.

프로그래밍 방식의 생성. 사이트맵 방식의 핵심은 CMS 번역 데이터의 부산물이라는 점입니다. CMS가 /english/page.html과 /deutsch/page.html의 번역 관계를 안다면 생성기가 그 관계 테이블을 순회해 빌드 또는 사이트맵 요청 시 <xhtml:link> 블록을 출력해야 합니다. 매번 기준 데이터에서 재생성하므로 hreflang이 실제 상황과 어긋날 수 없고, 로케일 추가도 수백 페이지의 수작업이 아닌 데이터 변경이 됩니다.

개발 인력이 없는 팀을 위해 제 Ahrefs hreflang 가이드에 간단한 Google Sheets 템플릿 을 만들었습니다. Setup 탭에서 기본 언어와 최대 네 변형을 선택하고, URLs 탭의 열에 각 언어 URL을 붙여 넣으면 Results 탭이 사이트맵 XML 블록을 자동 생성합니다. 실제 CMS 통합이 과한 작은 사이트를 위한 하나의 기준 데이터 원칙의 구체적 구현입니다.

어떤 방식이든 적용되는 규칙

방식과 무관하며 타협할 수 없는 규칙입니다.

  • 자기 참조. 모든 페이지, <url> 블록 또는 헤더는 자신을 나열합니다. Mueller는 선택적이지만 좋은 관행이라고 설명합니다. 제 연구에서는 18.0%가 빠뜨렸지만 자동화하면 비용이 들지 않습니다.
  • 상호 참조. “Each language version must list itself as well as all other language versions.” (번역) 「각 언어 버전은 자신과 다른 모든 언어 버전을 나열해야 합니다.」 적용 규칙은 “If two pages don’t both point to each other, the tags will be ignored. This is so that someone on another site can’t arbitrarily create a tag naming itself as an alternative version of one of your pages.” (번역) 「두 페이지가 서로를 가리키지 않으면 태그는 무시됩니다. 다른 사이트의 누군가가 임의로 자신을 여러분 페이지의 대체 버전으로 지정하지 못하게 하기 위해서입니다.」 반환 링크 하나가 없으면 그 쌍이 제외됩니다.
  • 완전한 절대 URL. “Alternate URLs must be fully-qualified, including the transport method (http/https)” (번역) 「대체 URL은 전송 방식(http/https)을 포함한 완전한 형식이어야 합니다.」 따라서 https://example.com/foo이며 //example.com/foo나 /foo가 아닙니다. Google이 색인하는 정확한 형식과 프로토콜·www·끝 슬래시·대소문자가 모두 맞아야 반환 링크가 일치합니다.

정확한 코드 사용

“The first code of the hreflang attribute is the language code (in ISO 639-1 format) followed by an optional second code that represents the region code (in ISO 3166-1 Alpha 2 format).” (번역) 「hreflang 속성의 첫 코드는 ISO 639-1 언어 코드이며, 선택적인 두 번째 코드는 ISO 3166-1 Alpha 2 지역 코드입니다.」 en-US처럼 하이픈으로 결합합니다. 언어만 타기팅할 수는 있지만(es = 전 세계 스페인어) 지역만 타기팅할 수는 없습니다. 항상 언어가 먼저입니다.

제가 가장 자주 보고 연구 데이터에서도 발견한 실수는 다음과 같습니다.

  • en-GB 대신 en-UK — uk는 우크라이나어이고 영국 지역 코드는 gb입니다.
  • 일본어 ja 대신 jp, 중국어 zh 대신 cn.
  • 두 글자 ISO 639-1이 필요한 곳에 세 글자 코드(ger, eng) 사용.
  • EU, UN, UK를 지역 코드로 사용 — 유효한 ISO 3166-1 alpha-2 대상이 아닙니다.

**x-default**는 명시된 어느 로케일에도 맞지 않는 사용자를 위한 대체 경로의 예약 값입니다. 언어 선택기나 자동 리디렉션 홈페이지가 그 예입니다.

<link rel="alternate" href="https://example.com/" hreflang="x-default" />

필수는 아닙니다. 제 연구에서 56.3%가 빠뜨린 가장 흔한 누락 항목이지만 x-default 누락은 상호 태그 누락처럼 클러스터를 깨뜨리지 않습니다. 일치하지 않는 사용자에게 Google이 자체 언어/지역 감지를 사용합니다. 그래도 추가하세요. 클러스터를 깨뜨리는 요소가 아닌 점검 항목입니다. 이 클러스터에는 별도의 x-default 세부 주제가 있습니다.

대규모 구현: CMS와 플랫폼 접근법

WordPress. Yoast SEO는 단독으로 hreflang을 생성하지 않습니다. 실제 태그 출력에는 WPML이나 Polylang 같은 다국어 플러그인이 필요합니다. WPML은 번역이 있는 모든 페이지의 hreflang을 자동 생성하고 기본 언어 버전의 x-default를 추가하며 기본적으로 XML 사이트맵에 주석을 삽입합니다. 사이트맵 대신 또는 추가로 head 태그를 출력하려면 WPML → Languages → SEO Options의 “Display alternative languages in the HEAD section” (번역) 「HEAD 섹션에 대체 언어 표시」 설정을 사용합니다. WPML의 Yoast 연동 문서 를 보세요.

Shopify. Shopify Markets는 시장/언어를 설정하고 콘텐츠를 게시해 탐색 메뉴에 연결하면 상호 hreflang 태그를 자동 생성합니다. 설계상 가장 흔한 반환 링크 누락을 없앱니다. 다만 자동 태그는 fr-FR, fr-CA 같은 언어·지역이 아니라 fr, de 같은 언어 전용인 경우가 많아 프랑스·캐나다·벨기에의 프랑스어를 구별해야 하는 브랜드에는 부족합니다. 이 경우 theme.liquid의 수동 <link> 태그나 앱을 사용합니다. Shopify의 테마 hreflang 문서 에 패턴이 있습니다. 자동 Markets 태그와 수동/앱 태그의 혼용은 문서화된 충돌 원인이므로 하나만 선택하세요.

맞춤형 / headless / 엔터프라이즈. 위 사이트맵 설명처럼 빌드 또는 응답 시 번역 관계 테이블에서 주석을 생성하세요. 같은 하나의 기준 데이터 원칙입니다. hreflang은 시간이 지나면 어긋나는 수동 산출물이 아니라 CMS에 이미 있는 데이터의 계산 결과가 됩니다.

클러스터를 깨뜨리는 구현 실수

지역/IP 자동 리디렉션. 문법 오류와 별개이며 더 나쁜 실패입니다. 추정 위치에 따라 방문자, 특히 크롤러를 리디렉션하면 Googlebot이 주로 미국에서 크롤링하므로 지역 클러스터 전체가 색인에서 빠질 수 있습니다. 제 Pubcon 발표에서는 “would redirect search engines to where they crawl from. Google for instance mostly crawls from the US so we would effectively de-index all geo pages.” (번역) 「검색엔진을 크롤링 출발지로 리디렉션할 것입니다. Google은 주로 미국에서 크롤링하므로 사실상 모든 지역 페이지의 색인을 제거하게 됩니다.」라고 했습니다. EU의 부당한 지역 차단 금지 규정에 따른 노출 위험이라는 규제 측면도 있습니다. 올바른 패턴은 사용자는 리디렉션하되 크롤러는 하지 않는 것입니다. 사람을 감지해 선택적으로 이동시키되 봇은 모든 URL에 직접 접근하게 하고 실제 연결 신호는 hreflang에 맡깁니다. Google의 다지역 지침 도 이런 이유로 자동 이동보다 방해하지 않는 제안 배너를 권장합니다.

비canonical·리디렉션·noindex URL을 hreflang 대상으로 지정. 로케일 URL이 바뀌어 리디렉션을 걸었는데 hreflang은 이전 주소라면 클러스터가 301 또는 404를 참조하게 됩니다. 제 연구의 16.9%가 깨지거나 리디렉션된 페이지를 참조했습니다. 각 버전은 자신을 canonical로 지정해야 합니다. 다른 곳을 canonical로 지정하거나 noindex인 페이지를 대상으로 하면 반환 링크가 깨집니다. 8.0%는 비canonical URL을 가리켰습니다.

URL 형식 불일치. hreflang URL과 색인된 URL은 끝 슬래시·www·프로토콜·대소문자까지 바이트 단위로 같아야 합니다. 불일치는 조용한 상호 참조 실패입니다.

출시 후 구현 검증

원시 소스만이 아닌 렌더링된 소스를 보세요. 클라이언트 JavaScript로 넣은 hreflang은 curl이나 Ctrl+U(“페이지 소스 보기”)에는 아무것도 없지만 렌더링된 DOM에는 있습니다. GSC URL 검사 → “Test Live URL” (번역) 「실제 URL 테스트」 → “View Tested Page” (번역) 「테스트된 페이지 보기」 또는 렌더링 크롤러로 확인하세요. 실제로는 정상인데 “태그가 없다”고 생각하거나, 반대로 소스에는 있지만 <body>에 렌더링되어 깨진 것을 놓치는 가장 흔한 이유입니다.

URL 하나만 표본 검사하지 말고 클러스터 전체를 크롤링하세요. 상호 참조는 페이지 간 관계이므로 한 페이지는 거의 아무것도 알려 주지 못합니다. Screaming Frog의 hreflang 감사 나 Ahrefs Site Audit으로 전체를 크롤링해 반환 누락, 비canonical 대상과 깨진 참조를 대규모로 찾으세요. Ahrefs의 Hreflangs 탭은 깨진 링크를 빨갛게 표시하는 클러스터 그래프를 그려 CSV보다 이해하기 쉽습니다.

&hl=와 &gl=로 실제 SERP를 검증하세요. Google 검색 URL에 호스트 언어(&hl=)와 지리 위치(&gl=) 매개변수를 붙여 자신의 위치에서 추측하는 대신 해당 로케일 결과를 미리 보세요.

검증은 일회성 단계가 아닙니다. 새 로케일, URL/리디렉션 변경과 개발·스테이징 설정의 운영 유입이 출시 후 장애의 반복 원인입니다. 출시일 QA뿐 아니라 회귀 테스트와 정기 크롤링에 hreflang 검사를 넣으세요. 제 발표의 표현은 “any number of things can break from masking to things carrying over from dev/test/staging environments.” (번역) 「마스킹부터 개발/테스트/스테이징 환경에서 넘어오는 것까지 수많은 원인으로 문제가 생길 수 있습니다.」입니다.

버려야 할 오해

  • “Sitemaps process faster than HTML tags.” (번역) 「사이트맵이 HTML 태그보다 빨리 처리된다.」 틀렸습니다. 둘 다 크롤링 시 처리된다고 제가 직접 반박했습니다. 사이트맵의 이점은 속도가 아닌 관리와 QA입니다.
  • “Yandex doesn’t support hreflang in sitemaps.” (번역) 「Yandex는 사이트맵 hreflang을 지원하지 않는다.」 틀렸습니다. Yandex 문서는 지원을 확인합니다.
  • “Use all three methods for extra signal.” (번역) 「추가 신호를 위해 세 방식 모두 사용하라.」 Google에 따르면 이점이 없고 불일치 위험만 늘립니다.
  • “A missing x-default breaks the cluster.” (번역) 「x-default 누락은 클러스터를 깨뜨린다.」 틀렸습니다. 선택 사항이며 Google은 자체 감지를 사용합니다.
  • “Wrong hreflang gets you penalized.” (번역) 「잘못된 hreflang은 페널티를 받는다.」 틀렸습니다. 명령이 아닌 힌트입니다. 깨진 hreflang은 페널티가 아니라 무시됩니다.

다음으로 볼 곳

이 글은 hreflang 허브 아래의 상세 구현 가이드입니다. 허브는 개념, 상호 참조의 중요성과 Bing이 대신 하는 일을 다룹니다. x-default는 대체 경로 값을 자세히 설명합니다. 구현의 바탕 전략은 국제 SEO 영역을 보세요. hreflang은 기술 계층이지 진정한 현지화를 대신하지 않습니다.

전문가 메모 추가

전문가 인용문 고정

새로운 사람인가요? 미등록 프로필을 다음 위치에서 /admin/experts/ → 전문가 인용문 고정 먼저 만드세요.