HTML <head>

We usually define HTML <head> tags, such as <title> or <meta>, in the render() hook:

// _default.page.server.js
// Environment: Node.js

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-view-framework'

export { render }

async function render(pageContext) {
  return escapeInject`<html>
    <head>
      <title>SpaceX</title>
      <meta name="description" content="We deliver payload to space.">
    </head>
    <body>
      <div id="root">
        ${dangerouslySkipEscape(await renderToHtml(pageContext.Page))}
      </div>
    </body>
  </html>`
}

Page specific <head>

If we want to define <title> and <meta> tags on a page-by-page basis, we can use pageContext:

// _default.page.server.js

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-view-framework'

export { render }

async function render(pageContext) {
  // We use `pageContext.documentProps` which pages define in their `onBeforeRender()` hook
  let title = pageContext.documentProps.title
  let description = pageContext.documentProps.description

  // Defaults
  title = title || 'SpaceX'
  description = description || 'We deliver payload to space.'

  return escapeInject`<html>
    <head>
      <title>${title}</title>
      <meta name="description" content="${description}">
    </head>
    <body>
      <div id="root">
        ${dangerouslySkipEscape(await renderToHtml(pageContext.Page))}
      </div>
    </body>
  </html>`
}
// about.page.server.js

export { onBeforeRender }

function onBeforeRender() {
  const documentProps = {
    // This title and description will override the defaults
    title: 'About SpaceX',
    description: 'Our mission is to explore the galaxy.'
  }
  return {
    pageContext: {
      documentProps
    }
  }
}

<head> defined by component

To define <head> tags by some deeply nested view component:

  1. Add documentProps to passToClient.
  2. Pass pageContext.documentProps to all components.
  3. Modify pageContext.documentProps in the deeply nested component.

For example:

// _default.page.server.js
// Environment: Node.js

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import renderToHtml from 'some-view-framework'

export async function render(pageContext) {
  // We use our view framework to pass `pageContext.documentProps` to all components
  // of our component tree. (E.g. React Context or Vue's `app.config.globalProperties`.)
  const pageHtml = await renderToHtml(
    <ContextProvider documentProps={pageContext.documentProps} >
      <Page />
    </ContextProvider>
  )

  // What happens here is:
  // 1. Our view framework passed `documentProps` to all our components
  // 2. One of our (deeply nested) component modified `documentProps`
  // 3. We now render `documentProps` to HTML meta tags
  return escapeInject`<html>
    <head>
      <title>${pageContext.documentProps.title}</title>
      <meta name="description" content="${pageContext.documentProps.description}">
    </head>
    <body>
      <div id="app">
        ${dangerouslySkipEscape(pageHtml)}
      </div>
    </body>
  </html>`
}
// Somewhere in a component deep inside our component tree

// Thanks to our previous steps, `documentProps` is available here.
documentProps.title = 'I was set by some deep component.'
documentProps.description = 'Me too.'

Client Routing

If we use Client Routing, we make sure to update document.title on page change:

// _default.page.server.js

// We make `pageContext.documentProps` available in the browser.
export const passToClient = ['documentProps', 'pageProps']
// _default.page.client.js

import { useClientRouter } from 'vite-plugin-ssr/client/router'

useClientRouter({
  render(pageContext) {
    if( ! pageContext.isHydration ) {
      document.title = pageContext.documentProps.title
    }
    // ...
  }
})

Libraries

We can also use libraries such as @vueuse/head or react-helmet but we should do so only if we have a good reason: the solution using pageContext.documentProps is simpler and works for the vast majority of cases.

Markdown

For pages defined with markdown, see Markdown <head>.

Edit this page