44 lines
5.1 KiB
Markdown
44 lines
5.1 KiB
Markdown
# Localized SEO hreflang Strategy TODO
|
||
|
||
## Goal
|
||
Establish a consistent canonical and hreflang setup for the marketing site so search engines can index German and English content without duplicate-content penalties.
|
||
|
||
## Status (Stand 18.02.2026)
|
||
- **Discovery:** In progress (route audit complete).
|
||
- **Implementation:** In progress (canonical and locale-prefixed routing live).
|
||
- **Validation:** Not started.
|
||
|
||
## Discovery
|
||
- [x] Audit current route map and localized content coverage (marketing pages, blog, checkout flow).
|
||
- Marketing routes live in `routes/web.php` without locale prefixes. Locale handling is session-based via `LocaleController::set`, `HandleInertiaRequests`, and `useLocalizedRoutes`.
|
||
- Slug coverage is mixed: `/contact` (EN) and `/kontakt` (DE) coexist, `/how-it-works` vs. `/so-funktionierts`, while other key pages only exist once (e.g. `/packages`, `/demo`, `/blog`, legal pages such as `/impressum` with no EN variant). Occasion detail routes are German-only (`/anlaesse/{type}` with `hochzeit`, `geburtstag`, etc.).
|
||
- Blog URLs are shared across locales (`/blog`, `/blog/{slug}`), with translated content injected server-side. Checkout surfaces (`/purchase-wizard/{package}`, `/checkout/{package}`) rely on shared slugs and localized copy but no alternate URLs.
|
||
- `MarketingLayout` currently generates a single canonical URL per request and an `x-default` alternate, but no locale-specific `hreflang` links. Canonical calculation removes a `/de|/en` prefix even though paths are prefix-free, so both locales resolve to the same canonical if we later introduce prefixed URLs.
|
||
- The sitemap at `public/sitemap.xml` already lists `/de/...` and `/en/...` alternates that the app does not currently serve, causing mismatch risk.
|
||
- [x] Decide on URL strategy (session-based locale vs. language-prefixed routes) and document migration implications.
|
||
- Decision: adopt path-prefixed locales (`/{locale}/{slug}`) for all marketing and checkout-facing routes, matching the i18n PRP guidance. Default requests (`/foo`) will 301 to the locale-specific URL using the visitor’s persisted preference or `de` fallback.
|
||
- Migration outline:
|
||
1. Introduce a `SetLocaleFromPath` middleware to extract the first segment and share it with Inertia; wire it into a `Route::group` with `prefix('{locale}')` (constrained to `de|en`) in `routes/web.php`.
|
||
2. Move existing marketing routes into the prefixed group, normalising slugs so EN and DE share identical structures where feasible (e.g. `/de/kontakt` → `/de/contact` or `/de/kontakt` mirrored by `/en/contact`). Keep legacy German-only slugs (`/so-funktionierts`, `/anlaesse/...`) behind per-locale path maps.
|
||
3. Add legacy fallback routes (without prefix) that permanently redirect to the new prefixed URLs to preserve existing backlinks and sitemap entries.
|
||
4. Ensure Inertia helpers (`useLocalizedRoutes`) and navigation components build URLs with the active locale segment instead of relying on session posts to `/set-locale`.
|
||
- Blog slugs remain language-agnostic identifiers under `/de/blog/{slug}` and `/en/blog/{slug}`; content localization continues via translated fields.
|
||
- [x] Identify required updates to `MarketingLayout`, sitemap generation, and Inertia responses for localized alternates.
|
||
- `MarketingLayout` needs to accept structured SEO props (canonical URL, title, description, alternates) instead of deriving everything client-side. It should emit `<link rel="alternate" hreflang="">` entries for each supported locale plus `x-default`, set `og:locale`/`og:locale:alternate`, and keep canonical URLs locale-specific (no prefix stripping).
|
||
- Server responses should standardise SEO data via a helper (e.g. `MarketingPage::make()` or dedicated view model) so each `Inertia::render` call provides `seo` props with `canonical`, `alternates`, and translated meta. `HandleInertiaRequests` can share `supportedLocales` and the resolved host, while a new `LocalizedUrlGenerator` service maps routes/slugs per locale.
|
||
- The static `public/sitemap.xml` must be regenerated (or replaced with an automated generator/Artisan command) once prefixed URLs exist, ensuring each entry carries self-referential canonicals and paired `xhtml:link` elements. Include blog detail pages and legal pages for both locales.
|
||
|
||
## Implementation
|
||
- [x] Ensure canonical URLs and hreflang tags are generated per locale with reciprocal references.
|
||
- [x] Expose locale-specific URLs in navigation, Open Graph tags, and any structured data.
|
||
- [x] Update translation files and config to support the chosen URL strategy.
|
||
|
||
## Validation
|
||
- [ ] Add automated checks (feature test or Playwright) verifying hreflang/canonical tags for both locales.
|
||
- [ ] Validate via Search Console-style inspection or lighthouse to confirm alternate links render correctly.
|
||
- [ ] Update docs (PRP + marketing playbooks) with the final hreflang strategy and operational guidance.
|
||
|
||
## Open Questions
|
||
- How will we handle locale fallbacks for missing translations when hreflang is enforced?
|
||
- Do we need localized sitemap indexes per language or a unified sitemap with hreflang annotations?
|