SEO - v1.0.0
Hreflang (Multi-language)
Hreflang tags help search engines understand the language and regional targeting of your pages. This guide covers implementing hreflang for international SEO.
Overview
Hreflang attributes tell search engines which language versions of a page exist and help them serve the correct version to users based on their language and location.
Configuration
Enable Hreflang Support
// In config/seo.php
'hreflang' => [
'enabled' => true,
'locales' => ['en', 'en-US', 'en-GB', 'fr', 'de', 'es', 'ja', 'zh-CN'],
'x_default' => true,
],
Supported Locale Formats
| Format | Example | Description |
|---|---|---|
| Language only | en, fr, de |
General language targeting |
| Language-Region | en-US, en-GB |
Specific regional targeting |
| Language_Region | zh_CN, pt_BR |
Alternative format |
Setting Hreflang URLs
Using the Model
$post->updateSeoMeta([
'hreflang' => [
'en' => 'https://example.com/post',
'en-US' => 'https://example.com/en-us/post',
'en-GB' => 'https://example.co.uk/post',
'fr' => 'https://example.fr/article',
'de' => 'https://example.de/beitrag',
'x-default' => 'https://example.com/post',
],
]);
Using the Hreflang Service
use ArtisanPackUI\Seo\Services\HreflangService;
$hreflangService = app(HreflangService::class);
// Get the SeoMeta record for the model
$seoMeta = $post->getOrCreateSeoMeta();
// Set alternate URL for a single locale
$hreflangService->setAlternateUrl($seoMeta, 'en', 'https://example.com/post');
$hreflangService->setAlternateUrl($seoMeta, 'fr', 'https://example.fr/article');
// Or set multiple alternate URLs at once
$hreflangService->setAlternateUrls($seoMeta, [
'en' => 'https://example.com/post',
'fr' => 'https://example.fr/article',
], replace: false); // false = merge with existing, true = replace all
// Remove a language
$hreflangService->removeAlternateUrl($seoMeta, 'de');
// Get hreflang tags for rendering
$tags = $hreflangService->getHreflangTags($post);
// Returns: [['hreflang' => 'en', 'href' => '...'], ...]
x-default Tag
The x-default tag specifies the default page for users whose language/region doesn't match any specified hreflang.
Automatic x-default
When enabled in config, the package automatically adds x-default:
'hreflang' => [
'x_default' => true, // Auto-add x-default
],
The x-default URL defaults to the en or first specified URL.
Manual x-default
$post->updateSeoMeta([
'hreflang' => [
'en' => 'https://example.com/post',
'fr' => 'https://example.fr/article',
'x-default' => 'https://example.com/post',
],
]);
Rendering Hreflang Tags
Using Blade Component
<x-seo-hreflang :model="$post" />
Generated Output
<link rel="alternate" hreflang="en" href="https://example.com/post">
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/post">
<link rel="alternate" hreflang="fr" href="https://example.fr/article">
<link rel="alternate" hreflang="de" href="https://example.de/beitrag">
<link rel="alternate" hreflang="x-default" href="https://example.com/post">
Livewire Hreflang Editor
The package includes a Livewire component for managing hreflang URLs:
<livewire:hreflang-editor :model="$post" />
This provides:
- Add/remove language variants
- URL validation
- Locale selection from configured locales
- x-default management
Dynamic Hreflang Generation
For multi-language sites with consistent URL patterns:
// In your model
public function getHreflangUrls(): array
{
$locales = ['en', 'fr', 'de', 'es'];
$urls = [];
foreach ($locales as $locale) {
$urls[$locale] = route('posts.show', [
'locale' => $locale,
'post' => $this->slug,
]);
}
$urls['x-default'] = $urls['en'];
return $urls;
}
// Update SEO meta
$post->updateSeoMeta([
'hreflang' => $post->getHreflangUrls(),
]);
Regional vs Language Targeting
Language-Only Targeting
Use when content is the same for all regions speaking that language:
'hreflang' => [
'en' => 'https://example.com/post',
'fr' => 'https://example.com/fr/post',
'de' => 'https://example.com/de/post',
],
Regional Targeting
Use when content differs by region (pricing, legal, cultural):
'hreflang' => [
'en-US' => 'https://example.com/post',
'en-GB' => 'https://example.co.uk/post',
'en-AU' => 'https://example.com.au/post',
],
Combined Approach
'hreflang' => [
'en' => 'https://example.com/post', // Generic English
'en-US' => 'https://example.com/us/post', // US-specific
'en-GB' => 'https://example.co.uk/post', // UK-specific
'fr' => 'https://example.fr/article', // Generic French
'fr-CA' => 'https://example.ca/fr/post', // Canadian French
],
Common Patterns
Subdomain Structure
'hreflang' => [
'en' => 'https://en.example.com/post',
'fr' => 'https://fr.example.com/post',
'de' => 'https://de.example.com/post',
],
Subdirectory Structure
'hreflang' => [
'en' => 'https://example.com/en/post',
'fr' => 'https://example.com/fr/post',
'de' => 'https://example.com/de/post',
],
Country-Code TLDs
'hreflang' => [
'en-US' => 'https://example.com/post',
'en-GB' => 'https://example.co.uk/post',
'fr' => 'https://example.fr/post',
'de' => 'https://example.de/post',
],
Validation
Required Rules
- Self-referencing: Each page must include itself in its hreflang set
- Reciprocal: All pages in the set must link to each other
- Absolute URLs: Always use full URLs, not relative paths
- Valid codes: Use ISO 639-1 language codes and ISO 3166-1 alpha-2 country codes
Validation Helper
use ArtisanPackUI\Seo\Services\HreflangService;
$hreflangService = app(HreflangService::class);
// Validate a locale code format
$isValid = $hreflangService->validateLocale('en-US'); // Returns bool
// Check if model has hreflang data
$hasData = $hreflangService->hasHreflangData($post);
// Get hreflang count for a model
$count = $hreflangService->getHreflangCount($post);
Best Practices
- Be consistent - Use the same hreflang format across your entire site
- Include all versions - List all language versions, including the current page
- Use x-default - Always specify a default for unmatched users
- Validate URLs - Ensure all hreflang URLs are accessible (200 status)
- Update together - When adding a new language, update all existing pages
- Match content - Hreflang pages should have equivalent content, not just translations of the homepage
Troubleshooting
Common Issues
Pages not indexed in correct language:
- Verify reciprocal linking (all pages link to each other)
- Check that URLs return 200 status
- Ensure self-referencing is in place
Duplicate content warnings:
- Add hreflang to all language versions
- Verify canonical URLs are set correctly
x-default not working:
- Ensure x-default URL is also included as a language-specific version
- Check that x-default URL is accessible
Next Steps
- Meta Tags - Basic meta tag management
- Model Integration - HasSeo trait reference
- Configuration - Hreflang settings
- Troubleshooting - Common issues and solutions