international-seo
Use this skill when optimizing websites for multiple countries or languages - hreflang tag implementation, URL structure strategy (ccTLD vs subdomain vs subdirectory), geo-targeting in Google Search Console, multilingual content strategy, and international site architecture. Triggers on multi-language sites, multi-region targeting, hreflang debugging, or expanding a site to new markets.
marketing seointernational-seohreflangmultilingualgeo-targetinglocalizationWhat is international-seo?
Use this skill when optimizing websites for multiple countries or languages - hreflang tag implementation, URL structure strategy (ccTLD vs subdomain vs subdirectory), geo-targeting in Google Search Console, multilingual content strategy, and international site architecture. Triggers on multi-language sites, multi-region targeting, hreflang debugging, or expanding a site to new markets.
international-seo
international-seo is a production-ready AI agent skill for claude-code, gemini-cli, openai-codex, and 1 more. Optimizing websites for multiple countries or languages - hreflang tag implementation, URL structure strategy (ccTLD vs subdomain vs subdirectory), geo-targeting in Google Search Console, multilingual content strategy, and international site architecture.
Quick Facts
| Field | Value |
|---|---|
| Category | marketing |
| Version | 0.1.0 |
| Platforms | claude-code, gemini-cli, openai-codex, mcp |
| License | MIT |
How to Install
- Make sure you have Node.js installed on your machine.
- Run the following command in your terminal:
npx skills add AbsolutelySkilled/AbsolutelySkilled --skill international-seo- The international-seo skill is now available in your AI coding agent (Claude Code, Gemini CLI, OpenAI Codex, etc.).
Overview
International SEO ensures search engines serve the right language or regional version of your content to the right users. It involves URL structure decisions, hreflang implementation, and content localization strategy that affects how Google treats multi-market websites. Getting these signals wrong causes duplicate content issues, wrong-language rankings, and cannibalization between regional variants. Done correctly, international SEO gives each market its own clear identity and ranking potential.
Tags
seo international-seo hreflang multilingual geo-targeting localization
Platforms
- claude-code
- gemini-cli
- openai-codex
- mcp
Related Skills
Pair international-seo with these complementary skills:
Frequently Asked Questions
What is international-seo?
Use this skill when optimizing websites for multiple countries or languages - hreflang tag implementation, URL structure strategy (ccTLD vs subdomain vs subdirectory), geo-targeting in Google Search Console, multilingual content strategy, and international site architecture. Triggers on multi-language sites, multi-region targeting, hreflang debugging, or expanding a site to new markets.
How do I install international-seo?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill international-seo in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support international-seo?
This skill works with claude-code, gemini-cli, openai-codex, mcp. Install it once and use it across any supported AI coding agent.
Maintainers
Generated from AbsolutelySkilled
SKILL.md
International SEO
International SEO ensures search engines serve the right language or regional version of your content to the right users. It involves URL structure decisions, hreflang implementation, and content localization strategy that affects how Google treats multi-market websites. Getting these signals wrong causes duplicate content issues, wrong-language rankings, and cannibalization between regional variants. Done correctly, international SEO gives each market its own clear identity and ranking potential.
When to use this skill
Trigger this skill when the user:
- Needs to implement hreflang tags for a multi-language or multi-region site
- Is choosing a URL structure (ccTLD, subdomain, or subdirectory) for international expansion
- Wants to configure geo-targeting in Google Search Console
- Is launching a multilingual site or adding a new language/region
- Has international duplicate content problems (same content indexed in multiple languages)
- Is expanding an existing site into new geographic markets
- Needs to debug hreflang errors (missing x-default, broken return tags, invalid codes)
Do NOT trigger this skill for:
- Single-language, single-region sites with no plans for international expansion
- General on-page SEO or technical SEO not related to international targeting
Key principles
Language and country targeting are different things -
hreflang="en"targets English speakers regardless of location.hreflang="en-GB"targets English speakers in the UK. Use language-only tags when content differs by language but not region; use language+region tags when content varies by country (pricing, currency, regulations).hreflang is a signal, not a directive - Google may ignore hreflang if it finds stronger contradicting signals (canonicals, internal links, server location). Treat it as a strong hint, not a guarantee. Pair it with consistent internal linking and correct canonical tags.
URL structure is an architecture decision with trade-offs - ccTLDs give the strongest geo-signal but require maintaining separate domains. Subdomains are flexible but split domain authority. Subdirectories are easiest to manage and consolidate authority but give weaker geo-signals. Choose based on budget, team capacity, and how distinct each market's content really is.
Translate AND localize - not just translate - Machine-translated content that retains the source culture (idioms, examples, currency, date formats) fails users and often fails search. Localization means adapting for the market, not just the language.
Every language version needs a bidirectional hreflang set - If page A has an hreflang pointing to page B, page B must have a matching hreflang pointing back to page A. Asymmetric hreflang is one of the most common implementation errors and causes Google to ignore the entire annotation set.
Core concepts
Language vs country targeting - ISO 639-1 language codes (en, fr, de) specify
language. ISO 3166-1 alpha-2 country codes (US, GB, FR) specify country. Combine
them as language-COUNTRY (e.g., en-US, fr-FR, pt-BR). Use language-only tags
for content that's the same across countries for that language; use language+country
only when content genuinely differs by market.
hreflang tag syntax - The rel="alternate" link element with an hreflang
attribute tells Google which URL serves which audience. Tags can appear in the HTML
<head>, HTTP response headers, or XML sitemaps. All three methods are equivalent;
choose based on your CMS and hosting setup.
x-default - The x-default hreflang value designates a fallback URL for users
whose language/region isn't explicitly targeted. This is typically your homepage or
a language-selector page. Every hreflang implementation must include an x-default tag
or Google may treat the annotation set as incomplete.
URL structure options - Three canonical approaches exist: ccTLD (example.de),
subdomain (de.example.com), subdirectory (example.com/de/). Each has distinct
trade-offs around domain authority, geo-signal strength, and operational complexity.
See references/url-structure-strategy.md for a full decision matrix.
Geo-targeting signals - Google uses multiple signals to determine regional relevance: ccTLD, Google Search Console geo-targeting setting, server IP location, hreflang tags, content language, local addresses and phone numbers, internal links. hreflang is the most precise signal for language+country combinations.
Content localization vs translation - Translation converts words between languages. Localization adapts the full user experience: currency, units, legal disclaimers, local references, cultural tone, and imagery. For SEO, localized content performs better because it matches local search intent and terminology.
International duplicate content - When two pages serve the same content in the
same language but for different regions (e.g., en-US and en-GB with 95% identical
text), Google may consolidate them and pick one arbitrarily. Use hreflang to tell
Google they're intentional variants, not duplicates.
Common tasks
Implement hreflang tags in HTML head
Add <link rel="alternate"> tags in the <head> of every page. Every page must
reference itself and all its variants, including x-default.
<head>
<!-- Self-referencing hreflang is required -->
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/pricing/" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/pricing/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/pricing/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/pricing/" />
<!-- x-default is required - points to language selector or most generic version -->
<link rel="alternate" hreflang="x-default" href="https://example.com/pricing/" />
</head>Key rules:
- Use absolute URLs, not relative paths
- Every listed page must have a reciprocal set pointing back to all others
- Language codes are case-insensitive but country codes are conventionally uppercase
- Include the current page in its own hreflang set (self-reference)
Implement hreflang in XML sitemap
For large sites, managing hreflang in HTML heads is error-prone. XML sitemaps are easier to generate programmatically and don't require touching every template.
<?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://example.com/en-us/pricing/</loc>
<xhtml:link rel="alternate" hreflang="en-US" href="https://example.com/en-us/pricing/"/>
<xhtml:link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/pricing/"/>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/pricing/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/pricing/"/>
</url>
<url>
<loc>https://example.com/en-gb/pricing/</loc>
<xhtml:link rel="alternate" hreflang="en-US" href="https://example.com/en-us/pricing/"/>
<xhtml:link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/pricing/"/>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/pricing/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/pricing/"/>
</url>
</urlset>Every URL entry in the sitemap must list the full hreflang group - not just its own tag.
Implement hreflang in Next.js
// app/[locale]/pricing/page.tsx
import { Metadata } from 'next'
type Props = { params: { locale: string } }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const baseUrl = 'https://example.com'
const locales = ['en-US', 'en-GB', 'de', 'fr']
const alternates: Record<string, string> = {}
for (const locale of locales) {
alternates[locale] = `${baseUrl}/${locale.toLowerCase()}/pricing/`
}
return {
alternates: {
canonical: `${baseUrl}/${params.locale}/pricing/`,
languages: {
...alternates,
'x-default': `${baseUrl}/pricing/`,
},
},
}
}Next.js 13+ renders these as <link rel="alternate"> tags automatically.
Choose URL structure
Use this decision matrix when selecting a URL strategy for international expansion:
| Factor | ccTLD (example.de) | Subdomain (de.example.com) | Subdirectory (example.com/de/) |
|---|---|---|---|
| Geo-signal strength | Strongest | Medium | Weak (relies on GSC setting) |
| Domain authority | Separate per domain | Partially shared | Fully consolidated |
| Cost | High (register each TLD) | Low | Low |
| Operational complexity | High (separate infra) | Medium | Low |
| CDN/hosting | Per-domain setup needed | Flexible | Easiest |
| Best for | Large, well-funded, market-committed | Flexible mid-size | Single-domain consolidation |
Recommendation for most teams: subdirectory unless you have dedicated country-level
marketing budgets and teams. See references/url-structure-strategy.md for migration
paths and server configuration.
Set up geo-targeting in Google Search Console
Geo-targeting in GSC is required for generic TLDs (.com, .io, .co) and subdomains.
It is NOT available for ccTLDs (they inherit targeting from the TLD).
Steps:
- Open Google Search Console and select the property (subdomain or subdirectory)
- Navigate to Settings > International Targeting
- Under "Country", select the target country from the dropdown
- Click Save
Important constraints:
- You can set one country target per Search Console property
- Subdirectory properties inherit the root domain property's setting by default
- This setting is a hint, not a hard gate - hreflang still takes precedence for language
- Remove the setting if the site serves a global audience (leave it blank)
Handle international duplicate content
When en-US and en-GB pages are nearly identical, use hreflang to declare them as
intentional variants rather than letting Google pick a canonical arbitrarily.
<!-- On en-US page -->
<link rel="canonical" href="https://example.com/en-us/page/" />
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/page/" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/page/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/page/" />
<!-- On en-GB page - canonical points to itself, not en-US -->
<link rel="canonical" href="https://example.com/en-gb/page/" />
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/page/" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/page/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/page/" />Never point both pages to the same canonical - that tells Google one of them is a duplicate to be suppressed, defeating the purpose of separate regional pages.
Debug hreflang errors
Common errors reported in Google Search Console under Enhancements > International Targeting:
| Error | Cause | Fix |
|---|---|---|
| Return tag missing | Page A references B but B doesn't reference A back | Add reciprocal tags to all referenced pages |
| Unknown language tag | Invalid ISO 639-1 or 3166-1 code used | Check codes against ISO lists; en-uk is wrong, use en-GB |
| No x-default | hreflang set exists but no x-default tag | Add hreflang="x-default" to a fallback URL |
| Multiple hreflang for same locale | Same language+country code appears twice on a page | Remove duplicate; keep only one tag per locale |
| HTTP errors on hreflang URLs | Linked pages return 4xx/5xx | Fix pages or update hreflang to point to live URLs |
Use the hreflang Testing Tool (hreflang.org) or Screaming Frog to audit at scale.
Anti-patterns / common mistakes
| Anti-pattern | Why it's wrong | What to do instead |
|---|---|---|
| Auto-redirect by IP/browser language | Hides content from Googlebot (which crawls from US IPs) - regional versions won't get indexed | Show all versions to all crawlers; use hreflang to signal preference, let users choose |
| Machine-translated content without review | Produces unnatural text that matches no real search queries, penalized by quality algorithms | Use professional or post-edited machine translation; localize beyond just words |
| Missing x-default | hreflang set treated as incomplete by Google; fallback users land on wrong-language page | Always include hreflang="x-default" pointing to a language-selector or default locale |
| Asymmetric hreflang | If A lists B but B doesn't list A, Google ignores the entire annotation set | Every page in a group must list ALL other pages in that group |
| Using wrong locale codes | en-UK, zh-CN (wrong capitalization), sp (not a valid ISO code) |
Use ISO 639-1 for language (en, zh, es) and ISO 3166-1 alpha-2 for country (GB, CN, ES) |
| Pointing hreflang to redirected or canonicalized URLs | Google may not follow the chain; annotations on redirected pages are ignored | Always use the final canonical URL in hreflang tags |
| One sitemap hreflang, one HTML hreflang | Mixed implementation creates conflicting signals | Choose one method and implement it consistently across the entire site |
Gotchas
Auto-redirecting users by IP or Accept-Language hides content from Googlebot - Googlebot crawls from US IP addresses. If your site redirects non-US IPs to regional variants, Googlebot sees only the English-US version and never indexes your German, French, or Spanish pages. Show all language versions to all crawlers without redirects; use hreflang to express the preference.
Hreflang in sitemap and HTML head simultaneously creates conflicting signals - If you implement hreflang in your XML sitemap and also have
<link rel="alternate">tags in the HTML head pointing to different URLs (e.g., the sitemap uses trailing slashes and the HTML doesn't), Google may discard the entire annotation set. Choose one implementation method and make it consistent across the entire site.Using the wrong canonical with regional variants tells Google to suppress one variant - On an
en-GBpage, if the canonical points to theen-USpage, Google treatsen-GBas a duplicate and eventually stops indexing it entirely. Each regional variant must have a canonical pointing to itself, paired with hreflang indicating it's an intentional variant.Subdirectory geo-targeting in GSC only applies to the root property, not subdirectories - Google Search Console geo-targeting settings apply to the property you configure, which for a root domain includes all subdirectories. You cannot set
example.com/de/to target Germany andexample.com/fr/to target France through GSC - that's what hreflang is for. GSC targeting is for the top-level property only.Machine-translated content with source-language URL slugs ranks poorly in target markets - Translating page content but keeping URL slugs in English (e.g.,
/de/how-to-use-software/) means the URL provides no language signal and local search queries don't match. Localize slugs along with content:/de/software-verwenden/performs better and avoids mixed-signal confusion.
References
For detailed content on specific topics, read the relevant file from references/:
references/hreflang-implementation.md- Complete hreflang guide: HTML/HTTP/sitemap syntax, valid codes, x-default usage, framework-specific implementation (Next.js, Nuxt), paginated content, and Search Console debugging. Load when implementing or auditing hreflang.references/url-structure-strategy.md- Detailed comparison of ccTLD vs subdomain vs subdirectory with SEO implications, domain authority consolidation, hosting and CDN considerations, server configuration (Apache/Nginx), and migration paths. Load when choosing or changing URL structure for international sites.
Only load a references file if the current task requires deep detail on that topic.
References
hreflang-implementation.md
Hreflang Implementation Guide
Complete reference for implementing hreflang across HTML, HTTP headers, and XML sitemaps. Covers valid language and region codes, x-default behavior, common mistakes, framework-specific patterns, and debugging workflows.
What hreflang does
The hreflang attribute tells Google which URL to serve to which language and/or
regional audience. It is a hint, not a directive - Google may override it when
conflicting signals (canonical tags, content language, internal links) point elsewhere.
hreflang solves two problems:
- Language targeting - Serve French users the French page, not the English page
- Regional variants - Distinguish
en-US(with USD pricing) fromen-GB(with GBP pricing)
It does NOT affect Bing or other search engines in the same way.
Valid language and region codes
Language codes (ISO 639-1)
Use two-letter lowercase codes for languages:
| Code | Language |
|---|---|
en |
English |
fr |
French |
de |
German |
es |
Spanish |
pt |
Portuguese |
it |
Italian |
ja |
Japanese |
ko |
Korean |
zh |
Chinese |
ar |
Arabic |
nl |
Dutch |
pl |
Polish |
ru |
Russian |
sv |
Swedish |
tr |
Turkish |
For Chinese, always specify the script variant - zh-Hans (Simplified) or zh-Hant
(Traditional) - rather than bare zh. Google accepts both BCP 47 and ISO 3166-1 for
country codes.
Country codes (ISO 3166-1 alpha-2)
Use two-letter UPPERCASE codes for countries:
| Code | Country |
|---|---|
US |
United States |
GB |
United Kingdom |
CA |
Canada |
AU |
Australia |
DE |
Germany |
FR |
France |
ES |
Spain |
MX |
Mexico |
BR |
Brazil |
JP |
Japan |
KR |
South Korea |
CN |
China |
IN |
India |
SG |
Singapore |
Common wrong codes
| Wrong | Correct | Why |
|---|---|---|
en-UK |
en-GB |
UK is not an ISO 3166-1 code |
sp |
es |
Spanish ISO code is es |
zh-CN capitalization |
zh-Hans or zh-Hans-CN |
Prefer script variant for Chinese |
no |
nb or nn |
Norwegian Bokmal (nb) and Nynorsk (nn) are the valid codes |
iw |
he |
Hebrew old code; use he |
Implementation method 1: HTML link tags
Place tags in the <head> of every page. Each page must list all its variants
including itself (self-reference) and x-default.
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>Pricing - Example</title>
<!-- Self-reference is required -->
<link rel="canonical" href="https://example.com/en-us/pricing/" />
<!-- Full hreflang set - every page in the group lists all others -->
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/pricing/" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/pricing/" />
<link rel="alternate" hreflang="en-AU" href="https://example.com/en-au/pricing/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/pricing/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/pricing/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/pricing/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/pricing/" />
</head>Absolute URLs only - never use relative paths in hreflang tags.
Implementation method 2: HTTP headers
For non-HTML files (PDFs, JSON feeds) or when you cannot modify HTML, use HTTP
Link response headers. Format mirrors the HTML link element.
HTTP/1.1 200 OK
Content-Type: application/pdf
Link: <https://example.com/en-us/report.pdf>; rel="alternate"; hreflang="en-US",
<https://example.com/de/report.pdf>; rel="alternate"; hreflang="de",
<https://example.com/report.pdf>; rel="alternate"; hreflang="x-default"Nginx example:
location ~ /en-us/(.+\.pdf)$ {
add_header Link '<https://example.com/en-us/$1>; rel="alternate"; hreflang="en-US", <https://example.com/de/$1>; rel="alternate"; hreflang="de", <https://example.com/$1>; rel="alternate"; hreflang="x-default"';
}Apache example (.htaccess):
<FilesMatch "\.pdf$">
Header add Link '<https://example.com/en-us/report.pdf>; rel="alternate"; hreflang="en-US"'
Header append Link '<https://example.com/de/report.pdf>; rel="alternate"; hreflang="de"'
</FilesMatch>Implementation method 3: XML sitemap
Best for large sites where modifying every HTML template is impractical. Requires
the xhtml namespace declaration.
<?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">
<!-- Group 1: /pricing/ page with all regional variants -->
<url>
<loc>https://example.com/en-us/pricing/</loc>
<lastmod>2025-01-15</lastmod>
<xhtml:link rel="alternate" hreflang="en-US" href="https://example.com/en-us/pricing/"/>
<xhtml:link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/pricing/"/>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/pricing/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/pricing/"/>
</url>
<url>
<loc>https://example.com/en-gb/pricing/</loc>
<lastmod>2025-01-15</lastmod>
<xhtml:link rel="alternate" hreflang="en-US" href="https://example.com/en-us/pricing/"/>
<xhtml:link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/pricing/"/>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/pricing/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/pricing/"/>
</url>
<url>
<loc>https://example.com/de/pricing/</loc>
<lastmod>2025-01-15</lastmod>
<xhtml:link rel="alternate" hreflang="en-US" href="https://example.com/en-us/pricing/"/>
<xhtml:link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/pricing/"/>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/pricing/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/pricing/"/>
</url>
</urlset>Important: Every <url> entry must list the FULL hreflang group - not just its own tag.
The sitemap approach requires all variants to appear in the same sitemap file.
x-default: when and how to use
x-default designates the fallback URL when no hreflang value matches the user's
language and country. It is required in every hreflang annotation set.
Appropriate x-default targets:
- Language/region selector page (
/choose-your-region/) - The most broadly applicable version (usually
enoren-US) - The homepage, if no language-specific versions exist for the path
<!-- Language selector as x-default -->
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
<!-- Default to en-US content -->
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/page/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en-us/page/" />x-default can point to the same URL as one of the language-targeted tags. That is valid and common.
Framework-specific implementation
Next.js 13+ (App Router)
// app/[locale]/page.tsx
import { Metadata } from 'next'
const locales = ['en-US', 'en-GB', 'de', 'fr', 'es'] as const
type Locale = typeof locales[number]
function buildHreflangAlternates(path: string) {
const base = 'https://example.com'
const languages: Record<string, string> = {}
for (const locale of locales) {
languages[locale] = `${base}/${locale.toLowerCase()}${path}`
}
return {
canonical: `${base}${path}`,
languages: {
...languages,
'x-default': `${base}${path}`,
},
}
}
export async function generateMetadata(): Promise<Metadata> {
return {
alternates: buildHreflangAlternates('/pricing/'),
}
}Next.js 12 (Pages Router)
// pages/[locale]/pricing.tsx
import Head from 'next/head'
const HreflangTags = ({ currentPath }: { currentPath: string }) => {
const base = 'https://example.com'
const variants = [
{ locale: 'en-US', path: `/en-us${currentPath}` },
{ locale: 'en-GB', path: `/en-gb${currentPath}` },
{ locale: 'de', path: `/de${currentPath}` },
]
return (
<Head>
{variants.map(({ locale, path }) => (
<link key={locale} rel="alternate" hreflang={locale} href={`${base}${path}`} />
))}
<link rel="alternate" hreflang="x-default" href={`${base}${currentPath}`} />
</Head>
)
}Nuxt 3
// composables/useHreflang.ts
export function useHreflang(path: string) {
const config = useRuntimeConfig()
const base = config.public.siteUrl
const locales = ['en-US', 'en-GB', 'de', 'fr']
useHead({
link: [
...locales.map(locale => ({
rel: 'alternate',
hreflang: locale,
href: `${base}/${locale.toLowerCase()}${path}`,
})),
{
rel: 'alternate',
hreflang: 'x-default',
href: `${base}${path}`,
},
],
})
}WordPress (programmatic via wp_head)
<?php
// functions.php - add to theme or plugin
function add_hreflang_tags() {
$current_url = get_permalink();
$polylang_translations = function_exists('pll_the_languages')
? pll_get_post_translations(get_the_ID())
: [];
foreach ($polylang_translations as $lang_code => $post_id) {
$url = get_permalink($post_id);
echo '<link rel="alternate" hreflang="' . esc_attr($lang_code) . '" href="' . esc_url($url) . '" />' . "\n";
}
// x-default
echo '<link rel="alternate" hreflang="x-default" href="' . esc_url(home_url('/')) . '" />' . "\n";
}
add_action('wp_head', 'add_hreflang_tags');hreflang for paginated content
When paginated content has language variants, hreflang applies to each paginated URL independently. Do not use rel=prev/next in combination with hreflang at the set level.
<!-- Page 2 of /de/blog/ -->
<link rel="canonical" href="https://example.com/de/blog/page/2/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/blog/page/2/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/blog/page/2/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/blog/page/2/" />Each paginated URL is its own distinct resource. The hreflang group for page 2 only includes page 2 variants - not the root blog page.
Debugging hreflang with Google Search Console
- Open Google Search Console
- Go to Enhancements > International Targeting
- Click the Language tab to see hreflang errors
Common error types
| Error message | Root cause | Resolution |
|---|---|---|
| "Return tag missing" | Page A lists page B, but B doesn't list A | Add the missing reciprocal tag to all referenced pages |
| "Unknown language tag" | Invalid ISO code (e.g., en-uk, sp) |
Replace with valid ISO 639-1 + ISO 3166-1 codes |
| "Multiple x-default" | More than one hreflang="x-default" on a page |
Remove duplicates; keep exactly one x-default per page |
| "No return tag from URL" | The linked URL returns a non-200 status | Fix the linked URL or update hreflang to point to a live URL |
Third-party tools
- Screaming Frog SEO Spider - Crawls site and extracts all hreflang tags; surfaces missing return tags, invalid codes, and broken URLs in bulk
- hreflang.org tester - Paste a URL to validate its hreflang implementation
- Google Rich Results Test - Can render a single page and confirm tags are in the DOM
- Sitebulb - Visual hreflang cluster maps showing which pages reference which
Quick checklist
Before deploying hreflang:
- Every page in a group lists all other pages in that group
- Every page is self-referenced in its own hreflang set
- x-default is present on every page in the group
- All URLs are absolute (https://...)
- Language codes use ISO 639-1 (lowercase)
- Country codes use ISO 3166-1 alpha-2 (uppercase)
- No broken URLs in hreflang attributes (all return 200)
- Canonical tags are consistent with hreflang targets
- Only one implementation method used per site (HTML OR sitemap OR HTTP headers)
url-structure-strategy.md
International URL Structure Strategy
Choosing between ccTLD, subdomain, and subdirectory is one of the most consequential decisions in international SEO. It affects domain authority, geo-signal strength, operational complexity, and how difficult it will be to migrate later. This guide covers the full trade-off analysis, server configuration, and migration strategy.
The three options
Option 1: Country-code top-level domain (ccTLD)
Each country gets its own registered top-level domain.
example.de (Germany)
example.fr (France)
example.co.uk (UK - .co.uk is common but .uk is also used)
example.com.au (Australia)
example.com.br (Brazil)Strongest geo-signal - A .de domain is inherently German to Google, without
any additional configuration. Google uses the ccTLD as a primary geo-targeting signal.
Option 2: Subdomain
Each country or language gets a subdomain of the main domain.
de.example.com
fr.example.com
en-gb.example.com
au.example.comMedium geo-signal - Google treats subdomains as potentially separate sites but can be told to associate them with a parent domain via Google Search Console.
Option 3: Subdirectory (subfolder)
All countries/languages live under the same domain with path-based separation.
example.com/de/
example.com/fr/
example.com/en-gb/
example.com/en-au/Weakest geo-signal without GSC - Requires explicit geo-targeting configuration in Google Search Console for each subdirectory property. However, benefits from full domain authority consolidation.
Decision matrix
| Factor | ccTLD | Subdomain | Subdirectory |
|---|---|---|---|
| Geo-signal (without GSC) | Strongest | Medium | Weakest |
| Geo-signal (with GSC + hreflang) | Strongest | Strong | Adequate |
| Domain authority consolidation | None (each domain is separate) | Partial | Full |
| New market launch speed | Slow (register, propagate, configure) | Medium | Fast |
| Cost per market | High (TLD registration, hosting per domain) | Medium | Low |
| Hosting flexibility | Each domain needs own setup | Can share or split | All on one server |
| CDN setup complexity | Per-domain | Moderate | Single origin |
| Team complexity | High (separate deployments) | Medium | Low |
| Link equity isolation | Yes (links to .de don't help .com) | Partial | No (links flow to root) |
| Local trust signals | High (local users trust local TLD) | Medium | Low |
| Best for | Enterprise, committed local markets | Flexible mid-size expansion | Single-domain, resource-constrained |
When to choose each option
Choose ccTLD when:
- You have dedicated local marketing budgets and in-country teams for each market
- Your target markets strongly prefer local TLDs (Japan
.jp, Germany.de) - You want complete operational independence between regions
- Domain authority isolation is acceptable (or preferred for brand/legal reasons)
- You can sustain the infrastructure overhead of multiple domains
Choose subdomain when:
- You want hosting flexibility (serve
de.example.comfrom a German data center) - You need separate deployments per region but want some brand connection to root
- You're adding international markets incrementally and aren't sure of long-term commitment
- You have separate CMS instances per region
Choose subdirectory when:
- You're a startup or SMB without dedicated international teams
- You want all link equity and domain authority to benefit every market
- You have a single CMS or application that can handle locale-based routing
- Speed of launch matters more than maximum geo-signal strength
- You're expanding to 2-5 markets and may reverse course
Domain authority and link equity implications
Subdirectory (authority consolidates)
A backlink to example.com/de/blog/post/ flows authority to the root example.com
domain. Every market benefits from every link, regardless of which market earned it.
Link to example.com/de/blog/ ---> flows to --> example.com (all markets benefit)
Link to example.com/fr/blog/ ---> flows to --> example.com (all markets benefit)This is the key SEO advantage of subdirectories for newer or smaller sites that need to build authority efficiently.
ccTLD (authority is isolated)
Links to example.de don't help example.com or example.fr. Each domain must
build its own authority independently.
Link to example.de ---> only benefits example.de
Link to example.fr ---> only benefits example.frThis is fine for large brands with strong local link building in each market, but costly for sites starting from scratch in a new country.
Subdomain (partial sharing)
Google's treatment of subdomains has evolved. Currently, Google generally treats subdomains as part of the parent domain for ranking purposes (especially with GSC association), but the behavior is less predictable than subdirectories.
Server configuration
Subdirectory with Nginx
Route all locale-prefixed paths to the same application, passing locale via header or URL parameter:
server {
listen 443 ssl;
server_name example.com;
# Route locale-prefixed paths to application
location ~ ^/(de|fr|es|pt-br)(/.*)?$ {
proxy_pass http://app_backend;
proxy_set_header X-Locale $1;
proxy_set_header Host $host;
}
# Default (en or x-default)
location / {
proxy_pass http://app_backend;
proxy_set_header X-Locale "en";
proxy_set_header Host $host;
}
}Subdomain with Nginx
Each subdomain routes to potentially different backends:
server {
listen 443 ssl;
server_name de.example.com;
location / {
proxy_pass http://de_app_backend;
proxy_set_header Host $host;
}
}
server {
listen 443 ssl;
server_name fr.example.com;
location / {
proxy_pass http://fr_app_backend;
proxy_set_header Host $host;
}
}ccTLD with shared backend (Nginx virtual hosts)
server {
listen 443 ssl;
server_name example.de;
ssl_certificate /etc/ssl/example.de/fullchain.pem;
ssl_certificate_key /etc/ssl/example.de/privkey.pem;
location / {
proxy_pass http://app_backend;
proxy_set_header X-Country "DE";
proxy_set_header X-Lang "de";
proxy_set_header Host $host;
}
}Apache subdirectory (.htaccess)
# Rewrite rules for locale routing
RewriteEngine On
# Serve locale-specific content
RewriteRule ^(de|fr|es)(/.*)?$ /index.php?locale=$1&path=$2 [L,QSA]
# Default locale
RewriteRule ^(.*)$ /index.php?locale=en&path=$1 [L,QSA]CDN configuration for international sites
Cloudflare (subdirectory or subdomain)
Use Cloudflare's geo-routing with Cache Rules to serve regional variants efficiently:
# Cloudflare Transform Rules - set X-Country header
When: Country equals "DE"
Then: Set request header "X-Target-Locale" to "de"
When: Country equals "FR"
Then: Set request header "X-Target-Locale" to "fr"For subdirectories, configure Page Rules to cache /de/* separately from /fr/*:
Cache Rule: example.com/de/*
Edge TTL: 4 hours
Cache Key: Include URL path (includes /de/ prefix)AWS CloudFront (ccTLD or subdomain)
Create separate distributions per domain/subdomain, each pointing to the same or different origin:
{
"Origins": {
"de.example.com": { "DomainName": "app.eu-central-1.example.com" },
},
"DefaultCacheBehavior": {
"ForwardedValues": {
"Headers": ["Accept-Language", "CloudFront-Viewer-Country"]
}
}
}Migration paths
Migrating from subdomain to subdirectory
- Set up subdirectory structure on root domain
- Copy content and implement hreflang on new paths
- Submit new paths to GSC and request indexing
- Set up 301 redirects from old subdomains
- Monitor Search Console for coverage errors and ranking changes
- Wait 2-4 weeks before evaluating impact (crawl/index lag)
# Redirect de.example.com to example.com/de/
server {
server_name de.example.com;
return 301 https://example.com/de$request_uri;
}Migrating from ccTLD to subdirectory
This is the highest-risk migration. Each ccTLD has its own ranking history.
- Announce the consolidation in GSC (remove ccTLD properties after migration)
- Set up subdirectory paths with exact URL parity where possible
- Implement hreflang on new paths before redirecting
- 301 redirect the entire ccTLD to the subdirectory equivalent
- Monitor for 3-6 months - ccTLD migrations can take longer to stabilize
# Redirect example.de to example.com/de/
server {
server_name example.de;
return 301 https://example.com/de$request_uri;
}Migrating from subdirectory to ccTLD
Lower risk for SEO than the reverse, but requires sustained link building on new domains.
- Register and configure ccTLDs
- Set up 301 redirects from subdirectory to ccTLD
- Update hreflang to reference ccTLD URLs
- Update internal links sitewide
- Build local backlinks to the new ccTLD - existing root domain links will not transfer
Google Search Console geo-targeting
For non-ccTLD URLs, configure GSC geo-targeting per property:
- Root domain (
example.com) - Set to the default market, or leave blank for global - Subdirectory (
example.com/de/) - Add as a separate property, set country to Germany - Subdomain (
de.example.com) - Add as a separate property, set country to Germany - ccTLD (
example.de) - Cannot set geo-targeting; inherits from TLD
To add a subdirectory property in GSC:
- Click "Add property" in GSC
- Choose "URL prefix" and enter
https://example.com/de/ - Verify ownership (DNS TXT, HTML file, or Google Analytics)
- Go to Settings > International Targeting > Country
- Select the target country
Recommendation summary
For most teams building international presence:
Starting out with 1-3 languages: Use subdirectories. Fast, cheap, authority consolidates, easy to reverse if a market doesn't take off.
Growing to 5+ markets with budget: Consider subdomains for hosting flexibility, especially if markets need different tech stacks or deployment cadences.
Enterprise with committed local market investment: ccTLDs for markets that matter most. Use subdirectories for smaller or experimental markets.
Never: Auto-detect and redirect by IP without offering an override. This blocks Googlebot from crawling regional variants.
Frequently Asked Questions
What is international-seo?
Use this skill when optimizing websites for multiple countries or languages - hreflang tag implementation, URL structure strategy (ccTLD vs subdomain vs subdirectory), geo-targeting in Google Search Console, multilingual content strategy, and international site architecture. Triggers on multi-language sites, multi-region targeting, hreflang debugging, or expanding a site to new markets.
How do I install international-seo?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill international-seo in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support international-seo?
international-seo works with claude-code, gemini-cli, openai-codex, mcp. Install it once and use it across any supported AI coding agent.