0% прочитано

Laravel x-default hreflang: What I Actually Did for the Default Locale

In a multilingual Laravel project, x-default can easily look like another language version. In ICanUp, I use it differently: x-default points to the default locale URL. I walk through the real HreflangService, its relationship with canonical URLs, and the edge cases across different content types.

9 вересня 2026 р. 8 хв читанняLaravel

When I added hreflang to ICanUp, UK and EN were straightforward: every locale gets its own alternate URL.

The part that raised more questions was x-default. It is not another language, and it does not mean a search engine should always choose the default locale.

In my implementation, x-default is a fallback pointer that reuses the application default locale URL.

x-default does not replace UK or EN hreflang entries. It is added alongside language alternates and points to the fallback URL.
Laravel hreflang x-default pointing to the default locale URL alongside UK and EN localized URLs
UK and EN remain separate hreflang alternates, while x-default uses the default locale URL as the fallback.

The Hreflang Contract I Already Had in ICanUp

Before x-default, I already had the core routing contract: one entity, a shared slug, and locale-aware public URLs.

The default UK locale has no prefix, while EN uses /en.

I covered this model in more detail in my article about shared slugs vs translated slugs in multilingual Laravel.

Locale-aware URL contract
UK:https://icanup.com.ua/posts/example-slug EN:https://icanup.com.ua/en/posts/example-slug

I do not create separate URL logic for hreflang.

The SEO layer consumes the same routing contract as the public application.

What x-default Means in My Implementation

I do not treat x-default as a locale.

There is no locale named x-default in configuration, and it has no translation or separate content.

It is an additional HreflangUrl appended after the language-specific entries.

  • uk points to the UK localized URL.
  • en points to the EN localized URL.
  • x-default reuses the default locale URL.

Why My Fallback Is the Default Locale

In ICanUp, UK is the default locale.

That is already an application-level setting, not an SEO-specific decision.

I therefore do not hardcode UK inside hreflang logic. The service reads default_locale from the same localization settings used by the rest of the application.

Hreflang result
hreflang="uk"https://icanup.com.ua/posts/example-slug hreflang="en"https://icanup.com.ua/en/posts/example-slug hreflang="x-default"https://icanup.com.ua/posts/example-slug
x-default does not have to point to the default locale in every architecture. This is the fallback strategy used by ICanUp because the application already has a defined default locale.

Canonical and x-default Solve Different Problems

Canonical and hreflang are easy to mix together, but they answer different questions.

Canonical identifies the canonical URL of the current localized page. Hreflang describes relationships between locale versions.

x-default belongs to the hreflang contract. It does not replace canonical.

SignalMeaning
canonicalCanonical URL of the current locale page
hreflang="uk"UK alternate
hreflang="en"EN alternate
hreflang="x-default"Fallback URL

The EN Page Does Not Canonicalize to the Default Locale

The fact that x-default points to the UK URL does not mean the English page should use a UK canonical.

The English page remains a full localized page with its own self-canonical.

x-default does not collapse language versions into one page.

HTML - EN page canonical and hreflang
<link    rel="canonical"    href="https://icanup.com.ua/en/posts/example-slug"> <link    rel="alternate"    hreflang="uk"    href="https://icanup.com.ua/posts/example-slug"> <link    rel="alternate"    hreflang="en"    href="https://icanup.com.ua/en/posts/example-slug"> <link    rel="alternate"    hreflang="x-default"    href="https://icanup.com.ua/posts/example-slug">

I covered this distinction separately in my article about Laravel hreflang and canonical URLs.

x-default can point to the same URL as another hreflang entry while each localized page keeps its own canonical.

How x-default Is Actually Built in HreflangService

The production implementation is simpler than a separate x-default branch.

First, build() loops over available_locales and creates the ordinary HreflangUrl entries.

Then the same resolver runs once more for default_locale.

PHP - Add x-default from default locale
$defaultUrl = $urlResolver(    $this->localeSettings->default_locale,); if (filled($defaultUrl)) {    $urls[] = new HreflangUrl(        locale: 'x-default',        url: $defaultUrl,    );}
HreflangService contains no literal "uk" for x-default. The fallback automatically follows LocaleSettings::default_locale.

