From frontendskills
Use when managing the document head of a frontend SPA — per-route title, meta, Open Graph, and canonical tags via TanStack Router's head option (React) or Unhead (@unhead/vue / @unhead/react), plus a correct html lang. Improves accessibility (titles announced on navigation) and SEO/social previews.
How this skill is triggered — by the user, by Claude, or both
Slash command
/frontendskills:set-up-document-headThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```bash
grep -rn "<title>\|useHead\|head:\|HeadContent\|document.title" src/ index.html 2>/dev/null | head
grep -n "<html" index.html 2>/dev/null
Check whether routes set titles, whether <html lang> is set, and whether OG/canonical exist. Prerequisite: ideally set-up-routing (per-route head); pairs with set-up-i18n (sync lang to the active locale).
<title> only → add per-route titles + meta.React + TanStack Router → its built-in head route option (no extra dep). React without it → @unhead/react. Vue → @unhead/vue.
head)Render the head, set defaults at the root, and override per route. Update the root route to render <HeadContent />:
// src/routes/__root.tsx
import { createRootRouteWithContext, HeadContent, Outlet } from '@tanstack/react-router';
export const Route = createRootRouteWithContext<RouterContext>()({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'MyApp' }, // default; routes override
],
}),
component: () => (
<>
<HeadContent />
<Outlet />
</>
),
});
Per-route, with a dynamic title from loaded data (the router dedupes, preferring the deepest route):
// src/routes/todos.$id.tsx
export const Route = createFileRoute('/todos/$id')({
loader: ({ context, params }) =>
context.queryClient.ensureQueryData(queryKeys.todos.detail(params.id) /* + queryFn */),
head: ({ loaderData }) => ({
meta: [
{ title: loaderData ? `${loaderData.text} — MyApp` : 'Todo — MyApp' },
{ name: 'description', content: loaderData?.text ?? 'Todo details' },
],
}),
});
pnpm add @unhead/react
Wrap the app in <UnheadProvider> (from createHead()), then in any component:
import { useHead } from '@unhead/react';
useHead({ title: 'Dashboard — MyApp', meta: [{ name: 'description', content: '…' }] });
pnpm add @unhead/vue
// src/main.ts
import { createHead } from '@unhead/vue';
app.use(createHead());
<script setup lang="ts">
import { useHead } from '@unhead/vue';
useHead({ title: 'Dashboard — MyApp', meta: [{ name: 'description', content: '…' }] });
</script>
Reactive sources (a ref/computed) update the head automatically.
<html lang>, Open Graph, canonical<html lang> in index.html (<html lang="en">). With set-up-i18n, sync it to the active locale: document.documentElement.lang = locale on change. This is an a11y requirement (screen readers pick voice/pronunciation from it).og:title, og:description, og:image, twitter:card. Set per route alongside the title.<link rel="canonical">) on pages reachable by multiple URLs.pnpm dev
Navigate: the tab title and <title> change per route (a screen reader announces the new title); view-source/inspect shows the right meta. Share a URL → the OG preview is correct.
SPA caveat: client-rendered head tags are applied after JS runs, so non-JS crawlers/scrapers may miss them. For SEO/social-critical pages, prerender or SSR — and use the landing catalogue, which owns discoverability: ../../landing/set-up-seo/SKILL.md (see also head-patterns.md).
head route option / loader data.lang to the locale.npx claudepluginhub velimirmueller/claude_development_skills --plugin frontendskillsGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.