# hreflang and Multilingual SEO: Why Your Translated Pages Are Not Ranking

> Translating your site does not automatically get you traffic. Learn how to pick a URL structure, implement hreflang correctly, avoid duplicate-content traps, and make Google serve the right language to the right user.
- **Author**: Mohit Sapat
- **Published**: 2026-09-22
- **URL**: https://www.babelize.co/blogs/hreflang-multilingual-seo-guide

---

<p class="lead">
    Translating your site does not automatically get you traffic. Learn how to pick a URL structure, implement hreflang correctly, avoid duplicate-content traps, and make search engines serve the right language to the right user.
</p>

<h2>The Most Expensive Localization Mistake</h2>

<p>
    A team spends three months translating their marketing site into eight languages. They launch. Six months later, organic traffic from Germany, Japan, and Brazil is roughly unchanged, and the English pages still outrank the translated ones for translated queries.
</p>

<p>
    The translations were fine. The <em>plumbing</em> was not. Search engines never learned that those pages existed, or they learned about them and decided they were duplicates of each other.
</p>

<p>
    Localization SEO is mostly three decisions: what your URLs look like, how you tell crawlers which page belongs to which audience, and how you avoid competing with yourself. Get those right and the translations do their job.
</p>

<h2>Decision 1: URL Structure</h2>

<p>
    Search engines need a distinct, crawlable URL per language. There are three viable patterns.
</p>

<table>
    <thead>
        <tr>
            <th>Pattern</th>
            <th>Example</th>
            <th>Best for</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Subdirectory</td>
            <td>example.com/de/pricing</td>
            <td>Almost everyone. Inherits domain authority.</td>
        </tr>
        <tr>
            <td>Subdomain</td>
            <td>de.example.com/pricing</td>
            <td>Separate infra or teams per region</td>
        </tr>
        <tr>
            <td>ccTLD</td>
            <td>example.de/pricing</td>
            <td>Strong local brand, legal or trust requirements</td>
        </tr>
    </tbody>
</table>

<p>
    <strong>Default to subdirectories.</strong> Every localized page benefits from the authority your main domain has already accumulated, there is one SSL certificate, one analytics property, and one deployment. ccTLDs give the strongest geo-signal but mean starting each country's domain authority from zero, plus buying and defending a dozen domains.
</p>

<p>
    What is <em>not</em> viable: a query parameter (<code>?lang=de</code>) or, worse, switching language purely in client-side state with no URL change at all. If the German version of a page has no unique URL, it cannot be indexed, linked to, or shared.
</p>

<h3>Language vs. region</h3>

<p>
    Use <code>/de/</code> when the content is the same for all German speakers. Use <code>/de-at/</code> and <code>/de-ch/</code> only when the content genuinely differs -- different pricing, currency, legal terms, or shipping. Splitting a locale you do not actually differentiate just divides your own ranking signals across near-identical pages for no benefit. If you are unsure which codes to use, our <a href="/blogs/locale-codes-bcp-47-explained/">guide to locale codes and BCP 47</a> covers the common traps, such as <code>en-UK</code> instead of <code>en-GB</code>.
</p>

<h2>Decision 2: hreflang Annotations</h2>

<p>
    <code>hreflang</code> is how you tell Google "these pages are the same content in different languages -- show each user the right one." Without it, your German and Austrian pages look like duplicates and Google picks one, often the wrong one.
</p>

<p>
    A correct annotation set on the English pricing page looks like this:
</p>

<pre><code>&lt;link rel="alternate" hreflang="en" href="https://example.com/pricing" /&gt;
&lt;link rel="alternate" hreflang="de" href="https://example.com/de/pricing" /&gt;
&lt;link rel="alternate" hreflang="ja" href="https://example.com/ja/pricing" /&gt;
&lt;link rel="alternate" hreflang="pt-br" href="https://example.com/pt-br/pricing" /&gt;
&lt;link rel="alternate" hreflang="x-default" href="https://example.com/pricing" /&gt;</code></pre>

<p>
    Four rules decide whether this works or is silently ignored:
</p>

