👉
We recommend using this advanced capability — which can be complex — only if you have a clear reason why simpler alternatives aren't an option for you.
// /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.ts// Environment: serverexport { data }export type Data = Awaited<ReturnType<typeof data>>import type { PageContextServer } from 'vike/types'import fetch from 'node-fetch'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.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.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.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.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.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.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.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.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.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.ts// Environment: server & clientimport type { PageContext } from 'vike/types'export { getTitle, getDescription }function getTitle(pageContext: 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: 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.ts// Environment: client and serverexport { Layout } from '../layouts/Responsive'
// /pages/admin/+Layout.ts// Environment: client and serverexport { Layout } from '../layouts/Dashboard'
// /pages/+Layout.ts// Environment: client and server// The default layout in case the page doesn't set oneexport { Layout } from '../layouts/LayoutDefault'
// /pages/+config.ts// Environment: configimport type { Config } from 'vike/types'export 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 } }} satisfies Config
// /pages/+onRenderHtml.ts// Environment: serverexport { onRenderHtml }import type { PageContextServer } from 'vike/types'import { renderToHtml } from 'some-ui-framework'import { escapeInject, dangerouslySkipEscape } from 'vike/server'async function onRenderHtml(pageContext: PageContextServer) { const { Page, Layout } = pageContext.config const pageHtml = renderToHtml(<Layout><Page/></Layout>) return escapeInject`<!DOCTYPE html> <html> <body> <div id="root">${dangerouslySkipEscape(pageHtml)}</div> </body> </html>`}
Note how we use pageContext.config.Page instead of pageContext.Page: that's because pageContext.Page is just an alias for pageContext.config.Page.
// /pages/+onRenderClient.tsx// Environment: clientexport { onRenderClient }import type { PageContextClient } from 'vike/types'import { renderDom } from 'some-ui-framework'async function onRenderClient(pageContext: PageContextClient) { const { Page, Layout } = pageContext.config renderDom( <Layout><Page/></Layout>, document.getElementById("root") )}
Example: modify +data env
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.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:
// /pages/+config.tsexport { config }import type { Config, ConfigEffect } from 'vike/types'const config = { meta: { dataIsomorph: { env: { config: true }, effect } }} satisfies Configconst effect: ConfigEffect = ({ configDefinedAt, configValue }) => { if (typeof configValue !== 'boolean') { throw new Error(`${configDefinedAt} should be a boolean`) } if (configValue === true) { return { meta: { data: { env: { server: true, client: true } } } } }}
// /pages/+config.ts// Environment: configimport type { Config } from 'vike/types'export default { meta: { someSettingOrHook: { // [Required] Defines the environment in which the config value is loaded. env: { // Load the config value on the server. [Required] server: true, // Load the config value on the client. [Required] client: true, // Load the config value at config-time (when Vike loads +config.js files). [Optional] config: false // default value }, // [Optional] Whether config values should be merged (instead of overridden). cumulative: false, // default value // [Optional] Function called when the config value is loaded at config-time. // 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 } } } } } } } }} 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 } }}// The following isn't needed in this example, but if you define Vike.Config// in a .d.ts file then make sure there is at least one export/import statement.// Tell TypeScript this file isn't an ambient module:export {}
// /pages/product/@id/+config.tsimport { Config } from 'vike/types'export default { // ✅ Typed sqlQuery: (id) => `SELECT { name, price } FROM products WHERE id = "${id}";`} satisfies Config