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 {