// SEO de la SPA pública (doc 10, DC-18): fija
, meta description,
// canonical y alternates hreflang de la ruta actual manipulando el .
// Sin dependencias: cada vista lo llama al cargar sus datos (y el prerender
// captura el head ya resuelto).
export interface HeadInput {
title?: string
description?: string
/** URL canónica absoluta de la ruta en el locale activo. */
canonical?: string
/** URL absoluta por locale (hreflang). */
alternates?: Record
}
function upsertMeta(name: string, content: string | undefined) {
let tag = document.head.querySelector(`meta[name="${name}"]`)
if (!content) {
tag?.remove()
return
}
if (!tag) {
tag = document.createElement('meta')
tag.setAttribute('name', name)
document.head.appendChild(tag)
}
tag.setAttribute('content', content)
}
function upsertLink(rel: string, href: string, hreflang?: string) {
const selector = hreflang ? `link[rel="${rel}"][hreflang="${hreflang}"]` : `link[rel="${rel}"]`
let tag = document.head.querySelector(selector)
if (!tag) {
tag = document.createElement('link')
tag.setAttribute('rel', rel)
if (hreflang) tag.setAttribute('hreflang', hreflang)
document.head.appendChild(tag)
}
tag.setAttribute('href', href)
}
export function useHead(input: HeadInput): void {
if (input.title !== undefined) document.title = input.title
upsertMeta('description', input.description)
// Canonical y alternates se reponen enteros en cada ruta (los sobrantes
// de la ruta anterior se retiran).
document.head
.querySelectorAll('link[rel="canonical"], link[rel="alternate"][hreflang]')
.forEach((tag) => tag.remove())
if (input.canonical) upsertLink('canonical', input.canonical)
for (const [locale, href] of Object.entries(input.alternates ?? {})) {
upsertLink('alternate', href, locale)
}
}