<head> meta tags

To add <head> tags, such as <title> and <meta name="description">, you can use:

These options are implemented by the UI framework Vike extension (vike-react/vike-vue/vike-solid). See Without Vike extension if you don't use such extension.

To add <head> tags to all your pages:

// /pages/+Head.jsx or
// /pages/+Head.vue
// Environment: server
 
export { Head }
 
import socialImage from '../assets/images/socialImage.jpg'
import favicon from '../assets/images/favicon.png'
 
function Head() {
  return <>
    <meta charset="utf-8">
    <title>SpaceX</title>
    <meta name="description" content="We deliver payload to space.">
    <meta property="og:image" content={socialImage}>
    <link rel="icon" href={favicon}>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </>
}

Example: React / Vue / Solid.

Setting <head> tags only to one page and based on fetched data:

// /pages/rocket/starship/+data.js
// Environment: server
 
export { data }
 
async function data() {
  const rocket = await sql.run(
    'SELECT { title, description, social_image } FROM rockets WHERE name = "starship";'
  )
  return rocket
}
// /pages/rocket/starship/+Head.jsx
// Environment: server
 
export { Head }
 
import { useData } from 'vike-react/useData' // or 'vike-vue' / 'vike-solid'
 
function Head() {
  const rocket = useData()
  return <>
    <meta name="description" content={rocket.description}>
    <meta property="og:image" content={rocket.social_image}>
  </>
}

title & favicon

If your pages can have different <title> values, then use the setting title instead of the Head setting:

// /pages/rocket/starship/+title.js
// Environment: server, client
export const title = 'SpaceX | Rocket Starship'
// /pages/rocket/falcon-9/+title.js
// Environment: server, client
export const title = 'SpaceX | Rocket Falcon 9'

Upon client-side navigating from the URL /rocket/starship to /rocket/falcon-9, the page's title is dynamically updated from SpaceX | Rocket Starship to SpaceX | Rocket Falcon 9. (By mutating the DOM with document.title = 'SpaceX | Rocket Falcon 9'.)

While defining <title> by using the Head setting correctly sets the page's title for the first page the user visits, it doesn't update the page's title upon client-side navigation.

Most <head> tags, such as the social image tag <meta property="og:image">, don't need to be updated upon client-side navigation because they are only used by crawlers. (Browsers don't do anything with <meta property="og:image">.) Crawlers don't execute JavaScript and, therefore, don't client-side navigate.

Likewise, if your favicon can change from page to page, then define /pages/rocket/starship/+favicon.png instead of using the Head setting.

You can set title and favicon based on fetched data, for example:

// /pages/rocket/starship/+title.js
// Environment: server, client
 
export function title(pageContext) {
  const rocket = pageContext.data
  return rocket.title
}
// With TypeScript:
 
import type { Data } from './+data'
import type { PageContext } from 'vike/types'
export function title(pageContext: PageContext<Data>) {
  const rocket = pageContext.data
  return rocket.title
}

As shown here you can define the logic, of using data.title to set the page's title, once and apply it to all your pages.

useHead()

The upcoming useHead() component hook will enable any UI component to add <head> tags, see: New component hook useHead().

Internationalization

Example of internationalizing (i18n) <head> tags:

// /pages/+Head.js
// Environment: server
 
export { Head }
 
import { usePageContext } from 'vike-react/usePageContext' // or 'vike-vue' / 'vike-solid'
 
function Head() {
  const pageContext = usePageContext()
  const description = pageContext.locale === 'de-DE' ?
    'Wir liefern zum Weltall.' :
    'We deliver payload to space.'
  return <>
    <meta name="description" content={description}>
  </>
}
// /pages/+title.js
// Environment: server, client
 
export function title(pageContext) {
  const title = pageContext.locale === 'de-DE' ?
    'SpaceX | Das Weltall Unternehmen' :
    'SpaceX | The space company'
  return title
}

See also:

Without Vike extension

If you don't use a Vike extension, then you have direct control over <head> tags in your onRenderHtml() hook.

// /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))
}

See also