Introduction
If your website serves content in multiple languages or targets multiple regions, you need multilingual SEO. Without it, search engines may show the wrong language version or treat translations as duplicate content.
The hreflang attribute tells search engines which language version to show which users. It prevents duplicate content issues between translations.
This guide covers hreflang implementation, URL structure options, and common international SEO challenges.
Key Concepts
hreflang Tags
<link rel="alternate" hreflang="en" href="https://example.com/about">
<link rel="alternate" hreflang="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/about">
URL Structure Options
// Subdirectories (recommended)
https://example.com/about // English
https://example.com/de/about // German
// Subdomains
https://en.example.com/about
// Separate domains
https://example.de/about
Practical Examples
1. Next.js i18n with hreflang
export async function generateMetadata({ params }) {
const locales = ['en', 'de', 'fr', 'es'];
return {
alternates: {
canonical: `/${params.locale}/about`,
languages: Object.fromEntries(
locales.map(l => [l, `/${l}/about`])
),
},
};
}
2. hreflang in Sitemap
<?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/about</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/about"/>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/about"/>
</url>
</urlset>
3. Language Detection Middleware
import { match } from '@formatjs/intl-localematcher';
export function middleware(request) {
const { pathname } = request.nextUrl;
const locales = ['en', 'de', 'fr'];
const hasLocale = locales.some(l => pathname.startsWith(`/${l}`));
if (hasLocale) return;
const locale = match(negotiator.languages(), locales, 'en');
return NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url));
}
Best Practices
- ✅ Include hreflang on every page referencing all language versions
- ✅ Always include x-default for fallback
- ✅ Use subdirectory URL structure for simplicity
- ✅ Each translation canonicals to itself
- ❌ Don't auto-redirect by IP without language switch option
- ❌ Don't use machine translation without review
Common Pitfalls
- 🚫 Asymmetric hreflang — if A links to B, B must link to A
- 🚫 Missing x-default
- 🚫 Canonical conflicts — each language should canonical to itself
- 🚫 Mixing hreflang in head and sitemap — pick one