⚠️ The
vite-plugin-ssr
project has been renamed
Vike.
- If you are already using vite-plugin-ssr then migrate to Vike.
- For new projects, don't use vite-plugin-ssr but use Vike instead.
<head>
meta tags
The <head>
tags (e.g. <title>
or <meta name="description">
)
are defined by our server-side render()
hook.
We can also define <head>
tags on a page-by-page basis and on a component-by-component basis, see sections below.
// /renderer/_default.page.server.js
// Environment: server
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr/server'
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>`
}
By page (static)
To define <head>
tags for a specific page, we can use a Custom Export.
// /pages/about.page.js
// Custom Export
export const documentProps = {
// This title and description will override the defaults
title: 'About SpaceX',
description: 'Our mission is to explore the galaxy.'
}
// /renderer/_default.page.server.js
// Environment: server
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr/server'
import { renderToHtml } from 'some-ui-framework'
export { render }
async function render(pageContext) {
// Our custom export is available here as `pageContext.exports.documentProps`
const { documentProps } = pageContext.exports
// 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>`
}
By page (dynamic)
To define <head>
tags that are dynamic (determined at run-time),
we can also use a Custom Export.
// /pages/rocket.page.route.js
export const '/rocket/@rocketSlug'
// /pages/rocket.page.js
// Custom Export
export { getDocumentProps }
// getDocumentProps() can use fetched data to provide <title> and <meta name="description">
function getDocumentProps(pageProps) {
return {
title: pageProps.product.name,
description: pageProps.product.description
}
}
// /renderer/_default.page.server.js
// Environment: server
export { render }
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr/server'
import { renderToHtml } from 'some-ui-framework'
async function render(pageContext) {
// `pageProps` is provided by our data fetching mechanism, see the "Data Fetching" Guide.
const { Page, pageProps } = pageContext
// Custom exports are available at pageContext.exports
const { getDocumentProps } = pageContext.exports
const title = (
// Conditional call in case a page doesn't define getDocumentProps()
getDocumentProps?.(pageProps).title ||
// Default for pages that don't define getDocumentProps()
'SpaceX'
)
const description = (
// Conditional call in case a page doesn't define getDocumentProps()
getDocumentProps?.(pageProps).description ||
// Default for pages that don't define getDocumentProps()
'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>`
}
By component
To define <head>
tags by some deeply nested view (React/Vue/...) component:
- We add
documentProps
to passToClient
.
- We pass
pageContext.documentProps
to all components, see Guides > Access pageContext
anywhere.
- We modify
pageContext.documentProps
in the deeply nested component.
For example:
// /renderer/_default.page.server.js
// Environment: server
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr/server'
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.'
Client Routing
If we use Client Routing, we need to make sure to update document.title
upon page navigation:
// /renderer/_default.page.server.js
// Environment: server
// We make `pageContext.documentProps` available in the browser.
export const passToClient = ['documentProps', 'pageProps']
// /renderer/_default.page.client.js
// Environment: browser
export { render }
function render(pageContext) {
if (!pageContext.isHydration) {
// Page navigation — we update the website's title
document.title = pageContext.documentProps.title
}
// ...
}
Libraries
We can also use libraries such as @vueuse/head or react-helmet.
We recommend to use such library only if you 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()
.
// /renderer/_default.page.server.js
// Environment: server
import { dangerouslySkipEscape } from 'vite-plugin-ssr/server'
import { renderToHtml } from 'some-ui-framework'
export async function render(pageContext) {
return dangerouslySkipEscape(await renderToHtml(pageContext.Page))
}
Markdown
For pages defined with markdown, see Integration > Markdown > <head>
(pageContext.exports
).