GitHub

Metadata & SEO

The meta field declares everything that appears in the <head> — title, description, stylesheets, Open Graph tags, and structured data. All metadata is rendered server-side, so crawlers and social media scrapers see the final HTML without executing JavaScript.

Basic metadata

export default {
  route: '/about',
  meta: {
    title:       'About Us — Acme Corp',
    description: 'Learn about the team behind Acme Corp.',
    styles:      ['/app.css'],
  },
  view: () => `<h1>About Us</h1>`,
}

This generates:

<title>About Us — Acme Corp</title>
<meta name="description" content="Learn about the team behind Acme Corp.">
<link rel="stylesheet" href="/app.css">

All meta fields

FieldTypeDescription
titlestringPage title — appears in the browser tab and search results.
descriptionstringMeta description — appears in search engine snippets. Keep under 160 characters.
stylesstring[]Array of stylesheet URLs — each emits a <link rel="stylesheet"> tag.
ogTitlestringOpen Graph title. If omitted, falls back to title.
ogImagestringOpen Graph image URL — shown when the page is shared on social media.
schemaobjectJSON-LD structured data object — emitted as a <script type="application/ld+json"> tag.
canonicalstring | (ctx, serverState) => stringCanonical URL — overrides the auto-derived canonical. Accepts a function for dynamic values.

Open Graph

Open Graph tags control how the page appears when shared on social media (Twitter/X, Facebook, LinkedIn, Slack, etc.):

meta: {
  title:       'My Product — Acme Corp',
  description: 'The best product ever made.',
  ogTitle:     'My Product',                         // shorter for social
  ogImage:     'https://acme.com/og/my-product.jpg', // 1200×630 recommended
}

Generated tags:

<meta property="og:title" content="My Product">
<meta property="og:description" content="The best product ever made.">
<meta property="og:image" content="https://acme.com/og/my-product.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="My Product">
<meta name="twitter:image" content="https://acme.com/og/my-product.jpg">
Use an absolute URL for ogImage — social media crawlers need the full URL to fetch the image. Recommended size: 1200×630 pixels.

Structured data (ld+json)

The schema field accepts a plain object conforming to schema.org vocabulary. Pulse serialises it as a <script type="application/ld+json"> tag in the head:

meta: {
  title: 'How to make sourdough — My Blog',
  schema: {
    '@context': 'https://schema.org',
    '@type':    'Article',
    headline:   'How to make sourdough',
    author: {
      '@type': 'Person',
      name:    'Jane Smith',
    },
    datePublished: '2025-01-15',
    image:         'https://myblog.com/sourdough.jpg',
  },
}

Common schema types:

@typeUse for
WebSiteThe homepage or site root
WebPageGeneral pages
ArticleBlog posts, news articles
ProductE-commerce product pages
FAQPageFAQ pages (enables rich results in Google)
BreadcrumbListBreadcrumb navigation
OrganizationCompany/brand information

Canonical URLs

Pulse automatically derives a canonical URL from the request and emits it as a <link rel="canonical"> tag on every page. In most cases no configuration is needed.

Use meta.canonical to override — for example, on paginated pages or when content is accessible at more than one URL. A plain string is resolved once at startup:

// Paginated blog — pages 2, 3, … all canonicalise to the first page
meta: {
  title:     'Blog — Page 2',
  canonical: 'https://mysite.com/blog',
}

Pass a function to derive the canonical from the request context or server data. The function receives (ctx, serverState):

// Canonical from a URL param
meta: {
  canonical: (ctx) => `https://mysite.com/products/${ctx.params.slug}`,
}

// Canonical from a server fetcher result (e.g. canonical slug from a database lookup)
meta: {
  canonical: (ctx, serverState) => `https://mysite.com/products/${serverState.product.slug}`,
}
Streaming caveat: when stream: true (the default), the <head> is written before server fetchers resolve, so serverState will be null for streaming responses. If your canonical depends on server data, set stream: false on that spec, or derive it from ctx.params instead.

Stylesheets

The styles array accepts any number of stylesheet URLs. They are emitted as <link rel="stylesheet"> tags in the <head> in the order declared:

meta: {
  styles: [
    '/app.css',
    '/fonts.css',
    'https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap',
  ],
}
For maximum performance, host your own fonts rather than using Google Fonts. External stylesheet requests add render-blocking latency and a DNS lookup.

SEO tips

  • Write a unique title and description for every page — duplicate metadata prevents pages from competing in search results.
  • Keep descriptions under 160 characters — longer values are truncated.
  • Use structured data to qualify for Google rich results.
  • All metadata is in the server-rendered HTML — search engines and social scrapers do not need to execute JavaScript to read it.
  • Pulse targets 100/100 Lighthouse SEO out of the box. Run /pulse-report after new pages to confirm.