The Same Resolver Runs Twice

This small detail is one of the parts I like most about the implementation.

The service has no separate x-default URL function. It uses the same resolver that already generates the regular default locale URL.

PHP - Build locale hreflang entries
foreach ($this->localeSettings->available_locales as $locale) {    $url = $urlResolver($locale);     if (blank($url)) {        continue;    }     $urls[] = new HreflangUrl(        locale: $locale,        url: $url,    );}

The URL Still Goes Through the Routing Layer

I do not manually add /en or another locale prefix.

localizedRoute() reads the locale parameter name from the AVN localization configuration and passes it into Laravel route generation.

Routing remains the source of truth for both public navigation and hreflang.

PHP - Generate locale-aware route
$localeParameter = (string) config(    'avn-localization.route_parameter',    'locale',); return route($routeName, [    $localeParameter => $locale,    $routeParameter => $slug,]);

This continues the architecture I described in my article about logical route names with localized URLs.

Available Locale and Available Content Are Not Always the Same Thing

The production code has an important nuance here.

For Posts, Categories, and PostSeries, the resolver returns a URL for every available_locale when the shared slug exists.

For Pages, I added an explicit title translation check and return null when that localized page is not available.

PHP - Page filters unavailable translations
return $this->build(    function (string $locale) use ($page): ?string {        if (blank(            $page->translationForLocale(                'title',                $locale,            )        )) {            return null;        }         return $this->localizedRoute(            routeName: 'pages.show',            routeParameter: 'page',            slug: $page->slug,            locale: $locale,        );    },);
I do not generalize Page behavior to every content type. In the current HreflangService, Page has the translation availability guard. Posts, Categories, and PostSeries currently use available_locales plus the shared slug.

x-default Inherits Resolver Behavior

Because x-default calls the same $urlResolver, it automatically inherits the rules of the current content type.

For a Page, the default URL is added only if the resolver returns a valid URL. For a Post, the fallback is generated through the shared-slug route.

This keeps build() independent of domain-specific details for each entity.

What I Verify in Tests

  • Each available_locale receives an hreflang entry when its resolver returns a URL.
  • The default locale URL is added again as x-default.
  • The default UK route has no unnecessary locale prefix.
  • The EN route receives /en.
  • Changing the active request locale does not redefine the application default locale.
  • A Page without a title translation does not generate that locale alternate.
  • The shared slug is used in Post and Category locale URLs.
  • x-default uses the same resolver as the default locale.

I Verify Raw HTML

SEO tags need to be available to crawlers without client-side reconstruction, so I verify the server-rendered HTML.

For Inertia SSR, this is a stronger check than simply seeing tags after hydration in the browser DOM.

BASH - Check hreflang in raw HTML
curl -s \  https://icanup.com.ua/en/posts/example-slug \  | grep -E 'canonical|hreflang'

What I Would Avoid

  • Do not create a pseudo-locale named x-default.
  • Do not canonicalize every translation to the default locale.
  • Do not hardcode UK separately inside the SEO service.
  • Do not build localized URLs through simple string concatenation.
  • Do not create a separate URL generator only for x-default.
  • Do not assume every content type has the same availability rules without checking its real resolver.

For me, x-default is not a third language page. It is a reuse of the application fallback URL inside the hreflang contract.

What I Kept for Next Time

  • Define the application default locale before defining the SEO fallback.
  • Do not mix x-default with canonical.
  • Generate x-default through the same resolver as the default locale URL.
  • Do not hardcode the default locale inside SEO infrastructure.
  • Keep routing as the source of truth for public URLs.
  • Verify availability behavior separately for each content type.
  • Verify server-rendered HTML instead of only browser DOM.

Conclusion

In ICanUp, x-default turned out to be a small part of the output but an important part of the multilingual routing contract.

I did not create separate content or a separate URL strategy for it. HreflangService simply calls the current resolver again for default_locale.

UK and EN remain separate localized alternates with their own canonical URLs.

As a result, x-default does not add another URL generation system - it reuses the fallback the application already defines.