useHead
Añade el título, etiquetas meta, link, script y style y atributos de html y body al documento mientras el componente está montado.
Se autoimporta en los componentes y en app/.
import { useHead } from "@meekojs/meeko/framework";useHead(input) declara lo que un componente aporta al <head>: el título, etiquetas <meta>, <link>, <script> y <style>, y atributos de <html> y <body>. Meeko combina las entradas de document en meeko.config.ts, app/app.meeko, los layouts, la página y sus componentes, y el head de la ruta con una sola regla de prioridad, y quita las de un componente cuando éste se desmonta.
En el servidor las escribe en el HTML; en el navegador la hidratación adopta esas mismas etiquetas sin duplicarlas, y un getter o una señal actualizan el documento cuando cambian. Se autoimporta en las aplicaciones.
Uso#
<!-- app/pages/inbox.meeko -->
<script lang="ts">
const unread = signal(3);
useHead(() => ({
title: unread.value ? `(${unread.value}) Bandeja de entrada` : "Bandeja de entrada",
meta: [{ name: "robots", content: "noindex" }],
}));
</script>
<template><button on:click="() => unread.value = 0">Marcar como leídos</button></template>Al pulsar el botón, el título del documento cambia sin recargar.
Tipo#
function useHead(input: HeadSource<HeadInput>): voidinterface HeadInput {
title?: string
titleTemplate?: string | ((title: string) => string)
meta?: HeadMeta[]
link?: HeadLink[]
script?: HeadScript[]
style?: HeadStyle[]
htmlAttrs?: Record<string, string | undefined> & HeadEventAttributes
bodyAttrs?: Record<string, string | undefined> & HeadEventAttributes
}Parámetros#
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
input |
HeadInput, ReadonlySignal<HeadInput> o () => HeadInput |
obligatorio | Lo que aporta el componente. Con un getter o una señal, el navegador vuelve a aplicarlo cuando cambian las señales que lee. |
HeadInput admite:
| Campo | Tipo | Descripción |
|---|---|---|
title |
string |
Título del documento. Gana el de más prioridad. |
titleTemplate |
string o (title) => string |
Plantilla del título: "%s · Mi sitio". En document.head, meeko.addHead y el head de la ruta sólo una cadena. |
meta |
HeadMeta[] |
{ name?, property?, httpEquiv?, content, media?, key? }. |
link |
HeadLink[] |
{ rel, href, hreflang?, type?, media?, sizes?, as?, crossorigin?, key? }. |
script |
HeadScript[] |
Externo con src o en línea con children; async, defer, module, type. |
style |
HeadStyle[] |
{ children, media?, key? }. |
htmlAttrs, bodyAttrs |
Record<string, string | undefined> |
Atributos de <html> y <body> (lang, dir, class, data-*). undefined no aporta nada; los on* se rechazan en ejecución y, desde 1.7, los de los eventos del DOM (onclick, onload…) también en los tipos. |
Valor devuelto#
void. Fuera de una aplicación de Meeko (por ejemplo, en una prueba unitaria con renderToString) no hace nada.
Ejemplos#
Un título reactivo desde los datos de la página#
<!-- app/pages/products/[id].meeko -->
<script server lang="ts">
const { params } = usePage();
export const product = await findProduct(params.id);
</script>
<script lang="ts">
const quantity = signal(1);
useHead(() => ({ title: `${product.name} (${quantity.value})` }));
</script>Datos estructurados para buscadores#
Un script application/ld+json en línea; Meeko escapa cada < para que el contenido no pueda cerrar la etiqueta:
<script lang="ts">
const props = defineProps<{ data: { title: string; author: string; publishedAt: string } }>();
useHead(() => ({
script: [{
type: "application/ld+json",
children: JSON.stringify({ "@context": "https://schema.org", "@type": "Article", headline: props.data.title, author: props.data.author, datePublished: props.data.publishedAt }),
}],
}));
</script>Atributos de <html> y <body> desde un layout#
<!-- app/layouts/docs.meeko -->
<script lang="ts">
const sidebarOpen = signal(false);
useHead(() => ({
htmlAttrs: { lang: "es", class: "docs" },
bodyAttrs: { "data-sidebar": sidebarOpen.value ? "open" : undefined },
}));
</script>
<template><main><MeekoOutlet /></main></template>Un composable de head reutilizable#
useHead puede llamarse dentro de un composable que el script invoca:
// app/composables/useNoIndex.ts
import { useHead } from "@meekojs/meeko/framework";
export function useNoIndex(): void {
useHead({ meta: [{ name: "robots", content: "noindex, nofollow", key: "robots" }] });
}En <script server>#
En el código de nivel superior del bloque de servidor, useHead forma parte del head de la ruta, la entrada de más prioridad:
<script server lang="ts">
const { params } = usePage();
const post = await posts.find(params.slug);
useHead({ title: post.title, link: [{ rel: "canonical", href: `https://example.com/blog/${post.slug}` }] });
export const body = post.html;
</script>Errores comunes#
Un objeto que lee señales una sola vez#
const count = signal(0);
useHead({ title: `${count.value} elementos` });meeko check lo acepta, pero el título se calcula una vez, al ejecutar el script: cambiar count no lo actualiza. Pasa un getter para que el título siga a la señal:
useHead(() => ({ title: `${count.value} elementos` }));Una etiqueta meta sin content#
useHead({ meta: [{ name: "description" }] });meeko check da TS2741: Property 'content' is missing in type '{ name: string; }' but required in type 'HeadMeta'. Una descripción se escribe más corto con useSeoMeta:
useHead({ meta: [{ name: "description", content: "Planes y precios" }] });Manejadores de eventos en los atributos#
useHead({ bodyAttrs: { onload: "init()" } });Desde 1.7, meeko check da TS2322: Type 'string' is not assignable to type 'never'. en los manejadores de los eventos del DOM (onload, onclick…). Cualquier otro atributo on* (onLoad, onmievento) pasa los tipos, pero Meeko lo rechaza en ejecución: TypeError: useHead.bodyAttrs: event handler attribute onLoad is not allowed. El código va en el script del componente:
onMounted(() => init());Una función en titleTemplate de la configuración#
document de meeko.config.ts se fija en el build como JSON.
export default defineMeekoConfig({ document: { head: { titleTemplate: (title) => `${title} · Mi sitio` } } });meeko check da TS2322: Type '(title: string) => string' is not assignable to type 'string' en meeko.config.ts, porque DocumentHead sólo admite una cadena. Usa %s, o la función en un useHead de app/app.meeko:
export default defineMeekoConfig({ document: { head: { titleTemplate: "%s · Mi sitio" } } });Notas#
- Prioridad. De menor a mayor:
documentde la configuración,app/app.meeko, cada layout (el más interno gana), la página con sus componentes y elheadde la ruta (export const head,route.headouseHeaden<script server>). Dentro de un mismo nivel gana la llamada posterior. Los atributos de<html>y<body>se combinan por nombre con la misma prioridad. - Deduplicación. Cada etiqueta tiene una clave:
keysi la declara o su clave natural (name,propertyohttpEquivenmeta, con sumedia;relyhrefenlink, salvocanonicaleicon, que son únicos;srco el código enscript; el CSS enstyle). Con la misma clave sólo queda la de más prioridad. - SSR. Las etiquetas gestionadas van en
<head>entre<!--meeko:head-->y<!--/meeko:head-->, con los atributos escapados y el nonce CSP de la petición en<script>y<style>. - Navegación. Al navegar, las entradas de los componentes que se desmontan desaparecen y vuelve lo que habían sustituido; al volver atrás, la página restaura las suyas. Un script que cambia es un elemento nuevo, que el navegador ejecuta.
- Seguridad. El contenido de
scriptystyleen línea no se escapa como HTML: no metas ahí texto del usuario sin validar. Los valores de los atributos sí se escapan. - Pasada en seco. En el render previo de una navegación del cliente,
useHeadno hace nada; lo aplica el render que se confirma.