Head

useHead sets the <title>, meta tags, links, scripts, styles and the attributes of <html> and <body>. Silgi renders them into the server HTML and keeps them in step after every navigation.

import { useHead } from "@silgi/react";

export default function Contact() {
  useHead({
    title: "Contact",
    htmlAttrs: { lang: "en" },
    meta: [{ name: "robots", content: "noindex" }],
    link: [{ rel: "canonical", href: "https://example.com/contact" }],
  });
  return <h1>Contact</h1>;
}

Entries stack in this order: app.head from the config, the root component, the layout, the page. For the same key the later one wins. A tag is keyed by name, property or http-equiv for meta, by rel="canonical" or rel and href for links, and by id or src for scripts. Set key on any tag to choose its key. A tag without a key is never merged. htmlAttrs and bodyAttrs merge per attribute.

useHead removes its entries when the component unmounts. In the browser an entry exists from the component's commit to its unmount: a render React throws away while a useFetch suspends, a component that throws after useHead inside an ErrorBoundary, and a page you leave at once all leave no tags behind.

Attributes of <html> and <body>

Attributes you set with htmlAttrs and bodyAttrs go with the entry that set them. A page that sets htmlAttrs: { lang: "tr" } and is loaded from the server gives up lang when you navigate to a page that does not set it. The server marks the names it wrote in a data-silgi-head-attrs attribute on <html> and <body>; the browser reads it once, takes those attributes as its own and removes the marker. An attribute the marker does not list, such as one in your own index.html, is put back to its value instead of removed.

Repeated tags

A meta tag may repeat within one entry when its key is one that Open Graph and the search engines let repeat: a property or name that starts with og:, article:, book:, profile: or author:, theme-color, google-site-verification, twitter:image and twitter:image:*. Every tag is kept, in order.

useHead({
  meta: [
    { property: "og:image", content: "/a.png" },
    { property: "og:image", content: "/b.png" },
  ],
});

Another entry that sets og:image replaces both. A tag with its own key is never part of the group; it replaces only the tag with that key. Other meta, such as description, keeps one tag per key: the last in the entry wins.

Search and social tags

useSeoMeta takes flat keys and writes the meta tags: description, robots, author, keywords, themeColor, colorScheme, og* (such as ogTitle and ogImage) and twitter* (such as twitterCard), next to title and titleTemplate.

import { useSeoMeta } from "@silgi/react";

export default function Product() {
  useSeoMeta({
    title: "Anvil",
    description: "A very heavy anvil.",
    ogImage: "https://example.com/anvil.png",
    twitterCard: "summary_large_image",
  });
  return <h1>Anvil</h1>;
}

Defaults

Every page starts with <meta charset="utf-8"> and a viewport meta. Meta you add to app.head.meta in the config is layered over them, so the charset and viewport stay unless you set a tag with the same key; a viewport of your own replaces the default one.

Title template

app.head.titleTemplate in the config wraps every title; %s is the title.

import { defineConfig } from "silgi";

export default defineConfig({
  framework: "react",
  app: {
    head: {
      titleTemplate: "%s · My app",
      meta: [{ name: "description", content: "My app" }],
    },
  },
});

Set in the config, the template applies to the title of a layout or a page too. The page's own useHead comes last, so its title wins over the layout's, and a template it sets wins over the config's. titleTemplate may also be a function in useHead. The last title and the last template set win, and the template applies to the final title. A string template with no title renders nothing; a function is called with undefined in that case, so it can return a default.

import { useHead } from "@silgi/react";

export default function Landing() {
  useHead({ titleTemplate: (title) => (title ? `${title} | Shop` : "Shop") });
  return <h1>Welcome</h1>;
}

Layouts do not wrap the error page, so a template set only by a layout does not apply to it; set the template in app.head.