0% прочитано

Laravel x-default hreflang: що саме я зробила для default locale

У multilingual Laravel-проєкті x-default легко сприйняти як ще одну мовну версію сторінки. У ICanUp я використовую його інакше: x-default вказує на URL default locale. Розбираю реальний HreflangService, зв’язок із canonical та edge cases для різних content types.

9 вересня 2026 р. 8 хв читанняЛаравель

Коли я додавала hreflang у ICanUp, UK та EN були зрозумілими: кожна locale отримує власний alternate URL.

Найбільше питань у мене викликав x-default. Він не є ще однією мовою і не означає, що search engine має завжди вибирати default locale.

У моїй реалізації x-default - це fallback pointer, який повторно використовує URL application default locale.

x-default не замінює hreflang для UK або EN. Він додається поруч із мовними alternates і вказує fallback URL.
Laravel hreflang x-default pointing to the default locale URL alongside UK and EN localized URLs
UK та EN залишаються окремими hreflang alternates, а x-default використовує URL default locale як fallback.

Який hreflang contract уже був у ICanUp

До x-default у мене вже був основний routing contract: одна entity, shared slug і locale-aware public URLs.

Для default UK locale URL не має prefix, а EN отримує /en.

Цей підхід я детальніше розбирала у статті про shared slug vs translated slugs у multilingual Laravel.

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

Для hreflang я не створюю окрему URL logic.

SEO layer використовує той самий routing contract, що і public application.

Що x-default означає в моїй реалізації

Я не трактую x-default як locale.

У configuration немає locale з назвою x-default, для нього немає translation і окремого content.

Це додатковий HreflangUrl, який додається після language-specific entries.

  • uk вказує на UK localized URL.
  • en вказує на EN localized URL.
  • x-default повторно використовує URL default locale.

Чому fallback у мене - саме default locale

У ICanUp default locale - UK.

Це вже application-level setting, а не рішення SEO service.

Тому я не hardcode UK усередині hreflang logic. Service читає default_locale з тих самих localization settings, які використовує решта 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 не зобов’язаний бути URL default locale в кожній можливій architecture. Це конкретна fallback strategy ICanUp, яка спирається на вже визначену application default locale.

Canonical і x-default вирішують різні задачі

Canonical і hreflang легко змішати, але вони відповідають на різні питання.

Canonical визначає canonical URL поточної localized page. Hreflang описує relationship між locale versions.

x-default належить до hreflang contract і не замінює canonical.

SignalЩо означає
canonicalCanonical URL поточної locale page
hreflang="uk"UK alternate
hreflang="en"EN alternate
hreflang="x-default"Fallback URL

EN сторінка не canonicalize на default locale

Те, що x-default вказує на UK URL, не означає, що EN page повинна мати UK canonical.

English page залишається повноцінною localized page і має власний self-canonical.

x-default не згортає мовні сторінки в одну.

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">

Цю різницю я окремо розбирала у статті про Laravel hreflang і canonical URLs.

x-default може збігатися з URL іншого hreflang entry, але canonical кожної localized page залишається її власним.

Як x-default реально будується в HreflangService

Production implementation виявилася простішою, ніж окрема x-default branch.

Спочатку build() проходить по available_locales і додає звичайні HreflangUrl.

Після цього той самий resolver ще раз викликається для 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 немає literal "uk" для x-default. Fallback автоматично слідує за LocaleSettings::default_locale.

Один resolver використовується двічі

Це невелика деталь, але саме вона мені подобається в цьому implementation.

Service не має окремої функції для x-default URL. Він використовує той самий resolver, що вже генерує URL звичайної default locale.

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,    );}

URL теж проходить через routing layer

Я не додаю /en або інший prefix вручну.

localizedRoute() бере route parameter name з AVN localization configuration і передає locale у Laravel route generation.

Routing залишається source of truth і для navigation, і для hreflang.

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

Це продовжує architecture, яку я описувала у статті про logical route names with localized URLs.

Available locale і available content - не завжди одне й те саме

Тут production code має важливий нюанс.

Для Post, Category і PostSeries resolver повертає URL для кожної available_locale, якщо shared slug існує.

А для Page я додала додаткову перевірку title translation і повертаю null, якщо localized page фактично недоступна.

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,        );    },);
Я не узагальнюю Page behavior на всі content types. У current HreflangService саме Page має translation availability guard. Post, Category і PostSeries зараз працюють від available_locales + shared slug.

x-default успадковує поведінку resolver

Оскільки x-default викликає той самий $urlResolver, він автоматично отримує правила конкретного content type.

Для Page default URL буде доданий тільки якщо resolver повернув valid URL. Для Post fallback формується через shared slug route.

Це дозволяє build() не знати domain-specific details конкретної entity.

Що я перевіряю тестами

  • Кожна available_locale отримує hreflang entry, якщо її resolver повертає URL.
  • Default locale URL додається ще раз як x-default.
  • Default UK route не отримує зайвий locale prefix.
  • EN route отримує /en.
  • Зміна active request locale не повинна змінювати application default locale.
  • Page без title translation для locale не генерує цей alternate.
  • Shared slug використовується в Post і Category locale URLs.
  • x-default проходить через той самий resolver, що і default locale.

Я перевіряю raw HTML

SEO tags мають бути доступні crawler без client-side reconstruction, тому фінальну перевірку я роблю в server-rendered HTML.

Для Inertia SSR це надійніша перевірка, ніж просто побачити теги після hydration у browser DOM.

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

Що я б не робила

  • Не створювала б pseudo-locale з назвою x-default.
  • Не canonicalize всі translations на default locale.
  • Не hardcode UK окремо всередині SEO service.
  • Не будувала б locale URL простим string concatenation.
  • Не створювала б окремий URL generator тільки для x-default.
  • Не приписувала б однакові availability rules усім content types без перевірки їх реального resolver.

Для мене x-default - це не третя мовна сторінка. Це повторне використання application fallback URL усередині hreflang contract.

Що я залишила собі на майбутнє

  • Спочатку визначити application default locale, а вже потім SEO fallback.
  • Не змішувати x-default із canonical.
  • Генерувати x-default через той самий resolver, що й default locale URL.
  • Не hardcode default locale в SEO infrastructure.
  • Тримати routing source of truth для public URLs.
  • Перевіряти availability behavior окремо для кожного content type.
  • Перевіряти server-rendered HTML, а не тільки browser DOM.

Висновок

У ICanUp x-default виявився маленькою частиною output, але важливою частиною multilingual routing contract.

Я не створювала для нього окремий content або окрему URL strategy. HreflangService просто повторно викликає current resolver для default_locale.

UK та EN при цьому залишаються окремими localized alternates з власними canonical URLs.

У результаті x-default не додає ще одну систему URL generation - він повторно використовує fallback, який application уже визначила.