🧠 The meta
setting is an advanced feature. Instead of directly using meta
yourself, consider using .server.js
/ .client.js
/ .shared.js
or Vike Extensions (which use meta
under the hood).
⚠️
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.
The meta
settings enables you to create:
New settings .
New hooks .
Example: onInit()
or onBeforeFetchingData()
Modifying the environment of existing hooks .
Example: dataEndpointUrl
This example showcases a simple data fetching logic that uses URL data endpoints (e.g. a RESTful API).
// /pages/countries/+dataEndpointUrl.js
// Environment: server
export default 'https://restcountries.com/v3.1/all'
// /pages/+config.js
// Environment: config
export default {
meta: {
dataEndpointUrl: {
env: {
server: true ,
// Load the value of /pages/**/+dataEndpointUrl.js only on the server
client: false
}
}
}
}
// /pages/+data.js
// Environment: server
export { 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.js
export 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.js
export 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: config
export default {
meta: {
sql: {
env: {
server: true ,
// Load the value of /pages/**/+sql.js only on the server
client: false
}
}
}
}
// /pages/+onBeforeRender.js
// Environment: server
export { 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 extension vike-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.
Usage
// /pages/about-us/+title.js
export default 'About Us'
// /pages/about-us/+description.js
export 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.js
export default {
title: 'About Us' ,
description: 'Who we are, our values, and what we stand for.'
}
See API > Config Files .
Your pages can also use data that was dynamically fetched in order to determine <title>
and <meta name="description">
:
// /pages/product/+title.js
export default pageContext => pageContext.data.product.name
// /pages/product/+description.js
export default pageContext => {
const { product } = pageContext.data
return `${ product . name } - ${ product . description }`
}
Implementation
// /pages/+config.ts
import 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: client
export { 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 & client
export { 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/+onRenderHtml.js
// Environment: server
export { onRenderHtml }
import { getTitle, getDescription } from './utils'
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
import { renderToHtml } from 'some-ui-framework'
async function onRenderHtml ( pageContext ) {
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: browser and server
export { Layout } from '../layouts/Responsive'
// /pages/admin/+Layout.js
// Environment: browser and server
export { Layout } from '../layouts/Dashboard'
// /pages/+Layout.js
// Environment: browser and server
// The default layout in case the page doesn't set one
export { Layout } from '../layouts/LayoutDefault'
// /pages/+config.js
// Environment: config
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
}
}
}
// /pages/+onRenderHtml.js
// Environment: server
export { onRenderHtml }
import { renderToHtml } from 'some-ui-framework'
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
async function onRenderHtml ( pageContext ) {
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.jsx
// Environment: browser
export { onRenderClient }
import { renderDom } from 'some-ui-framework'
async function onRenderClient ( pageContext ) {
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.ts
import 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.ts
export { config }
import type { Config, ConfigEffect } from 'vike/types'
const config = {
meta: {
dataIsomorph: {
env: { config: true },
effect
}
}
} satisfies Config
const 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 }
}
}
}
}
}
Example:
API
// /pages/+config.js
// Environment: config
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 }
}
}
}
}
}
}
}
}
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: config
export 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.ts
import { Config } from 'vike/types'
export default {
// ✅ Typed
sqlQuery : ( id ) => `SELECT { name, price } FROM products WHERE id = "${ id }";`
} satisfies Config
sqlQuery
is a custom setting, see Example: sql
.
If you define a cumulative
config , then also make sure to use the global interface Vike.ConfigResolved
.
// /pages/+config.ts
// Environment: config
export default {
meta: {
viewport: {
env: { server: true }
}
}
}
declare global {
namespace Vike {
interface Config {
viewport ?: number
}
interface ConfigResolved {
// Set the pageContext.config.viewport type
viewport ?: number []
}
}
}
See also