⚠️
We recommend using such advanced capability, which can be complex to use, only if you have a clear reason why simpler alternatives aren't an option for you.
// /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/+data.js// Environment: serverexport { data }import fetch from 'node-fetch'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) // ...}
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/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/+config.js// Environment: configexport default { meta: { sql: { env: { server: true, // Load the value of /pages/**/+sql.js only on the server client: false } } }}
// /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) // ...}
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/+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/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() // ...}
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: browser and serverexport { Layout } from '../layouts/Responsive'
// /pages/admin/+Layout.js// Environment: browser and serverexport { Layout } from '../layouts/Dashboard'
// /pages/+Layout.js// Environment: browser 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 } }}
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.js// Environment: configexport 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 } } } } } } } }}
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: configexport default { meta: { sqlQuery: { env: { server: true } } }}declare 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