<head>
We define the <head>
tags (e.g. <title>
or <meta name="description">
)
of our app in our render()
hook.
// _default.page.server.js
// Environment: Node.js
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-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>`
}
To define <head>
tags (e.g. <title>
or <meta name="description">
)
for a specific page,
we can use pageContext.pageExports
.
// about.page.js
// We statically export/determine `<head>` tags
export const documentProps = {
// This title and description will override the defaults
title: 'About SpaceX',
description: 'Our mission is to explore the galaxy.'
}
// _default.page.server.js
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-framework'
export { render }
async function render(pageContext) {
// We use `pageContext.pageExports.documentProps` which pages can statically define.
const documentProps = pageContext.pageExports.documentProps
// Defaults
const title = documentProps.title || 'SpaceX'
const description = documentProps.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>`
}
To define <head>
tags (e.g. <title>
or <meta name="description">
)
that are dynamic (i.e. determined at run-time),
we can use the onBeforeRender()
hook.
// rocket.page.route.js
export const '/rocket/:rocketSlug'
// rocket.page.server.js
export { onBeforeRender }
function onBeforeRender(pageContext) {
const documentProps = (() => {
const shipName = pageContext.routeParams.rocketSlug
if (shipName==='starship') {
return {
title: 'Starship',
description: 'Starship: deliver payload to Mars'
}
}
if (shipName==='falcon') {
return {
title: 'Falcon 9',
description: 'Falcon 9: deliver payload to Low Earth Orbit'
}
}
})()
return {
pageContext: {
documentProps
}
}
}
// _default.page.server.js
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-framework'
export { render }
async function render(pageContext) {
// We use `pageContext.documentProps` which pages can dynamically
// define in their `onBeforeRender()` hook.
const documentProps = pageContext.documentProps
// Defaults
const title = documentProps.title || 'SpaceX'
const description = documentProps.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>`
}
To define <head>
tags by some deeply nested view (React/Vue/...) component:
documentProps
to passToClient
.pageContext.documentProps
to all components, see Guides > Access pageContext
anywhere.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-ui-framework'
export async function render(pageContext) {
// We use our UI 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 UI 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.'
If we use Client Routing, we need to make sure to update document.title
upon 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
}
// ...
}
})
We can also use libraries such as @vueuse/head or react-helmet.
We should use such library only if we have a rationale: the aforementioned solutions are simpler and work for most use cases.
Head libraries already sanitize the HTML <head>
, this means we can skip escapeInject
and wrap the overall result with dangerouslySkipEscape()
.
// _default.page.server.js
// Environment: Node.js
import { dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-framework'
export async function render(pageContext) {
return dangerouslySkipEscape(await renderToHtml(pageContext.Page))
}
For pages defined with markdown, see Integration > Markdown > <head>
(pageContext.pageExports
).