diff --git a/frontend/public/sitemap-static.xml b/frontend/public/sitemap-static.xml
index 795768d..a7da08d 100644
--- a/frontend/public/sitemap-static.xml
+++ b/frontend/public/sitemap-static.xml
@@ -6,7 +6,7 @@
-
+
weekly
1.0
@@ -16,7 +16,7 @@
-
+
weekly
1.0
@@ -26,7 +26,7 @@
-
+
weekly
1.0
@@ -36,7 +36,7 @@
-
+
weekly
1.0
diff --git a/frontend/src/app/core/services/seo.service.spec.ts b/frontend/src/app/core/services/seo.service.spec.ts
index 3a8775e..df474d3 100644
--- a/frontend/src/app/core/services/seo.service.spec.ts
+++ b/frontend/src/app/core/services/seo.service.spec.ts
@@ -117,6 +117,34 @@ describe('SeoService', () => {
expect(ogLocaleCall?.[0].content).toBe('it_CH');
});
+ it('uses the locale-adaptive root as x-default for home pages', () => {
+ createService({
+ url: '/de',
+ data: {
+ seoTitleKey: 'SEO.ROUTES.HOME.TITLE',
+ seoDescriptionKey: 'SEO.ROUTES.HOME.DESCRIPTION',
+ },
+ translations: {
+ 'SEO.ROUTES.HOME.TITLE': '3D-Druck in Zürich | 3D fab',
+ 'SEO.ROUTES.HOME.DESCRIPTION': '3D-Druckservice in Zürich',
+ },
+ });
+
+ const alternates = Array.from(
+ document.head.querySelectorAll(
+ 'link[rel="alternate"][data-seo-managed="true"]',
+ ),
+ ).map((node) => ({
+ hreflang: node.getAttribute('hreflang'),
+ href: node.getAttribute('href'),
+ }));
+
+ expect(alternates).toContain({
+ hreflang: 'x-default',
+ href: `${document.location.origin}/`,
+ });
+ });
+
it('resolves translated route metadata for the active language', () => {
const { meta, title } = createService({
url: '/en/about',
diff --git a/frontend/src/app/core/services/seo.service.ts b/frontend/src/app/core/services/seo.service.ts
index f1b0b05..b04b060 100644
--- a/frontend/src/app/core/services/seo.service.ts
+++ b/frontend/src/app/core/services/seo.service.ts
@@ -105,7 +105,7 @@ export class SeoService {
cleanPath,
canonicalPath,
alternates,
- alternates.it ?? canonicalPath,
+ this.buildXDefaultPath(canonicalPath, alternates),
lang,
);
}
@@ -119,8 +119,7 @@ export class SeoService {
const alternates = this.normalizeAlternatePaths(override.alternates);
const xDefault =
this.normalizeSeoPath(override.xDefault) ??
- alternates?.it ??
- canonicalPath;
+ this.buildXDefaultPath(canonicalPath, alternates);
this.applySeoValues(
title,
@@ -162,7 +161,7 @@ export class SeoService {
cleanPath,
canonicalPath,
alternates,
- alternates.it ?? canonicalPath,
+ this.buildXDefaultPath(canonicalPath, alternates),
lang,
);
}
@@ -360,6 +359,25 @@ export class SeoService {
}, {});
}
+ private buildXDefaultPath(
+ canonicalPath: string | null,
+ alternates: SeoMap | null,
+ ): string | null {
+ if (canonicalPath && this.isLocalizedHomePath(canonicalPath)) {
+ return '/';
+ }
+
+ return alternates?.it ?? canonicalPath;
+ }
+
+ private isLocalizedHomePath(path: string): boolean {
+ const segments = path.split('/').filter(Boolean);
+ return (
+ segments.length === 1 &&
+ this.supportedLangSet.has(segments[0] as SupportedLang)
+ );
+ }
+
private normalizeAlternatePaths(
paths: SeoMap | null | undefined,
): SeoMap | null {