Pages used to render at multiple URLs (/about, /en/about, /de/about, …
all serving identical content). Now each page lives at exactly one URL:
base locale unprefixed, non-base locales (with content on disk) at
/{locale}/path. Search engines see the canonical via <link
rel="canonical"> and locale alternates via <link rel="alternate"
hreflang> + matching <xhtml:link> in sitemap.xml.
- Vite plugin discovers locales from disk (dirs containing site.json),
emits availableLocales + baseLocale as build-time literals in the
virtual config module. The previous hardcoded SUPPORTED_LOCALES
fanout in includedRoutes is gone — empty locale dirs no longer
produce phantom pre-renders.
- Router builds the /:locale/... regex from availableLocales minus
base. Single-locale sites don't register a locale route at all.
The /:pathMatch(.*)* catch-all swallows any stale /{baseLocale}/...
URL (HTTP 200 home fallback until 404-page work ships).
- usePageMeta emits canonical + hreflang per page via @unhead/vue's
link[]. Drafts and sites without site.url skip canonical.
- buildSitemap takes { availableLocales, baseLocale } options; emits
xhtml:link alternates for multi-locale, byte-identical output for
single-locale.
- New shared helper src/utils/canonicalUrl.js — single source for the
URL formula, used by both usePageMeta and sitemapGenerator.
- BREAKING: `locales` plugin option removed (auto-discovered from disk).
No consumer site passes it; verified by grep.
- BREAKING: /{baseLocale}/path URLs no longer pre-render or sitemap.
- 445 tests passing (12 new for canonicalUrl, 7 added to sitemap, 9
added to usePageMeta, 13 in router after refactor).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>