Skip to content
Meeko
Type to search the guides and the API reference. Open it from any page with / or Ctrl K; move with the arrow keys and open a result with Enter.
Get started

Contents

On this page

useHead

Adds title, meta, link, script and style tags and html and body attributes to the document while the component is mounted.

composable Since 1.2.0 No import @meekojs/meeko/framework Source code

Se autoimporta en los componentes y en app/.

ts
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#

meeko
<!-- 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#

ts
function useHead(input: HeadSource<HeadInput>): void

HeadInput

ts
interface 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#

meeko
<!-- 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:

meeko
<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#

meeko
<!-- 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:

ts
// 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:

meeko
<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#

Incorrectts
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:

Correctts
useHead(() => ({ title: `${count.value} elementos` }));

A meta tag without content#

Incorrectts
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:

Correctts
useHead({ meta: [{ name: "description", content: "Plans and pricing" }] });

Event handlers in attributes#

Incorrectts
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:

Correctts
onMounted(() => init());

A function in configuration's titleTemplate#

document in meeko.config.ts is fixed as JSON during the build.

Incorrectts
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:

Correctts
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's head (export const head, route.head or useHead in <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 key or its natural key (name, property or httpEquiv in meta, with its media; rel and href in link, except canonical and icon, which are unique; src or code in script; CSS in style). 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 script and style content 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, useHead does nothing; the committed render applies it.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters

Edit on GitHub: docs/en/reference/composables/usehead.md