Files
vue-seo-layer/lib/options.ts
Jakub Zych a651de4675 fix: HTML sitemap pages once per locale, never over a catch-all by default
- seoSitemapPages (lib/sitemapPages.ts) builds the routes per locale; the
  module adds them from a pages:resolved hook registered on ready, after
  @nuxtjs/i18n has localised the app's routes, so no cross-locale copies
  exist and each name ends in the locale's i18n suffix (L4 WR-05)
- a path an app catch-all covers is left to it and logged at build unless
  seo.htmlSitemap.overCatchAll is true (L4 WR-04)
- SitemapLink falls back to the i18n locale and renders only when the path
  resolves to the layer's sitemap page of that locale
- an app with only app.vue no longer gets its router turned on by the pages
2026-11-04 22:47:58 +01:00

42 lines
2.1 KiB
TypeScript

// The layer's pagination and HTML-sitemap module options normalised for
// the runtime config (lib/seo-module.ts) and their readers in app and
// server code. Pure, so `node --test` drives it.
export interface SeoPaginationOption {
form: 'query' | 'path'
param: string
segments: Record<string, string>
}
/**
* The HTML sitemap pages by locale, or false for none. overCatchAll: serve
* the layer's pages ahead of an app catch-all page that covers the path
* (default false: the catch-all keeps the path, L4 WR-04).
*/
export type SeoHtmlSitemapOption = { paths: Record<string, string>; overCatchAll: boolean } | false
/** The pagination option with its defaults, as the runtime config carries it. */
export function paginationOption(value: unknown): SeoPaginationOption {
const v = value !== null && typeof value === 'object' ? (value as Record<string, unknown>) : {}
const segments: Record<string, string> = {}
const raw = v.segments !== null && typeof v.segments === 'object' ? (v.segments as Record<string, unknown>) : {}
for (const [locale, segment] of Object.entries(raw)) if (typeof segment === 'string' && /^[\p{L}\p{N}_-]+$/u.test(segment)) segments[locale] = segment
return {
form: v.form === 'path' ? 'path' : 'query',
param: typeof v.param === 'string' && /^[A-Za-z0-9_-]+$/.test(v.param) ? v.param : 'page',
segments,
}
}
/** The HTML sitemap option: false, or the paths (each '/' plus at least one character) and overCatchAll. */
export function htmlSitemapOption(value: unknown): SeoHtmlSitemapOption {
if (value === false) return false
const v = value !== null && typeof value === 'object' ? (value as Record<string, unknown>) : {}
const paths: Record<string, string> = {}
const raw = v.paths !== null && typeof v.paths === 'object' ? (v.paths as Record<string, unknown>) : {}
for (const [locale, path] of Object.entries(raw)) {
if (typeof path === 'string' && /^\/[^?#\s]+$/.test(path) && !path.includes(':') && !path.includes('*')) paths[locale] = path.replace(/\/+$/, '')
}
return { paths, overCatchAll: v.overCatchAll === true }
}