useSeoMeta
Typed useHead shortcut for SEO that converts each field (description, Open Graph, Twitter, canonical) to the correct tag.
Se autoimporta en los componentes y en app/.
import { useSeoMeta } from "@meekojs/meeko/framework";SEO tags have rules that are easy to confuse: description and Twitter tags use <meta name>, Open Graph tags use <meta property>, and the canonical URL is a <link rel="canonical">. useSeoMeta(input) takes a plain object with one field per tag and produces the correct one for each. Underneath it is useHead: you call it in the same places, it follows the same priority and deduplication rules, and it accepts a getter or signal so tags follow the data.
It is auto-imported in applications.
Usage#
<!-- app/pages/blog/[slug].meeko -->
<script server lang="ts">
const { params } = usePage();
export const post = await posts.find(params.slug);
</script>
<script lang="ts">
useSeoMeta({
title: post.title,
description: post.summary,
ogImage: post.cover,
twitterCard: "summary_large_image",
canonical: `https://example.com/blog/${post.slug}`,
});
</script>
<template><article><h1>{post.title}</h1></article></template>Type#
function useSeoMeta(input: HeadSource<SeoMetaInput>): voidinterface SeoMetaInput {
title?: string | null | undefined
description?: string | null | undefined
ogTitle?: string | null | undefined
ogDescription?: string | null | undefined
ogImage?: string | null | undefined
ogImageAlt?: string | null | undefined
ogImageWidth?: string | number | null | undefined
ogImageHeight?: string | number | null | undefined
ogUrl?: string | null | undefined
ogType?: string | null | undefined
ogSiteName?: string | null | undefined
ogLocale?: string | null | undefined
twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player' | null | undefined
twitterSite?: string | null | undefined
twitterCreator?: string | null | undefined
twitterTitle?: string | null | undefined
twitterDescription?: string | null | undefined
twitterImage?: string | null | undefined
twitterImageAlt?: string | null | undefined
robots?: string | null | undefined
author?: string | null | undefined
keywords?: string | null | undefined
themeColor?: string | null | undefined
colorScheme?: string | null | undefined
applicationName?: string | null | undefined
referrer?: string | null | undefined
canonical?: string | null | undefined
}Parameters#
| Parameter | Type | Default | Description |
|---|---|---|---|
input |
SeoMetaInput, ReadonlySignal<SeoMetaInput> or () => SeoMetaInput |
required | SEO fields. An undefined or null field produces nothing. |
| Field | Tag |
|---|---|
title |
<title> |
description, robots, author, keywords, themeColor, colorScheme, applicationName, referrer |
<meta name="…"> (theme-color, color-scheme, application-name) |
ogTitle, ogDescription, ogImage, ogImageAlt, ogImageWidth, ogImageHeight, ogUrl, ogType, ogSiteName, ogLocale |
<meta property="og:…"> (og:image:alt, og:site_name…) |
twitterCard ("summary", "summary_large_image", "app" or "player"), twitterSite, twitterCreator, twitterTitle, twitterDescription, twitterImage, twitterImageAlt |
<meta name="twitter:…"> |
canonical |
<link rel="canonical"> |
ogImageWidth and ogImageHeight accept numbers; all other fields accept strings.
Return value#
void. Outside a Meeko application, it does nothing. Inside one, a field not in the list throws TypeError: useSeoMeta: unknown field <nombre> when applying tags.
Examples#
Tags for a product page#
<script lang="ts">
const props = defineProps<{ data: { name: string; summary: string; image: string; price: number } }>();
useSeoMeta(() => ({
title: props.data.name,
description: props.data.summary,
ogTitle: props.data.name,
ogType: "product",
ogImage: props.data.image,
ogImageAlt: `Photo of ${props.data.name}`,
ogImageWidth: 1200,
ogImageHeight: 630,
}));
</script>With a getter, tags change when navigating to another product even if the component is preserved.
A page that is not indexed#
import { useSeoMeta } from "@meekojs/meeko/framework";
// In the <script> of app/pages/account/settings.meeko
useSeoMeta({ robots: "noindex, nofollow" });Automatic canonical and Open Graph tags#
To avoid repeating canonical, ogUrl, ogTitle and ogDescription on every page, document.seo derives them from the final URL, title and description:
// meeko.config.ts
import { defineMeekoConfig } from "@meekojs/meeko/framework";
export default defineMeekoConfig({
site: "https://example.com",
document: { seo: true, head: { titleTemplate: "%s · Mi sitio" } },
});A page declaring the same tag with useSeoMeta overrides the derived one.
Common mistakes#
A misspelled field#
useSeoMeta({ title: "Precios", ogimage: "/og/pricing.png" });meeko check reports TS2353: Object literal may only specify known properties, and 'ogimage' does not exist in type 'HeadSource<SeoMetaInput>' and, at runtime, TypeError: useSeoMeta: unknown field ogimage.
useSeoMeta({ title: "Precios", ogImage: "/og/pricing.png" });A twitterCard that does not exist#
useSeoMeta({ twitterCard: "large" });meeko check reports TS2322: Type '"large"' is not assignable to type '"app" | "player" | "summary" | "summary_large_image" | null | undefined'.
useSeoMeta({ twitterCard: "summary_large_image" });An object with changing data#
useSeoMeta({ title: props.data.name });The title is fixed to the value at that moment: if the layout or page is preserved on navigation and the prop changes, the tag keeps the previous product. Pass a getter:
useSeoMeta(() => ({ title: props.data.name }));A relative URL in ogImage or canonical#
useSeoMeta({ ogImage: "/og/home.png", canonical: "/" });There is no error, but search engines and social networks expect absolute URLs in og:image and the canonical tag. Use the site's public origin, or document.seo for the canonical URL:
useSeoMeta({ ogImage: "https://example.com/og/home.png", canonical: "https://example.com/" });Notes#
- SSR. Tags appear in the server HTML, which search engines and social previews read. Calculate them from data SSR already has:
<script server>,load, props or anawait useApi. - Priority. The same as
useHead: routeheadtakes precedence over the page, the page over layouts, and layouts overdocument. Within a level, the later call wins. - Page
description. If routehead(export const headorroute.head) already declaresdescription, it takes precedence over the component'suseSeoMetadescription. - Escaping. Values go into attributes and Meeko escapes them: a title with quotes or
<is safe.