<ul>
    <li><strong>Reciprocity.</strong> Every page in the set must list every other page, including itself. If <code>/de/pricing</code> does not point back at <code>/pricing</code>, Google discards the whole annotation as unconfirmed.</li>
    <li><strong>Absolute URLs.</strong> Relative hrefs are not valid here.</li>
    <li><strong>Correct codes.</strong> Language is <a href="https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes" target="_blank" rel="noopener noreferrer">ISO 639-1</a>, optional region is ISO 3166-1 Alpha-2. <code>pt-br</code> is valid. <code>pt-BR</code> is accepted case-insensitively. <code>br</code> alone means Breton, not Brazil -- a genuinely common bug.</li>
    <li><strong>x-default.</strong> The fallback served to users whose language you do not support. Usually your English page or a language selector.</li>
</ul>

<h3>Doing it in Next.js</h3>

<p>
    In the App Router, generate the set from your locale list rather than hand-writing it (this assumes locale-prefixed routing like the one in our <a href="/blogs/implement-i18n-nextjs-next-intl/">next-intl setup guide</a>):
</p>

<pre><code>export async function generateMetadata({ params }) {
  const { locale, slug } = await params;
  const path = `/${slug}`;

  return {
    alternates: {
      canonical: `https://example.com/${locale}${path}`,
      languages: Object.fromEntries([
        ...locales.map((l) =&gt; [l, `https://example.com/${l}${path}`]),
        ["x-default", `https://example.com/en${path}`],
      ]),
    },
  };
}</code></pre>

<p>
    Generating from a single source list is what keeps the set reciprocal. Hand-maintained hreflang blocks drift within about two sprints.
</p>

<h2>Decision 3: Canonicals</h2>

<p>
    This is the single most common way teams destroy their own multilingual SEO:
</p>

<pre><code>&lt;!-- On /de/pricing -- WRONG --&gt;
&lt;link rel="canonical" href="https://example.com/pricing" /&gt;</code></pre>

<p>
    That tag tells Google the German page is a duplicate of the English one and should not be indexed at all. Your translation is now invisible. Every localized page needs a <strong>self-referencing canonical</strong>:
</p>

<pre><code>&lt;!-- On /de/pricing -- correct --&gt;
&lt;link rel="canonical" href="https://example.com/de/pricing" /&gt;</code></pre>

<p>
    Canonical and hreflang answer different questions. Canonical says "this is the definitive URL for <em>this</em> content." hreflang says "here are the other language versions." They coexist; they do not substitute for each other.
</p>

<h2>Stop Auto-Redirecting by IP</h2>

<p>
    Detecting a visitor's country and forcibly redirecting them is intuitive and wrong for three reasons:
</p>

<ul>
    <li>Googlebot crawls predominantly from US IPs. If every non-English URL redirects a US visitor to English, large parts of your site never get indexed.</li>
    <li>Country is not language. Plenty of people in Germany want the English docs. Plenty of people in the US read Spanish.</li>
    <li>Hard redirects trap users. If the German page redirects you to English and the switcher sends you back, you get a loop.</li>
</ul>

<p>
    The safer pattern: detect once, suggest rather than force, and remember the choice. Use the <code>Accept-Language</code> header for a first-visit default on the root path only, show a dismissible banner ("Read this in Deutsch?"), and persist the user's explicit choice in a cookie. Never redirect a crawler, and never redirect a user who arrived on a specific localized URL.
</p>

<h2>Things That Are Easy to Forget</h2>

<h3>The html lang attribute</h3>

<pre><code>&lt;html lang="de"&gt;</code></pre>

<p>
    Trivially small, and it drives screen-reader pronunciation, browser translation prompts, and correct hyphenation. Set it from the active locale, not statically.
</p>

<h3>Localized sitemaps</h3>

<p>
    List every locale URL in your sitemap, and include the <code>xhtml:link</code> alternate annotations there too. A sitemap is often how a new locale gets discovered at all:
</p>

<pre><code>&lt;url&gt;
  &lt;loc&gt;https://example.com/pricing&lt;/loc&gt;
  &lt;xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/pricing"/&gt;
  &lt;xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/pricing"/&gt;
