Navigated to /docs/core/seo-assets-and-styling

SEO, assets, and styling

Own route metadata, optimized media, fonts, scripts, and component styling without losing SSR safety.

Put metadata next to the route

Use a page head export when metadata belongs to one route. Use Seo for structured title, description, canonical URL, robots, Open Graph, Twitter, and theme-color values. Dynamic head functions can read route params and loader data.

TSX
tsximport { Seo } from "@tavojs/core";

export const head = <Seo
  title="Project dashboard"
  description="Track active projects and delivery status."
  canonical="https://example.com/dashboard"
  openGraph={{ type: "website", image: "https://example.com/og/dashboard.png" }}
/>;

Use framework asset components

Image provides one API for CSR assets and optional SSR optimization. Optimized remote images require an explicit HTTPS host allowlist; local assets must remain inside the public directory.

  • Install the optional sharp dependency only when the server performs image optimization.

  • Mark only above-the-fold images as priority.

  • Use the ?component query when an SVG should render inline and accept component props.

  • Use Font and Script when loading behavior and document placement matter.

Keep styling boundaries clear

Use CSS modules or Tavo.js UI component props for ordinary local presentation. The lower-level style registry is for libraries that must register deduplicated CSS during both SSR and browser rendering.

  • Call style(id, css) while an SSR registry is active; repeated IDs are emitted once.

  • Use ensureClientStyle(id, css) when a browser-only integration must install one managed style element.

  • Keep IDs stable across server and browser rendering so hydration does not duplicate CSS.

TSX
tsximport {
  createStyleRegistry,
  renderStyleTags,
  renderToString,
  style,
  withStyleRegistry,
} from "@tavojs/core";

function StatusBadge({ label }: { label: string }) {
  style(
    "status-badge",
    ".status-badge{border-radius:999px;padding:.25rem .5rem}"
  );
  return <span className="status-badge">{label}</span>;
}

const registry = createStyleRegistry();
const body = withStyleRegistry(registry, () => {
  return renderToString(<StatusBadge label="Ready" />);
});

const styles = renderStyleTags(registry);
const html = `${styles}${body}`;

Head and SEO properties

  • Use the route head export for route-owned metadata and Head for component-owned insertion. Raw HTML is accepted only through unsafeHeadHtml; there is no head string alias.

  • An explicit robots string takes precedence over noIndex and noFollow. Arrays of keywords become a comma-separated meta value.

  • SEO title, description, canonical, and Open Graph image provide fallbacks for corresponding Open Graph and Twitter fields.

  • unsafeHeadHtml is not escaped. Never place user-controlled data in it.

TS
tstype PageHead = {
  title?: string;
  unsafeHeadHtml?: string;
  status?: number;
  htmlAttributes?: Record<string, string | number | boolean>;
  bodyAttributes?: Record<string, string | number | boolean>;
};

type HeadProps = {
  title?: string;
  unsafeHeadHtml?: string;
  children?: Child;
};

type SeoProps = {
  title?: string; description?: string; canonical?: string;
  robots?: string; noIndex?: boolean; noFollow?: boolean;
  keywords?: string | string[]; author?: string; themeColor?: string;
  openGraph?: SeoOpenGraph; twitter?: SeoTwitter;
};

Image properties and optimizer defaults

  • Default candidate widths are 320, 640, 960, 1280, and 1600. A supplied width also adds its 2x candidate; values above 3840 or at most zero are removed.

  • Quality defaults to 75 and is rounded and clamped from 1 to 100. Format defaults to webp and generated sizes defaults to 100vw.

  • priority selects eager loading and high fetch priority. Other images default to lazy loading; decoding defaults to async.

  • Set unoptimized to keep the original URL. Remote optimization requires an HTTPS allowlist; local sources must stay inside publicDir.

TSX
tsx<Image
  src="/images/hero.jpg"
  alt="Team reviewing a release"
  width={1280}
  height={720}
  widths={[640, 960, 1280]}
  sizes="(max-width: 48rem) 100vw, 60vw"
  quality={80}
  format="webp"
  priority
/>

Font and Script properties

  • Font uses href for an external stylesheet or src plus family for a self-hosted @font-face. Self-hosted fonts preload by default; external stylesheet preload is opt-in.

  • Self-hosted format is inferred from woff2, woff, ttf, or otf when type is omitted. crossOrigin defaults to anonymous for font preloads.

  • Font variable must be a CSS custom property name. fallback is appended to the generated variable value.

  • Script supports src, type, async, defer, module, noModule, preload, content, json, id, nonce, integrity, crossOrigin, referrerPolicy, and fetchPriority.

  • A json value without src defaults to application/ld+json. Inline content escapes less-than signs and closing script sequences.

TSX
tsx<Font
  src="/fonts/brand.woff2"
  family="Brand Sans"
  display="swap"
  variable="--font-brand"
/>

<Script
  json={{ "@context": "https://schema.org", "@type": "WebSite", name: "Acme" }}
  nonce={nonce}
/>

<Script src="https://cdn.example.com/widget.js" defer integrity={integrity} />

Look up exact public types

Follow linked API names to their canonical TypeScript declarations and package boundaries.