SEO and the document head
@fluixi/start/head manages everything between <head> and </head>, title, meta,
Open Graph, structured data, with the values resolved on the server so a crawler sees them
in the HTML, not after hydration.
import { seo } from '@fluixi/start/head';
export default function Post() {
seo({
title: 'Compiled reactivity',
description: 'How Fluixi turns templates into targeted DOM calls.',
canonical: 'https://example.com/blog/compiled-reactivity',
});
return <article>...</article>;
}
seo is an alias for useHead; use whichever reads better where you are.
It composes down the tree
Call it in a layout for the defaults and in a page for the specifics. The most specific value wins, and everything else is inherited:
// src/routes/layout.tsx
seo({ titleTemplate: '%s · Fluixi', og: { siteName: 'Fluixi' } });
// src/routes/blog/[slug].tsx
seo({ title: 'Compiled reactivity' }); // → "Compiled reactivity · Fluixi"
titleTemplate is inherited and applied to the most specific title, so a section can set
its own frame once rather than every page repeating it. It also accepts a function when
%s is not expressive enough.
Reactive values
Any field that can sensibly change accepts an accessor, and the document updates when it does:
seo({ title: () => `${unread()} unread` });
On a route transition the previous page's head is dropped automatically, the registration is tied to the owner, so leaving the page removes what it added.
What is first-class
Rather than making you assemble meta tags, the common ones are typed fields:
seo({
title: 'Pricing',
description: 'Plans and pricing.',
keywords: ['pricing', 'plans'],
canonical: 'https://example.com/pricing',
robots: { index: true, follow: true },
lang: 'en',
themeColor: '#0d9488',
og: { type: 'website', image: 'https://example.com/og.png', siteName: 'Fluixi' },
twitter: { card: 'summary_large_image', site: '@fluixi' },
alternates: [{ hreflang: 'fr', href: 'https://example.com/fr/pricing' }],
feeds: [{ href: '/rss.xml', title: 'Blog' }],
icons: { icon: '/favicon.svg', apple: '/apple-touch-icon.png' },
verification: { google: '...' },
});
Structured data is a field too: pass an object or an array and it is emitted as
application/ld+json:
seo({
jsonLd: {
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Compiled reactivity',
},
});
Escape hatches
Anything not first-classed goes through meta, link and script:
seo({
meta: [{ name: 'fediverse:creator', content: '@[email protected]' }],
link: [{ rel: 'preconnect', href: 'https://fonts.example.com' }],
script: [{ src: 'https://analytics.example.com/s.js', defer: 'true' }],
});
Components
For markup-shaped code there are components covering the same ground, Title, Meta,
Link, Script, Base, JsonLD. They render nothing and register into the same
registry, so they compose with seo calls and dedupe against them.
import { Title, Meta } from '@fluixi/start/head';
<>
<Title>Pricing</Title>
<Meta name="description" content="Plans and pricing." />
</>
Deduplication
Sources are merged and deduplicated by identity before rendering, one <title>, one
canonical link, one og:image, whichever the most specific caller set. That is what makes
layout defaults safe: a page overriding description replaces it rather than emitting a
second tag.
Next: Images.