useHead
Adds title, meta, link, script and style tags and html and body attributes to the document while the component is mounted.
Se autoimporta en los componentes y en app/.
import { useHead } from "@meekojs/meeko/framework";useHead(input) declares a component's contribution to <head>: the title, <meta>, <link>, <script> and <style> tags, and <html> and <body> attributes. Meeko combines entries from document in meeko.config.ts, app/app.meeko, layouts, the page and its components, and the route's head under one priority rule, and removes a component's entries when it unmounts.
On the server it writes them to HTML; in the browser, hydration adopts those same tags without duplicating them, and a getter or signal updates the document on change. It is auto-imported in applications.
Usage#
<!-- app/pages/inbox.meeko -->
<script lang="ts">
const unread = signal(3);
useHead(() => ({
title: unread.value ? `(${unread.value}) Inbox` : "Inbox",
meta: [{ name: "robots", content: "noindex" }],
}));
</script>
<template><button on:click="() => unread.value = 0">Mark as read</button></template>Clicking the button changes the document title without reloading.
Type#
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
}Parameters#
| Parameter | Type | Default | Description |
|---|---|---|---|
input |
HeadInput, ReadonlySignal<HeadInput> or () => HeadInput |
required | The component's contribution. With a getter or signal, the browser reapplies it when the signals it reads change. |
HeadInput accepts:
| Field | Type | Description |
|---|---|---|
title |
string |
Document title. Highest priority wins. |
titleTemplate |
string or (title) => string |
Title template: "%s · Mi sitio". Only a string in document.head, meeko.addHead and the route's head. |
meta |
HeadMeta[] |
{ name?, property?, httpEquiv?, content, media?, key? }. |
link |
HeadLink[] |
{ rel, href, hreflang?, type?, media?, sizes?, as?, crossorigin?, key? }. |
script |
HeadScript[] |
External with src or inline with children; async, defer, module, type. |
style |
HeadStyle[] |
{ children, media?, key? }. |
htmlAttrs, bodyAttrs |
Record<string, string | undefined> |
<html> and <body> attributes (lang, dir, class, data-*). undefined contributes nothing; on* attributes are rejected at runtime and, since 1.7, DOM event attributes (onclick, onload…) are also rejected by types. |
Return value#
void. Outside a Meeko application (for example, in a unit test with renderToString), it does nothing.
Examples#
A reactive title from page data#
<!-- 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>Structured data for search engines#
An inline application/ld+json script; Meeko escapes every < so content cannot close the tag:
<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><html> and <body> attributes from a 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>A reusable head composable#
useHead can be called inside a composable invoked by the script:
// app/composables/useNoIndex.ts
import { useHead } from "@meekojs/meeko/framework";
export function useNoIndex(): void {
useHead({ meta: [{ name: "robots", content: "noindex, nofollow", key: "robots" }] });
}In <script server>#
In top-level server block code, useHead becomes part of the route's head, the highest-priority entry:
<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>Common mistakes#
An object that reads signals only once#
const count = signal(0);
useHead({ title: `${count.value} elementos` });meeko check accepts this, but the title is calculated once when the script runs: changing count does not update it. Pass a getter so the title tracks the signal:
useHead(() => ({ title: `${count.value} elementos` }));A meta tag without content#
useHead({ meta: [{ name: "description" }] });meeko check reports TS2741: Property 'content' is missing in type '{ name: string; }' but required in type 'HeadMeta'. A description is shorter with useSeoMeta:
useHead({ meta: [{ name: "description", content: "Plans and pricing" }] });Event handlers in attributes#
useHead({ bodyAttrs: { onload: "init()" } });Since 1.7, meeko check reports TS2322: Type 'string' is not assignable to type 'never'. for DOM event handlers (onload, onclick…). Any other on* attribute (onLoad, onmievento) passes types, but Meeko rejects it at runtime: TypeError: useHead.bodyAttrs: event handler attribute onLoad is not allowed. Put code in the component script:
onMounted(() => init());A function in configuration's titleTemplate#
document in meeko.config.ts is fixed as JSON during the build.
export default defineMeekoConfig({ document: { head: { titleTemplate: (title) => `${title} · Mi sitio` } } });meeko check reports TS2322: Type '(title: string) => string' is not assignable to type 'string' in meeko.config.ts, because DocumentHead only accepts a string. Use %s, or put the function in a useHead in app/app.meeko:
export default defineMeekoConfig({ document: { head: { titleTemplate: "%s · Mi sitio" } } });Notes#
- Priority. Lowest to highest: configuration's
document,app/app.meeko, each layout (innermost wins), the page with its components and the route'shead(export const head,route.headoruseHeadin<script server>). Within one level, the later call wins.<html>and<body>attributes merge by name with the same priority. - Deduplication. Each tag has a key: explicit
keyor its natural key (name,propertyorhttpEquivinmeta, with itsmedia;relandhrefinlink, exceptcanonicalandicon, which are unique;srcor code inscript; CSS instyle). For the same key, only the highest-priority entry remains. - SSR. Managed tags go in
<head>between<!--meeko:head-->and<!--/meeko:head-->, with escaped attributes and the request's CSP nonce on<script>and<style>. - Navigation. On navigation, unmounted components' entries disappear and what they replaced returns; going back restores the page's entries. A changed script becomes a new element, executed by the browser.
- Security. Inline
scriptandstylecontent is not escaped as HTML: do not put unvalidated user text there. Attribute values are escaped. - Dry run. In a client navigation's preliminary render,
useHeaddoes nothing; the committed render applies it.