&lt;/url&gt;</code></pre>

<h3>Translated metadata, not just body copy</h3>

<p>
    Titles, meta descriptions, Open Graph tags, image alt text, and JSON-LD all need localizing. A German page with an English <code>&lt;title&gt;</code> ranks for nothing in German -- the title tag is still one of the strongest on-page signals you control.
</p>

<h3>Keywords do not translate</h3>

<p>
    This is where literal translation costs you the most. Germans search "Handy" for a mobile phone, not "Mobiltelefon." A direct translation of your best-performing English keyword is frequently a phrase nobody in that market types. Run keyword research per locale (and use it to decide <a href="/blogs/choose-your-first-saas-languages/">which languages to launch first</a>) and let your translators adjust headings accordingly -- a page that reads naturally and uses the phrases locals actually search will outperform a technically accurate translation every time.
</p>

<h3>Localized URL slugs</h3>

<p>
    <code>/de/preise</code> beats <code>/de/pricing</code> for both relevance and click-through. It costs you a slug mapping in your routing config. Decide early -- changing slugs later means redirects.
</p>

<h2>A Short Audit You Can Run Today</h2>

<ul>
    <li>Open a localized page and view source. Does it have a self-referencing canonical? Does it list every other locale in hreflang, including x-default?</li>
    <li>Open one of those alternate URLs. Does it point back? If not, the set is broken.</li>
    <li>Check <code>&lt;html lang&gt;</code> matches the page's actual language.</li>
    <li>Is the <code>&lt;title&gt;</code> translated? The meta description? The OG image alt?</li>
    <li>Fetch a localized URL with <code>curl</code> from a US IP. Do you get a 200, or a redirect to English?</li>
    <li>Search Console -> Indexing -> Pages: are your locale URLs indexed, or excluded as "Duplicate, Google chose different canonical"?</li>
</ul>

<p>
    SEO is only one part of a launch. Our <a href="/blogs/localization-checklist-for-developers/">localization checklist for developers</a> covers the rest of what to verify before a new locale goes live.
</p>

<h2>Frequently Asked Questions</h2>

<h3>What is hreflang and why does it matter?</h3>
<p>
    hreflang is an HTML annotation that tells search engines which language and region a page targets and where its alternate language versions live. Without it, translated pages look like near-duplicates and search engines may index only one of them.
</p>

<h3>Should I use subdirectories, subdomains, or ccTLDs for languages?</h3>
<p>
    Subdirectories such as <code>example.com/de/</code> are the safest default because every locale inherits the authority of your main domain and you maintain one deployment. Use ccTLDs only when you have a strong local brand or legal requirement, since each country domain starts its authority from zero.
</p>

<h3>What canonical tag should a translated page use?</h3>
<p>
    Every localized page needs a self-referencing canonical pointing at its own URL. Pointing the German page's canonical at the English version tells search engines the translation is a duplicate and it will not be indexed.
</p>

<h3>Should I redirect users to their language based on IP address?</h3>
<p>
    No. Search crawlers mostly request pages from US IP addresses, so IP-based redirects can prevent localized pages from being indexed at all. Suggest a language with a dismissible banner, remember the user's explicit choice, and never redirect a user who landed on a specific localized URL.
</p>

<h3>Do I need to translate meta titles and descriptions?</h3>
<p>
    Yes. Titles, meta descriptions, Open Graph tags, image alt text, and structured data all need localizing. Keywords also should not be translated literally, since the phrase people actually search in a market is often different from a direct translation.
</p>

<h2>The Short Version</h2>

<p>
    Translation is the content problem. hreflang, canonicals, and URL structure are the discovery problem, and solving only the first one means paying for translations that nobody finds. Give every locale a real URL, wire the hreflang set reciprocally from a single source of truth, keep canonicals self-referencing, and suggest a language instead of forcing one.
</p>

<p>
    If keeping translated titles, descriptions, and metadata in sync across a growing set of locales is the part that keeps slipping, <a href="/">Babelize</a> handles it as part of your normal Git workflow so a new page ships localized metadata on day one rather than three sprints later.
</p>
---
- [All articles](https://www.babelize.co/blogs)