Document Head
makeHeadDriver() manages the document’s <title>, <meta> and <link> tags. Components declare what they want in the head as a function of their state; the driver merges the declarations of every mounted component and updates the document.
import { run, makeHeadDriver } from 'sygnal'import App from './App.jsx'
run(App, { HEAD: makeHeadDriver({ titleTemplate: '%s · Tasks' }) })titleTemplate is optional; %s is replaced by each title.
The head Static
Section titled “The head Static”A component’s head static maps its state to a head value, { title?, meta?, link? }:
function TaskPage({ state }) { return <h1>{state.task.title}</h1>}TaskPage.head = (state) => ({ title: state.task.title, meta: { description: state.task.summary, 'og:title': state.task.title }, link: [{ rel: 'canonical', href: `https://example.com/tasks/${state.task.id}` }],})
export default TaskPageIt is sent to the driver at start and whenever the result changes. Each component instance has one entry; its next value replaces it, and a falsy value or the component’s disposal removes it.
- Like every declaration static,
headis computed from the component’s state, with or without a model; a root needs aninitialState(SYG132). metakeys starting withog:,article:,fb:(and the other Open Graph prefixes) become<meta property>, the others<meta name>. Anullvalue removes a key a component mounted earlier set.linkentries are merged bykeywhen they have one, else byrelforcanonical, else byrelandhref.
Merging
Section titled “Merging”Entries are merged in mount order: the component mounted last wins the title, so a page’s title replaces the app’s default, and the default comes back when the page goes. meta keys and links merge the same way.
The driver only changes what it manages:
document.titlereturns to the page’s original title when no entry sets one;- tags it creates carry a
data-sygnal-headattribute and are removed when no entry sets them; - an existing
<meta>with the same name (for example a description inindex.html) is updated, and its original content restored later.
Values from the Model
Section titled “Values from the Model”A model entry can send a head value to the HEAD sink too, for example after a request:
function Report({ state }) { return <div><button className="load">Load</button><p>{state.status}</p></div>}Report.initialState = { status: 'idle' }Report.intent = ({ DOM }) => ({ LOAD: DOM.click('.load') })Report.model = { LOAD: { HTTP: () => ({ url: '/api/report', ok: 'LOADED' }) }, LOADED: { STATE: (state, report) => ({ ...state, status: 'ready', report }), HEAD: (state, report) => ({ title: report.name }), },}
export default ReportIt shares the component’s entry with its head static (the last value sent wins). To clear it, send an empty object {}.
With the Router
Section titled “With the Router”The router and the head driver don’t know about each other: the route is state, so the title is derived from it like anything else.
import { Switchable } from 'sygnal'import { router } from './routes.js'import TaskList from './TaskList.jsx'import TaskPage from './TaskPage.jsx'
const TITLES = { home: () => 'All tasks', task: (route) => `Task ${route.params.id}` }
function App({ state }) { return <main><Switchable of={{ home: TaskList, task: TaskPage }} current={state.route.name} /></main>}App.route = 'ROUTE'App.initialState = { route: router.current() }App.head = (state) => ({ title: TITLES[state.route.name](state.route) })App.model = { ROUTE: (state, route) => ({ ...state, route }),}
export default AppServer-Side Rendering
Section titled “Server-Side Rendering”renderToString(App, { head: list }) collects the head static of each component it renders into list, and renderHead(list) turns them into tags for the document’s <head>:
import { renderToString, renderHead } from 'sygnal'import App from './App.jsx'
const head = []const body = renderToString(App, { state: App.initialState, head })const html = `<!doctype html><html><head>${renderHead(head, { titleTemplate: '%s · Tasks' })}</head><body><div id="app">${body}</div></body></html>`The client’s head driver takes over the tags renderHead() wrote. Only the head static is rendered on the server: values that model entries send need the running app. In Vike apps, sygnal/vike collects the head statics of the page and its layouts; they override the title and description settings of the Vike config.