Most multilingual website implementations are technically broken. Mismatched hreflang tags, duplicate content across locale paths, render-blocking language switchers, and metadata that never gets translated past the English version are common failure points.
Multilingual SEO works best when you treat URL structure, hreflang, and locale-specific content as one system. This checklist walks through the technical decisions and implementation patterns that tend to hold up better across modern frontend stacks, especially when you're managing content programmatically with Strapi.
In brief:
- Multilingual SEO requires getting three layers right simultaneously: URL structure, hreflang implementation, and locale-specific content. Most teams only nail one.
- AI Overviews pull from language-matched content, not hreflang declarations, making native-language content creation non-negotiable.
- XML sitemap-based hreflang is the most reliable delivery method for React/Next.js/Nuxt apps where client-side rendering can delay tag discovery.
- A headless CMS with built-in internationalization support decouples content from presentation, enabling programmatic hreflang, metadata, and structured data generation across all locales.
The playbooks that worked before 2024 need updating. AI Overviews now source answers from language-matched content pools, not from whatever version your hreflang points to. Google has flipped to an AI translation opt-out model for publisher content. And Google's localized versions documentation highlights the risk of JavaScript-rendered hreflang tags being absent when processing depends on rendered output.
What Is Multilingual SEO (and Why It Still Trips Up Most Teams)
Multilingual SEO is the practice of optimizing your website so search engines can discover, index, and serve the correct language version of each page to users searching in that language.
Most teams conflate three distinct concerns. Language targeting tells search engines this page is in Spanish: a pure linguistic signal. Regional targeting says this page is for users in Mexico, a geographic signal. Locale-specific content combines both: this page is in Spanish, for Mexico, with MXN pricing and local references. Each maps to different hreflang syntax and different Google behavior.
This distinction matters because multilingual SEO and international SEO aren't synonyms. A site offering English, French, and German with no country-specific variations is multilingual. A site offering en-US, en-GB, and en-AU with localized pricing is multi-regional. A site doing both (es-MX with Mexican Spanish content and es-ES with European Spanish) is locale-specific. The hreflang codes, URL structures, and content strategies differ for each.
Where things break is usually familiar. Teams translate content but skip metadata. They implement hreflang on the English page but forget bidirectional links. They use locale=all patterns that no longer work in Strapi 5. The 10 practices below address each failure point.
1. Architect Your URL Structure for Locale Isolation
The first architectural decision for any multilingual website SEO project is how you separate locale versions in your URL structure. Google documents valid approaches and one it explicitly discourages.
ccTLDs vs. Subdomains vs. Subdirectories
| Strategy | Example | Geo-targeting Signal | Link Equity Scope | Maintenance |
|---|---|---|---|---|
| ccTLD | example.de | Strong, country-specific | Separate per domain | High, separate SEO per domain |
| Subdomain | de.example.com | Moderate, requires GSC config | Can be treated as separate site | Medium, separate hosting possible |
| Subdirectory | example.com/de/ | Moderate, requires hreflang | Shared under one domain | Low, single host |
| URL parameters | site.com?loc=de | None, not recommended by Google | — | — |
For most headless CMS setups, subdirectories are the practical choice. They keep all content under one domain, simplify deployment, and map cleanly to framework routing patterns. Google's documentation presents four URL structure options, with URL parameters explicitly marked as not recommended. Subdirectories require the least operational overhead when you're managing content through a single Strapi instance.
In a Strapi 5 + Next.js architecture, locale prefixes map directly to the App Router's [locale] dynamic segment. Your file structure looks like this:
app/
[locale]/
layout.tsx // sets <html lang="">, generates hreflang via generateMetadata
page.tsx // homepage per locale
blog/
[slug]/
page.tsx // localized blog postsStrapi's REST API accepts a locale query parameter (GET /api/articles?locale=fr) so your frontend fetches the correct content version based on the URL segment. The same pattern works with Nuxt i18n using the prefix routing strategy, which generates distinct URLs like /en/about and /fr/about for every locale. This tends to work well for CDN cache-key design and SEO.
One important note from Google: different URLs are recommended for each language version of a page rather than using cookies or browser settings to adjust the content language on the page. It helps to avoid IP-based detection here. Googlebot does not vary its crawler source location.
2. Implement Hreflang Tags Without Breaking Canonicalization
Hreflang tags tell search engines which language and regional version of a page to serve. Getting them right requires satisfying Google requirements: bidirectional links between all versions, self-referential tags on every page, and fully-qualified absolute HTTPS URLs.
Here's what correct implementation looks like:
<!-- On https://example.com/de/about -->
<link rel="canonical" href="https://example.com/de/about" />
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/about" />
<link rel="alternate" hreflang="de-DE" href="https://example.com/de/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />The x-default tag, missing from many implementations, signals the fallback page for users whose language doesn't match any specific version. The x-default page must also list all variants, including itself.
Common mistakes that invalidate your entire setup:
- Missing return links: If
/en/references/de/but/de/doesn't reference/en/, Google may ignore both annotations. - Cross-language canonicals: A German page with
<link rel="canonical" href="/en/page">tells Google the German page is a duplicate of the English one, directly contradicting your hreflang. - Relative URLs:
href="/en/page"is invalid. Hreflang requires absolute URLs. - Wrong separators:
hreflang="en_US"(underscore) is invalid. Use hyphens:hreflang="en-US".
In a headless CMS architecture, you can automate hreflang generation. Strapi 5's unified document system assigns a single documentId shared across all locale versions of a document. Query the Strapi REST API for a document's locale variants, then loop over them in your frontend's <head>:
function generateHreflangTags(localizations, xDefaultUrl) {
const tags = localizations.map(({ locale, url }) =>
`<link rel="alternate" hreflang="${locale}" href="${url}" />`
);
if (xDefaultUrl) {
tags.push(`<link rel="alternate" hreflang="x-default" href="${xDefaultUrl}" />`);
}
return tags.join('\n');
}For large multilingual sites, consider placing hreflang annotations in your XML sitemap rather than in HTML <head> tags. Sitemap-based hreflang is processed during the sitemap crawl cycle, not during page rendering, which makes it more reliable for JavaScript-heavy frontends where client-side head injection might be missed.
3. Run Locale-Specific Keyword Research (Not Just Translations)
Translating your English keywords into Spanish doesn't give you a Spanish keyword strategy. "Women's jackets" translates to "chaquetas mujer" in Spain, but Mexican searchers use "chamarras" for the same product. "Sneakers" dominates in the US while "trainers" dominates in the UK. No translation step catches these differences.
A straightforward process helps here:
- Start keyword research in the native language directly.
- Use Keyword Planner with Location and Language filters configured independently.
- Check actual SERPs in the target locale, not just volume data.
- Compare terms by market, because search intent and SERP features vary across locales.
Location: Mexico + Language: Spanish produces a different dataset than Location: Spain + Language: Spanish.
Search intent varies across locales too. "CMS" in English returns developer-focused comparison pages and documentation. "Sistema de gestión de contenidos" in Spanish may surface different SERP features and intent patterns entirely. Featured snippets, People Also Ask boxes, and AI Overviews all vary by market.
This matters more now because AI Overviews source language-matched answers. If your content exists only in English, it's effectively absent from AI Overview results in other languages, regardless of how perfect your hreflang implementation is.
4. Localize Content Beyond Translation
W3C i18n guide explains the distinction clearly: translation converts text between languages, while localization adapts content to cultural, regional, and technical conventions. As a developer, you control more of this than you might think.
Date, time, and currency formatting: Use the JavaScript Intl API instead of hardcoding formats. The same date renders as "12/19/2012" in en-US, "19/12/2012" in en-GB, and "١٩/١٢/٢٠١٢" in ar-EG. Currency formatting is equally locale-dependent. Intl.NumberFormat handles JPY (no decimals), EUR (comma decimals), and INR (lakh grouping) correctly from a single API call.
RTL layout handling: Arabic and Hebrew content requires <html lang="ar" dir="rtl">. Replace physical CSS properties with logical properties: margin-inline-start instead of margin-left, text-align: start instead of text-align: left. These adapt automatically when text direction changes.
Content structure differences: A localized Content-Type in Strapi can use Dynamic Zones that differ between locales. Each locale version can contain different components in different orders.
Strapi 5's AI translations on Growth plans provide a baseline translation when content is saved in the default locale. This works one direction only, with the default locale as the source of truth. Human review still matters for marketing copy, legal content, and culturally sensitive material. For all plans, the "Fill in from another locale" button copies content as a starting point for manual editing.
5. Optimize Metadata Per Locale with Programmatic Patterns
Every locale version needs its own translated, keyword-optimized <title>, <meta description>, and Open Graph tags. In a headless CMS setup, build this programmatically rather than manually editing HTML.
Define SEO fields on your Strapi Content-Types with the localization toggle enabled for metaTitle, metaDescription, and slug. Your API response then includes locale-specific SEO data:
{
"slug": "about-us",
"locale": "fr-FR",
"seo": {
"metaTitle": "À propos de nous | Mon Site",
"metaDescription": "Découvrez notre équipe et notre mission."
}
}In Next.js, generateMetadata pulls these fields and renders the correct tags per locale, including hreflang alternates.
Don't overlook og:locale tags. The Open Graph protocol uses underscore separators (fr_FR), while hreflang uses hyphens (fr-FR). Mixing these formats is a persistent bug. Store both formats in your CMS API response to avoid frontend transformation errors:
<meta property="og:locale" content="fr_FR" />
<meta property="og:locale:alternate" content="en_US" />
<meta property="og:locale:alternate" content="de_DE" />Per-locale noindex directives are also valuable. Set incomplete locale pages to noindex until content is ready, using a boolean field on the Content-Type.
6. Serve Locale-Specific Sitemaps and Robots Directives
A properly structured XML sitemap is the most reliable way to communicate hreflang to search engines, especially for JavaScript-heavy frontends. Set up a sitemap index file pointing to per-locale sitemaps:
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://www.example.com/sitemaps/sitemap-en.xml</loc>
</sitemap>
<sitemap>
<loc>https://www.example.com/sitemaps/sitemap-fr.xml</loc>
</sitemap>
</sitemapindex>Each per-locale sitemap includes <xhtml:link> elements listing all alternate versions, including self-referential ones, with the xmlns:xhtml="http://www.w3.org/1999/xhtml" namespace declared. Child <xhtml:link> elements don't count toward the 50,000 URL limit per sitemap.
Generate these programmatically from Strapi's content API. Query all published documents with their locale variants, then output the XML. Reference the sitemap index in your robots.txt:
User-agent: *
Disallow: /admin/
Sitemap: https://www.example.com/sitemap_index.xmlIt's worth avoiding Disallow rules for locale subdirectories (/en/, /de/, /fr/). Blocking these prevents Googlebot from crawling localized content. Note that robots.txt doesn't prevent indexing; use noindex meta tags for that purpose. Submit your sitemap index in Search Console; submitting the index alone is sufficient.
7. Optimize Core Web Vitals Across All Locales
Multilingual sites introduce performance problems that monolingual sites never encounter. Font loading is usually the first place this shows up.
A few patterns matter most:
- Non-Latin font loading: CJK fonts contain 10,000+ characters compared to ~100–1,000 for Latin fonts. Loading a full Japanese font file blocks rendering and tanks LCP. Use
unicode-rangesubsetting to split fonts into script-specific chunks. This can reduce file transfers by approximately 90% for CJK fonts. Use WOFF2 and setfont-display: swapto prevent invisible text during loading. - Language-switcher scripts: A common mistake is building language switchers that trigger locale detection, client-side route changes, and full re-rendering of translated content. This directly impacts INP. Moving translation logic to Next.js Server Components eliminates translation dictionary JavaScript from the client bundle entirely.
- CDN caching per locale: With subdirectory routing, each locale path (
/en/blog/post,/fr/blog/post) is a separate cacheable resource. Static generation viagenerateStaticParamsin Next.js orrouteRuleswith ISR in Nuxt pre-renders locale pages at build time.
Strapi's headless architecture delivers content via API, and the frontend serves pre-built HTML from the edge. That's a very fast delivery path for any locale.
8. Build Locale-Specific Internal Linking and Navigation
When a user is on /es/productos/, every internal link on that page must point to /es/[target-slug]/, not /en/[target-slug]/ or a locale-less /[target-slug]/. This sounds obvious, but it breaks constantly at scale.
Use framework-native locale-aware link components. Next.js <Link> and Nuxt <NuxtLink> inherit the current locale context automatically. Raw <a href="/about"> requires manual locale prefix prepending, which is an easy source of bugs at scale.
Navigation menus must be localized separately. Build a Navigation or Menu Content-Type in Strapi with localization enabled so link labels and targets resolve correctly per locale. Strapi's internationalization feature supports per-locale publishing for any Content-Type. Breadcrumbs follow the same principle: the trail for a Spanish page should read "Inicio > Blog > Artículo," not "Home > Blog > Article."
Language switcher UX: Don't use flag icons as standalone language identifiers. Flags represent countries, not languages, and mapping them to languages creates confusion. Spanish spans 20+ countries, and a UK flag excludes American English speakers. Use a globe or translate icon as the trigger, and label each language in its own script ("Deutsch" not "German," "Español" not "Spanish").
For accessibility, add lang attributes to each language option (<span lang="de">Deutsch</span>) so screen readers pronounce them correctly (WCAG 3.1.2). Use locale nudge banners ("Switch to Deutsch?") rather than auto-redirects, which can override user intent and create crawlability issues for search engines.
9. Configure Structured Data with Locale Signals
Most multilingual SEO guides skip structured data entirely. Adding locale signals to your JSON-LD helps search engines understand the language and regional targeting of each page.
Add the inLanguage property to your Article or WebPage schema using IETF BCP 47 codes. Build a BreadcrumbList with locale-prefixed @id values. Include areaServed on your Organization schema to signal target markets:
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Cómo implementar Schema Markup",
"inLanguage": "es",
"url": "https://www.example.com/es/blog/schema-markup",
"author": [{ "@type": "Person", "name": "María García" }],
"publisher": {
"@type": "Organization",
"name": "Example Corp",
"areaServed": [
{ "@type": "Country", "name": "ES" },
{ "@type": "Country", "name": "MX" }
]
}
}For translated content, use translationOfWork on the translated article pointing to the original, and workTranslation on the original pointing to translations. Schema.org Article defines both.
Generate this dynamically from your Strapi API response. Each localized document already carries its locale field. Map that to inLanguage, construct locale-prefixed breadcrumb URLs, and merge locale-specific sameAs social profile links. Validate output with Rich Results Test.
10. Monitor Multilingual SEO Performance Per Locale
The International Targeting report no longer exists in Google Search Console. It was removed along with the URL Parameter Tool. Per-locale monitoring now relies on filtering existing reports.
A simple monitoring workflow helps:
- In Google Search Console, add a Country filter for your target market and a Page regex filter to isolate locale paths.
- Use URL Inspection to check Google's chosen canonical for locale pages.
- Watch Indexing report errors such as "Duplicate without user-selected canonical" on locale pages.
- In GA4 Explore, compare session segments scoped to each locale path.
Google Search Console: Open Performance → Search Results. Add a Country filter for your target market, then add a Page filter using regex to isolate locale paths: ^https://example\.com/de/. This shows clicks, impressions, CTR, and average position specifically for your German content viewed by users in Germany. For sites exceeding the UI's row limits, the Search Console API provides deeper access.
Hreflang error detection: With the dedicated hreflang report removed, use the URL Inspection tool to check Google's chosen canonical for locale pages. If it selects the English URL as canonical for your German page, your hreflang cluster is broken. The Indexing report's "Duplicate without user-selected canonical" errors on locale pages are another symptom.
GA4 locale segments: In Explore, create Free Form explorations with session segments scoped to each locale path (Page path starts with /de/). Compare multiple locale segments simultaneously to identify which markets are growing. Note that Path Explorations do not include URL-based dimensions, so stick to Free Form for locale analysis.
Track these metrics per locale: indexed page count (GSC Indexing report filtered by path), CTR by locale, ranking position deltas across markets, and sessions per locale subdirectory. Review regularly. A sudden drop in indexed pages for one locale usually points to a crawlability or canonicalization issue specific to that language version.
Ship Multilingual Content Faster with a Headless CMS
The thread running through all 10 practices is that multilingual SEO at scale requires content and presentation to be decoupled. Hreflang tags, locale-specific metadata, structured data, and sitemaps all need to be generated programmatically from a content source that natively understands locales. This is where a headless CMS helps most.
Strapi 5's i18n system is built into core, with no separate plugin to install. It supports localized fields so you decide which fields vary by locale and which stay global. Dynamic Zones can contain different components per locale. Each locale version can be published independently, with role-based permissions scoped per locale so content teams manage their markets autonomously.
On Growth plans, AI translations automatically translate all other locale versions when you save the default locale, triggered by a single Save action, with an "All locales have been translated" notification confirming completion. The REST API and GraphQL API accept a locale parameter on every endpoint, feeding your Next.js, Nuxt, or any frontend framework with the exact locale content it needs.
Most importantly, you can enforce per-locale SEO fields at the Content-Type level. Making metaTitle, metaDescription, and slug required and localized means editors can't publish without completing metadata. That architectural decision helps avoid one of the most common multilingual SEO failures: translated content with untranslated metadata.
Ready to build? Explore Strapi Cloud for managed hosting, or self-host with full control.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.