// /pages/+config.js// Environment: configexport default { meta: { dataEndpointUrl: { env: { server: true, // Load the value of /pages/**/+dataEndpointUrl.js only on the server client: false } } }}
// /pages/+config.ts// Environment: configimport type { Config } from 'vike/types'export default { meta: { dataEndpointUrl: { env: { server: true, // Load the value of /pages/**/+dataEndpointUrl.ts only on the server client: false } } }} satisfies Config
// /pages/+data.js// Environment: serverexport { data }async function data(pageContext) { // The value exported by /pages/countries/+dataEndpointUrl.js is // available at pageContext.config.dataEndpointUrl const response = await fetch(pageContext.config.dataEndpointUrl) // ...}
// /pages/+data.ts// Environment: serverexport { data }export type Data = Awaited<ReturnType<typeof data>>import type { PageContextServer } from 'vike/types'async function data(pageContext: PageContextServer) { // The value exported by /pages/countries/+dataEndpointUrl.ts is // available at pageContext.config.dataEndpointUrl const response = await fetch(pageContext.config.dataEndpointUrl) // ...}
Example: +sql
Similarly to the previous example, another common use case is to enable pages to define their data requirements as SQL queries.
// /pages/product/@id/+sql.jsexport const sql = (id) => `SELECT { name, price } FROM products WHERE id = "${id}";`/* Or with an ORM:export const sql = { modelName: 'Product', select: ['name', 'price'], where: { id } } */
// /pages/product/@id/+sql.tsexport const sql = (id) => `SELECT { name, price } FROM products WHERE id = "${id}";`/* Or with an ORM:export const sql = { modelName: 'Product', select: ['name', 'price'], where: { id } } */
// /pages/user/@id/+sql.jsexport const sql = (id) => `SELECT { firstName, lastName } FROM users WHERE id = "${id}";`/* Or with an ORM:export const sql = { modelName: 'User', select: ['firstName', 'lastName'], where: { id } } */
// /pages/user/@id/+sql.tsexport const sql = (id) => `SELECT { firstName, lastName } FROM users WHERE id = "${id}";`/* Or with an ORM:export const sql = { modelName: 'User', select: ['firstName', 'lastName'], where: { id } } */
// /pages/+config.js// Environment: configexport default { meta: { sql: { env: { server: true, // Load the value of /pages/**/+sql.js only on the server client: false } } }}
// /pages/+config.ts// Environment: configimport type { Config } from 'vike/types'export default { meta: { sql: { env: { server: true, // Load the value of /pages/**/+sql.ts only on the server client: false } } }} satisfies Config
// /pages/+onBeforeRender.js// Environment: serverexport { onBeforeRender }import { runQuery } from 'some-sql-engine'async function onBeforeRender(pageContext) { // The value exported by /pages/**/+sql.js is available at pageContext.config.sql const { id } = pageContext.routeParams const { sql } = pageContext.config const query = sql(id) const data = await runQuery(query) // ...}
// /pages/+onBeforeRender.ts// Environment: serverexport { onBeforeRender }import type { PageContextServer } from 'vike/types'import { runQuery } from 'some-sql-engine'async function onBeforeRender(pageContext: PageContextServer) { // The value exported by /pages/**/+sql.ts is available at pageContext.config.sql const { id } = pageContext.routeParams const { sql } = pageContext.config const query = sql(id) const data = await runQuery(query) // ...}
Example: +title and +description
In order to define the tags <title> and <meta name="description"> of a page's HTML, you can create new settings title and description.
If you use a UI framework Vike extensionvike-react/vike-vue/vike-solid, then you don't need to create the title and description settings as they are already created by vike-react/vike-vue/vike-solid. You can still read this section as it's a good example for showcasing meta.
We first show how the settings title and description are used and then their implementation.
// /pages/about-us/+description.jsexport default 'Who we are, our values, and what we stand for.'
// /pages/about-us/+description.tsexport default 'Who we are, our values, and what we stand for.'
You can also use +config.js instead of creating the files +title.js and +description.js:
// /pages/about/+config.jsexport default { title: 'About Us', description: 'Who we are, our values, and what we stand for.'}
// /pages/about/+config.tsimport type { Config } from 'vike/types'export default { title: 'About Us', description: 'Who we are, our values, and what we stand for.'} satisfies Config
Your pages can also use data that was dynamically fetched in order to determine <title> and <meta name="description">:
// /pages/product/+title.js// Environment: server & clientexport default (pageContext) => pageContext.data.product.name
// /pages/product/+title.ts// Environment: server & clientimport type { PageContext } from 'vike/types'import type { Data } from './+data'export default (pageContext: PageContext<Data>) => pageContext.data.product.name
// /pages/product/+description.ts// Environment: serverimport type { PageContextServer } from 'vike/types'import type { Data } from './+data'export default (pageContext: PageContextServer<Data>) => { const { product } = pageContext.data return `${product.name} - ${product.description}`}
Implementation
// /pages/+config.jsexport default { meta: { title: { // Make the value of `title` available on both the server- and client-side env: { server: true, client: true } }, description: { // Make the value of `description` available only on the server-side env: { server: true } } }}
// /pages/+config.tsimport type { Config } from 'vike/types'export default { meta: { title: { // Make the value of `title` available on both the server- and client-side env: { server: true, client: true } }, description: { // Make the value of `description` available only on the server-side env: { server: true } } }} satisfies Config
// /pages/+onRenderClient.js// Environment: clientexport { onRenderClient }import { getTitle } from './utils'function onRenderClient(pageContext) { // Update the value of <title> upon page navigation if (!pageContext.isHydration) { document.title = getTitle(pageContext) } // ...}
// /pages/+onRenderClient.ts// Environment: clientexport { onRenderClient }import type { PageContextClient } from 'vike/types'import { getTitle } from './utils'function onRenderClient(pageContext: PageContextClient) { // Update the value of <title> upon page navigation if (!pageContext.isHydration) { document.title = getTitle(pageContext) } // ...}
// /pages/utils.js// Environment: server & clientexport { getTitle, getDescription }function getTitle(pageContext) { // The value exported by /pages/**/+title.js is available at pageContext.config.title const val = pageContext.config.title if (!val) return 'Some default title' if (typeof val === 'string') return val if (typeof val === 'function') return val(pageContext)}function getDescription(pageContext) { // Same as getTitle() // ...}
// /pages/utils.ts// Environment: server & clientimport type { PageContext } from 'vike/types'export { getTitle, getDescription }function getTitle(pageContext: PageContext) { // The value exported by /pages/**/+title.ts is available at pageContext.config.title const val = pageContext.config.title if (!val) return 'Some default title' if (typeof val === 'string') return val if (typeof val === 'function') return val(pageContext)}function getDescription(pageContext: PageContext) { // Same as getTitle() // ...}
// /pages/+onRenderHtml.ts// Environment: serverexport { onRenderHtml }import type { PageContextServer } from 'vike/types'import { getTitle, getDescription } from './utils'import { escapeInject, dangerouslySkipEscape } from 'vike/server'import { renderToHtml } from 'some-ui-framework'async function onRenderHtml(pageContext: PageContextServer) { const title = getTitle(pageContext) const description = getDescription(pageContext) return escapeInject`<!DOCTYPE html> <html> <head> <title>${title}</title> <meta name="description" content="${description}" /> </head> <body> <div id="root">${dataEndpointUrl(renderToHtml(<Page {...pageContext.data} />))}</div> </body> </html>`}
This implementation can be improved: the file /pages/utils.js is loaded on both the server- and client-side but getDescription() is only used by the server-side. This means getDescription() unnecessarily bloats the size of your client-side bundles. It would be cleaner to split /pages/utils.js in two separate files: one for the client-side and another one for the server-side. In general, be extra careful in which environment each file is loaded; consider using .server.js and .client.js to ensure the environment of files.
Example: +Layout
Another common use case for meta is to create a component <Layout> that defines the layout of the page.
// /pages/product/+Layout.js// Environment: client and serverexport { Layout } from '../layouts/Responsive'
// /pages/product/+Layout.ts// Environment: client and serverexport { Layout } from '../layouts/Responsive'
// /pages/admin/+Layout.js// Environment: client and serverexport { Layout } from '../layouts/Dashboard'
// /pages/admin/+Layout.ts// Environment: client and serverexport { Layout } from '../layouts/Dashboard'
// /pages/+Layout.js// Environment: client and server// The default layout in case the page doesn't set oneexport { Layout } from '../layouts/LayoutDefault'
// /pages/+Layout.ts// Environment: client and server// The default layout in case the page doesn't set oneexport { Layout } from '../layouts/LayoutDefault'
// /pages/+config.js// Environment: configexport default { meta: { Layout: { // Load the value of /pages/**/+Layout.js on both the server and client env: { server: true, client: true }, // Make it cumulative for nested layouts cumulative: true } }}
// /pages/+config.ts// Environment: configimport type { Config } from 'vike/types'export default { meta: { Layout: { // Load the value of /pages/**/+Layout.ts on both the server and client env: { server: true, client: true }, // Make it cumulative for nested layouts cumulative: true } }} satisfies Config
You can use meta to change the environment in which built-in hooks are loaded. For example, you can change the environment of the data() hook from { server: true, client: false } to { server: true, client: true }.
// /pages/some-page/+config.jsexport default { meta: { data: { // By default, the data() hook is loaded and executed only on the // server-side. By using meta we can make it isomorphic instead: // data() is loaded and executed as well on the client-side. env: { server: true, client: true } } }}
// /pages/some-page/+config.tsimport type { Config } from 'vike/types'export default { meta: { data: { // By default, the data() hook is loaded and executed only on the // server-side. By using meta we can make it isomorphic instead: // data() is loaded and executed as well on the client-side. env: { server: true, client: true } } }} satisfies Config
Making data() isomorphic allows you to fetch data directly between the user's browser and the data source (without involving your server), see API > +data() hook > Environment.
If you want to change the meta.env on a page-by-page basis, you can use the the file extension .shared.js (renaming +data.js to +data.shared.js), or you can create a new dataIsomorph config:
Beyond server and client, the meta[configName].env object can determine whether the config is loaded for any Vite environment.
⚠️
It's an experimental feature.
For example, @vitejs/plugin-rsc defines the rsc environment (the separate module graph for React Server Components).
// /pages/+config.js// Environment: configexport default { meta: { Page: { // Load +Page only in the Vite environment `rsc` env: { rsc: true } }, renderRsc: { // Same env: { rsc: true } } }}
// /pages/+config.ts// Environment: configimport type { Config } from 'vike/types'export default { meta: { Page: { // Load +Page only in the Vite environment `rsc` env: { rsc: true } }, renderRsc: { // Same env: { rsc: true } } }} satisfies Config
The rsc config values are available at pageContext.environments.rsc.config.
// +onRenderHtml.js// Environment: server// The pageContextRsc object is the same as pageContext except for the config values. For// example, since +Page only lives in the `rsc` environment:// - pageContextRsc.config.Page is defined// - pageContext.config.Page is undefinedconst pageContextRsc = pageContext.environments.rsc.pageContextconst stream = await pageContextRsc.config.renderRsc(pageContextRsc)
// +onRenderHtml.ts// Environment: server// The pageContextRsc object is the same as pageContext except for the config values. For// example, since +Page only lives in the `rsc` environment:// - pageContextRsc.config.Page is defined// - pageContext.config.Page is undefinedconst pageContextRsc = pageContext.environments.rsc.pageContextconst stream = await pageContextRsc.config.renderRsc(pageContextRsc)
A Vite environment, such as rsc, becomes a Vike environment if there is at least one meta object that sets env: { rsc: true }. (Otherwise, the Vite environment gets the same config values as the server environment.)
The environment must exist in Vite's config.environments and run in the same runtime (process or worker) as the server environment.
With file suffixes, a server-side Vike environment never loads .client.js files, e.g. rsc doesn't load +Page.client.js, while rsc does load .server.js and .shared.js files whose config has env.rsc: true.
API
// /pages/+config.js// Environment: configexport default { meta: { someConfig: { // [Required] Defines the environment in which the config value is loaded env: { // [Required] Load config value on the server server: true, // [Required] Load config value on the client client: true, // [Optional] Load config value at config-time (when Vike loads +config.js files) config: false, // default value // [Optional] Load config value only in prod/dev (`true`/`false` => only prod/dev) production: undefined // default value }, // [Optional] Whether config values should be merged (instead of overridden) cumulative: false, // default value // [Optional] Whether all config values should be loaded at initialization. This // means that all values of all pages are always available. eager: false, // default value // [Optional] Whether the config value must be defined globally (not per-page) global: false, // default value // [Optional] Function called when the config value is loaded at config-time. Note // that `effect()` requires `env.config` to be `true`. effect({ configDefinedAt, configValue }) { if (someCondition) { // This config object is merged with the current one. return { meta: { // This can be the same or another hook/setting. someOtherSettingOrHook: { env: { server: true, client: false } } } } } }, // [Optional] Whether the config value affects Vite's configuration and therefore // whether a change during dev (HMR) should trigger Vite to reload its config. vite: false // default value } }}
// /pages/+config.ts// Environment: configimport type { Config } from 'vike/types'export default { meta: { someConfig: { // [Required] Defines the environment in which the config value is loaded env: { // [Required] Load config value on the server server: true, // [Required] Load config value on the client client: true, // [Optional] Load config value at config-time (when Vike loads +config.ts files) config: false, // default value // [Optional] Load config value only in prod/dev (`true`/`false` => only prod/dev) production: undefined // default value }, // [Optional] Whether config values should be merged (instead of overridden) cumulative: false, // default value // [Optional] Whether all config values should be loaded at initialization. This // means that all values of all pages are always available. eager: false, // default value // [Optional] Whether the config value must be defined globally (not per-page) global: false, // default value // [Optional] Function called when the config value is loaded at config-time. Note // that `effect()` requires `env.config` to be `true`. effect({ configDefinedAt, configValue }) { if (someCondition) { // This config object is merged with the current one. return { meta: { // This can be the same or another hook/setting. someOtherSettingOrHook: { env: { server: true, client: false } } } } } }, // [Optional] Whether the config value affects Vite's configuration and therefore // whether a change during dev (HMR) should trigger Vite to reload its config. vite: false // default value } }} satisfies Config
TypeScript
Similar to Vike.PageContext, you can extend (or refine) Vike's Config type by using the global interface Vike.Config.
// /pages/+config.ts// Environment: configimport type { Config } from 'vike/types'export default { meta: { sqlQuery: { env: { server: true } } }} satisfies Configdeclare global { namespace Vike { interface Config { /** The SQL query that retrieves the page's data */ sqlQuery?: (id: number) => string } }}// Add this line if TypeScript complains about `declare global`.// https://typescriptlang.org/docs/handbook/2/modules.html#how-javascript-modules-are-definedexport {}
// /pages/product/@id/+config.tsimport type { Config } from 'vike/types'export default { // ✅ Typed sqlQuery: (id) => `SELECT { name, price } FROM products WHERE id = "${id}";`} satisfies Config