# International sites: subfolder vs subdomain vs ccTLD, by the numbers

> Choose between example.com/de/, de.example.com and example.de with a trade-off table, the signals Google really uses for country targeting, and the hreflang rules that hold on all three.

Updated 2026-09-25 · Technical SEO · HTML version: https://getreport.app/guides/international-sites-subfolder-vs-subdomain-vs-cctld

The structure question comes up once, before the second language exists, and it is expensive to change later. This guide puts the three options side by side with what each costs in hosting, authority and admin, says which signals Google really uses to decide who sees which version, and then covers the part that is the same whatever you choose: hreflang, canonicals and the checks that prove they work. Reading it takes ten minutes; the decision usually follows from your answer to one question at the end of the table.

## Quick answer

| Structure | Example | Authority | Setup and running cost | Country signal |
| --- | --- | --- | --- | --- |
| Subfolder | `example.com/de/` | One domain, shared | One host, one certificate, one CMS; easiest | Only through hreflang |
| Subdomain | `de.example.com` | Treated much like a separate site in practice | Separate hosting possible; DNS and certificates per host | Only through hreflang |
| ccTLD | `example.de` | Separate per domain, built from zero | A domain, a registration, often a legal presence per country | Strongest: the TLD itself |

- A small site translating its content: **subfolders**.
- A company with national teams, separate stock, prices or legal entities: **ccTLDs**, or subfolders per country if the marketing is central.
- SaaS and content sites selling the same thing everywhere: **subfolders**.
- Whatever the structure, every version needs a self-referencing canonical and a complete hreflang set with return links. Run the [hreflang checker](https://getreport.app/tools/hreflang-checker) on one page per template.

## Why the structure matters

Three things depend on it. **Authority**: links to `example.com/de/` count for the whole domain, links to `example.de` count for `example.de` alone, and a new ccTLD starts with no history at all. **Cost and admin**: one domain means one certificate, one CMS, one deployment; a ccTLD per market means a registration each, sometimes a local address to qualify for it, and a separate site to keep patched. **Targeting**: a ccTLD tells Google the country without further work, while subfolders and subdomains tell it nothing until hreflang does.

The mistakes are symmetrical. A company that runs eight ccTLDs for eight small markets spends years building eight reputations for one product. A company that stuffs six countries with different prices, currencies and legal terms into one domain without hreflang shows Austrians German prices and Swiss visitors euros.

## What Google uses to decide the country

Google's documentation on [managing multi-regional and multilingual sites](https://developers.google.com/search/docs/crawling-indexing/managing-multi-regional-sites) lists the signals in this order: the country-code top-level domain, hreflang annotations, server location, and other signals such as local addresses and phone numbers, the currency, and links from local sites. Two of those need a comment.

**Server location** is listed as a weak signal that Google tries not to rely on, because content delivery networks put servers everywhere. In practice it decides nothing; host wherever is fastest for your visitors.

**Search Console's International Targeting report**, where you could once set a country for a `.com`, was removed in 2022. There is no longer a switch; the signals above are all there is. If you read a guide that tells you to "set the geographic target in Search Console", it is out of date.

Currency, addresses and the language of the page itself are signals you control on every structure. A `/de/` folder with prices in CHF, a Zurich address and `lang="de-CH"` is targeted at Switzerland as clearly as a `.ch` domain, once hreflang says so.

## Language versus country

`de` is a language. `de-AT` and `de-CH` are a language in a country. Most sites need language versions, not country versions: a German page serves Germany, Austria and Switzerland unless the prices, the laws, the delivery options or the vocabulary differ. When they do, you need a page per country, and that is a business decision (do we sell there, at what price, under whose terms) before it is a technical one.

The technical rule is simple: one page per combination that is actually different. Two identical pages tagged `de-DE` and `de-AT` are duplicate content wearing different badges; Google will fold them, and the fold may not go the way you wanted. If the content is the same, publish one page tagged `de`.

## How getReport checks it

> **Free tool:** [Hreflang checker](https://getreport.app/tools/hreflang-checker): Validate the hreflang tags of a page and fetch every alternate to confirm it links back. Invalid codes, missing self-reference, dead alternates and broken return links, in one table.

The checker reads every `<link rel="alternate" hreflang>` on the page, validates the codes and the set, then fetches up to 50 of the alternates (whatever domain they are on) and reads their own hreflang tags to confirm the return link. The table lists each alternate with its code, URL, HTTP status, whether it links back, and the `lang` attribute of its `<html>` element:

![The hreflang alternates table for a site with language and country versions: each row shows the code, the URL, a status chip, whether the alternate links back and its html lang value; one alternate is missing its return link](https://getreport.app/guides/img/international-sites-subfolder-vs-subdomain-vs-cctld/alternates.webp "Every alternate is fetched, on any domain: the return-link column is the contract each version must keep.")

> **Check: hreflang tags are complete and valid.** hreflang tells Google which language or country version to show each searcher. A missing self-reference or an invalid code makes the whole set ignored, so visitors land on the wrong version.
>
> 1. Use valid codes (language, optionally a region; for example "en", "en-GB", "de-AT") and at most one "x-default".
> 2. Include this page's own URL in its hreflang set, and list the same set on every version.

> **Check: Every hreflang alternate links back to this page.** hreflang only works when every version lists every other version, including itself. An alternate that does not point back makes Google ignore the pair, so visitors get the wrong language.
>
> 1. On each alternate page, add a <link rel="alternate" hreflang="…"> for this page and for itself.
> 2. Generate the tags from one list (CMS, plugin or the XML sitemap with xhtml:link) so every page carries the same set.

> **Check: The <html> element declares its language.** The lang attribute tells search engines and translation tools which language the page is in, and lets screen readers pick the right pronunciation.
>
> 1. Add lang to the html element, for example <html lang="hr"> or <html lang="en-GB">.
> 2. In WordPress the theme prints it from Settings → General → Site Language.

> **Check: Canonical tag is present.** Without a canonical URL, search engines may index duplicate versions of this page (with and without trailing slash, with UTM parameters) and split its ranking signals.
>
> 1. Add <link rel="canonical" href="https://your-site.com/page/"> in <head>.
> 2. In WordPress, Yoast or Rank Math add this automatically — check it is not disabled for this page.

Two details worth knowing. The self-reference counts as present if any entry in the set points at the page's own URL *or* at its canonical, so a page reached under a variant URL still passes when its canonical is in the set. And the return link is checked against the same pair: the alternate must list this page's URL or its canonical. That is exactly the leniency Google shows, and no more.

The `html lang` column is the fastest way to catch a structure mistake: a `de.example.com` row whose `html lang` reads `en` is a subdomain that was set up but never translated, and Google will notice the same thing.

## Step by step

### 1. Decide with the questions, not with a preference

Work down the list; the first "yes" usually settles it.

1. Do the countries have separate legal entities, stock, prices or teams that publish on their own schedule? → ccTLDs, or subfolders per country under one central CMS if marketing is central and the differences are a price list.
2. Is there an existing ccTLD with years of links and rankings? → Keep it; add hreflang to connect it to the rest.
3. Is it one product, one team, translated content? → Subfolders.
4. Do you need a separate technical stack for one market (a different CMS, a partner's platform)? → A subdomain for that market, subfolders for the rest, hreflang across both.

Subdomains are rarely the first choice. Google has said for years that it handles both, but subdomains get treated much like separate sites in practice, and they cost the DNS, certificate and deployment work of separate sites without the country signal of a ccTLD. They are the right answer when a market needs its own stack.

### 2. Put hreflang on every version, across whatever domains you use

[hreflang](https://getreport.app/learn/hreflang) works across subfolders, subdomains and domains alike; the set just lists full URLs. A German page on a ccTLD structure carries:

```html
<link rel="alternate" hreflang="de" href="https://example.de/schuhe/" />
<link rel="alternate" hreflang="de-AT" href="https://example.at/schuhe/" />
<link rel="alternate" hreflang="de-CH" href="https://example.ch/schuhe/" />
<link rel="alternate" hreflang="en" href="https://example.com/shoes/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
<link rel="canonical" href="https://example.de/schuhe/" />
```

The same five alternate lines appear on all four pages; only the canonical changes. `x-default` names the page for visitors whose language you do not serve: a language chooser, or the main market's page. [x-default explained](https://getreport.app/guides/hreflang-x-default-explained) covers when to use which.

For a site with many templates, generate the set from one routing table rather than typing it per page. In the XML sitemap the same set is written once per URL:

```xml
<url>
  <loc>https://example.de/schuhe/</loc>
  <xhtml:link rel="alternate" hreflang="de" href="https://example.de/schuhe/" />
  <xhtml:link rel="alternate" hreflang="de-AT" href="https://example.at/schuhe/" />
  <xhtml:link rel="alternate" hreflang="de-CH" href="https://example.ch/schuhe/" />
  <xhtml:link rel="alternate" hreflang="en" href="https://example.com/shoes/" />
  <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
</url>
```

With ccTLDs, each domain's sitemap can only list its own URLs as `<loc>`, but the `xhtml:link` entries may point anywhere; every domain must be verified in the same Search Console account for Google to use them. [hreflang in sitemaps vs in HTML](https://getreport.app/guides/hreflang-in-sitemaps-vs-in-html) weighs the two.

### 3. Keep every canonical on its own version

Each language or country page canonicalises to itself. A canonical from `example.at/schuhe/` to `example.de/schuhe/` tells Google to drop the Austrian page, and no amount of hreflang brings it back. The one exception is a true duplicate you do not want indexed separately, and then it should not be in the hreflang set either.

### 4. Set the `lang` attribute per version

`<html lang="de-AT">` on the Austrian page, `<html lang="en">` on the English one. Screen readers use the [lang attribute](https://getreport.app/learn/html-lang) for pronunciation; the checker's table shows it per alternate; a mismatch between `lang` and the hreflang code is the quickest sign that a version serves the wrong content. In WordPress the theme prints it from Settings → General → Site Language, and multilingual plugins set it per language.

### 5. Do not redirect by IP without a way out

A visitor in Austria opening `example.de` may want the German site; an Austrian in Germany, a traveller, or Googlebot (which mostly crawls from the United States) certainly do not want to be bounced elsewhere. Googlebot that is redirected on every request never sees the page it asked for, so the alternates it fetches for the return-link check are not the pages you tagged. Show a banner ("It looks like you are in Austria. Switch to example.at?") and remember the choice in a cookie; never a forced redirect. If you must redirect, exempt known crawlers and always keep every version reachable at its own URL.

### 6. Check one page per template

The home page, a category, a product or article, a contact page. Paste each into the checker and read the table: every row should show a 200 status, "yes" in the return-link column and the expected `html lang`. Then read the findings: valid codes, one `x-default`, self-reference present. [Hreflang return links](https://getreport.app/guides/hreflang-return-links) explains each failure.

## Moving between structures

Changing from ccTLDs to subfolders, or the other way, is a site migration per country: every URL on the old host gets a permanent redirect to its equivalent on the new one, the hreflang sets on every version are rewritten to the new URLs on the same day, the sitemaps are regenerated, and Search Console's Change of Address is filed for each domain that moves. Consolidating eight ccTLDs into one domain typically loses some rankings for months while Google re-evaluates and gains them back with interest once the authority is pooled; splitting a domain into ccTLDs loses more and gains it back slower. Plan it with the [site migration checklist](https://getreport.app/guides/site-migration-checklist-domain-https-cms), and do not do it for a speed or a "signal" reason alone.

## Speed for far-away markets

A ccTLD does not make a site faster; a server or an edge cache near the visitor does. One origin with a CDN in front serves Sydney from Sydney and São Paulo from São Paulo whatever the domain says, and for cached pages the time to first byte is the same on all three structures. Only uncached, personalised pages (a logged-in account, a cart) benefit from an origin in the region, and that is a hosting decision, not a domain one. [CDN basics](https://getreport.app/guides/cdn-basics-what-it-fixes-and-what-it-does-not) covers what the edge does and does not fix.

## Platform notes

### WordPress

Three routes. A **multisite network** gives each language its own site under a subfolder or subdomain (or a mapped domain), with separate plugins and themes to keep in step. **WPML** and **Polylang** run all languages in one install and let you choose the URL format: language in a folder, on a subdomain, or a different domain per language. The plugin writes the hreflang set and the `lang` attribute; you configure the mapping once. Which to pick, and the settings that break return links, are in [Multilingual WordPress: WPML, Polylang and hreflang](https://getreport.app/guides/multilingual-wordpress-wpml-polylang-and-hreflang).

### Shopify

Shopify Markets publishes each market under a subfolder (`example.com/en-de/`), a subdomain, or a separate domain you connect, and writes the hreflang tags itself. The choice is per market, so a shop can run subfolders for most markets and a ccTLD for one that already has authority.

### Webflow, Wix and Squarespace

Webflow Localization and Wix Multilingual put each language in a subfolder and generate hreflang; neither offers ccTLDs from inside the product. Squarespace has no built-in multilingual mode; the workable pattern is one site per language, which means separate domains or subdomains and hand-written hreflang in the code injection settings.

## Verify

- The hreflang checker shows every alternate with status 200, return link "yes" and the expected `html lang`, on every template.
- Every version's canonical points at itself; the [canonical checker](https://getreport.app/tools/canonical-checker) shows "self-referencing" on each.
- In Search Console, each property (folder, subdomain or domain) shows the intended versions in the performance report for the intended countries. If Germany keeps seeing the Austrian page, the return links are broken somewhere.
- No forced IP redirect: opening any version through a VPN in another country shows that version, with a banner at most.

## Common mistakes

- **Redirecting by IP with no way out.** Visitors and crawlers cannot reach the version they asked for. Suggest, do not force.
- **Machine translations left indexable.** Ten auto-translated languages of thin quality make the whole domain look thin. Translate what you can support; keep the rest unpublished or `noindex`.
- **Flags for languages.** A Swiss flag next to "Deutsch" tells a German visitor this is not for them. Name the language in its own script ("Deutsch", "Français"); use flags only for countries.
- **Mixed structures with no map.** `/de/` for German, `fr.example.com` for French and `example.it` for Italian, each set up by a different agency, with hreflang on none. Any mix works if one routing table generates every set.
- **A canonical to the source language.** The translated page disappears from search, and the team blames hreflang.
- **The same page tagged with several countries.** `de-DE`, `de-AT` and `de-CH` all pointing at one URL is allowed, but pointless if the page is identical; tag it `de` once.
