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: '@fluixi@example.social' }],
  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.