FrançaisPlayground

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.