If you don't use vike-react/vike-vue/vike-solid, then you have direct control over <head> tags in your onRenderHtml() hook.

If you use vike-react/vike-vue/vike-solid, then see Guides > <head> tags.

// /renderer/+onRenderHtml.js
// Environment: server
 
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
import { renderToHtml } from 'some-ui-framework'
 
export { onRenderHtml }
 
async function onRenderHtml(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>`
}

By page

To define <head> tags on page-by-page basis, we recommend creating new settings, for example title and description as shown at API > meta > Example: title and description.

By component

To define <head> tags by any UI component:

  1. Add 'headProps' to passToClient.
  2. Use usePageContext() so that pageContext.headProps can be accessed and modified by any component.

For example:

// /renderer/+onRenderHtml.js
// Environment: server
 
export { onRenderHtml }
 
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
import renderToHtml from 'some-ui-framework'
 
async function onRenderHtml(pageContext) {
  // We use our UI framework to pass `pageContext.headProps` to all components
  // of our component tree. (E.g. React Context or Vue's `app.config.globalProperties`.)
  const pageHtml = await renderToHtml(
    <ContextProvider headProps={pageContext.headProps} >
      <Page />
    </ContextProvider>
  )
 
  // 1. One of our UI component modified pageContext.headProps while rendering components.
  // 2. We now render `headProps` to HTML meta tags.
  return escapeInject`<html>
    <head>
      <title>${pageContext.headProps.title}</title>
      <meta name="description" content="${pageContext.headProps.description}">
    </head>
    <body>
      <div id="app">
        ${dangerouslySkipEscape(pageHtml)}
      </div>
    </body>
  </html>`
}
// SomeComponent.js
 
  // Inside a UI component
  const pageContext = usePageContext()
  const { headProps } = pageContext
  headProps.title = 'I was set by some component.'
  headProps.description = 'Me too.'

Client Routing

If you use Client Routing, make sure to update document.title upon page navigation:

// /renderer/+config.js
// Environment: config
 
export default {
  // Make pageContext.headProps available on the client-side.
  passToClient: ['headProps']
}
// /renderer/+onRenderClient.js
// Environment: client
 
export { onRenderClient }
 
async function onRenderClient(pageContext) {
  if (!pageContext.isHydration) {
    // Client-side navigation — we update the page's title
    document.title = pageContext.headProps.title
  }
  // ...
}

Libraries

You can also use libraries such as @vueuse/head or react-helmet.

But we recommend using such library only if you have a rationale as the aforementioned solutions are simpler.

Head libraries already sanitize the HTML <head>, this means you can skip escapeInject and wrap the overall result with dangerouslySkipEscape().

// /renderer/+onRenderHtml.js
// Environment: server
 
export { onRenderHtml }
 
import { dangerouslySkipEscape } from 'vike/server'
import { renderToHtml } from 'some-ui-framework'
 
async function onRenderHtml(pageContext) {
  return dangerouslySkipEscape(await renderToHtml(pageContext.Page))
}