onBeforeRender() hook

The onBeforeRender() hook is for expert Vike users. If you're new to Vike, we recommend using the data() hook instead, and/or Vike extensions for automatically integrating data fetching tools (RPC with Telefunc/tRPC, REST with Tanstack Query, GraphQL with Apollo/Relay, etc).

The most notable use case for the onBeforeRender() hook is deep integration with data fetching tools.

For simple data fetching needs, use the data() hook instead. As a strategy to decide which one to use, always first try to use data() and only use onBeforeRender() as a fallback if data() doesn't work out.

The onBeforeRender() hook can be used to orchestrate multiple custom hooks and settings, see:

Another common use case is to use onBeforeRender() as a global initializing hook. (As a temporary workaround until #962 - New hook onBoot() is implemented.)

onBeforeRender() VS data()

The central difference between the two hooks is that the value returned by data() always sets the value of pageContext.data, whereas onBeforeRender() can set multiple pageContext values.

// /pages/some-page/+data.js
 
export function data() {
  const someValue = /* ... */
  // pageContext.data === someValue
  return someValue
}
// /pages/some-page/+onBeforeRender.js
 
export function onBeforeRender() {
  const someValue1 = /* ... */
  const someValue2 = /* ... */
  // pageContext.prop1 === someValue1
  // pageContext.prop2 === someValue2
  return {
    pageContext: {
      prop1: someValue1,
      prop2: someValue2
    }
  }
}

Another difference is that the entire pageContext.data value is always sent to the client-side, whereas with onBeforeRender() you can (and have to) decide which values are sent to the client-side by using passToClient.

In a nutshell: while onBeforeRender() requires more manual work, it also gives you more control.

Client-side

Like data(), the onBeforeRender() hook always runs on the server-side by default. But you can use the file extension .shared.js to make Vike run onBeforeRender() on the client-side, see API > data() hook > Client-side.

onBeforeRender() + meta

Using onBeforeRender() together with custom hooks and settings (see meta) is a powerful technique enabling you to implement your own tailored DX.

For example.

A custom setting +sql.js (created with meta):

// /pages/user/+sql.js
export default { modelName: 'User', select: ['firstName', 'lastName'] }
// /pages/product/+sql.js
export default { modelName: 'Product', select: ['name', 'price'] }

A single global onBeforeRender() hook orchestrating the customm setting:

// /pages/+onBeforeRender.js
 
import { runQuery } from 'some-sql-engine'
 
export async function onBeforeRender(pageContext) {
  // The value exported by /pages/**/+sql.js is available at pageContext.config.sql
  const { sql } = pageContext.config
  const data = await runQuery(sql)
  // ...
}

See full implementation at API > meta > Example: sql.

Override

There can be only one onBeforeRender() hook per page. For example, if you define a global onBeforeRender() hook at /pages/+onBeforeRender.js as well as a page-specific one at /pages/star-wars/+onBeforeRender.js then the page-specific one overrides the global one. See API > Config > Inheritance.

If you want multiple onBeforeRender() hooks, then consider:

  • Creating custom hooks instead: you can then use one global onBeforeRender() hook that orchestrates many custom hooks.
  • Using data() hooks: you can then use one global onBeforeRender() while using page-specific data() hooks.

You can also suppress globally defined onBeforeRender() hooks on a per-page basis:

// /pages/+onBeforeRender.js
 
// Some global onBeforeRender() hook
export default () => {
  // ...
}
// /pages/some-page/+config.js
 
export default {
  // Suppress the global onBeforeRender() hook
  onBeforeRender: null
}

Advanced example

The following is an advanced example of using onBeforeRender() with meta in order to integrate data fetching tools. In particular, this approach can be used for advanced integration with GraphQL tools.

If you use a custom renderer instead of vike-react/vike-vue/vike-solid, then you can modify your onRenderHtml()/onRenderClient() hooks instead of doing the following.

// /pages/+config.js
// Environment: config
 
export default {
  // Pass the GraphQL cache to the client-side
  passToClient: ['cache'],
  // Modify/create hooks
  meta: {
    onBeforeRender: {
      // Modify the onBeforeRender() hook to run on both the server- and client-side
      env: { client: true, server: true }
    },
    // Create new hook
    onBeforeRenderHtml: {
      env: { server: true }
    },
    // Create new hook
    onBeforeRenderClient: {
      env: { client: true }
    }
  }
}

See:

// /pages/+onBeforeRender.js
// Environment: server or client
 
export async function onBeforeRender(pageContext) {
  // When run on the server-side
  if (pageContext.config.onBeforeRenderHtml) {
    const { pageContext } = await onBeforeRenderHtml(pageContext)
    return { pageContext }
  }
  // When run on the client-side
  if (pageContext.config.onBeforeRenderClient) {
    await onBeforeRenderClient(pageContext)
  }
}
// /pages/+onBeforeRenderHtml.jsx
// Environment: server
 
import { renderToHtml } from 'my-graphql-tool/server'
 
export async function onBeforeRenderHtml(pageContext) {
  const { Page } = pageContext
  // `cache` contains the data fetched by GraphQL
  const { cache, html } = await renderToHtml(<Page />)
  return {
    pageContext: {
      pageHtml: html,
      cache
    }
  }
}
// /pages/+onBeforeRenderClient.jsx
// Environment: client
 
import { hydrate } from 'my-ui-framework/client'
import { CacheProvider } from 'my-graphql-tool/client'
 
export function onBeforeRenderClient(pageContext) {
  const { Page, cache } = pageContext
  hydrate(
    // Re-use the `cache` data that was fetched on the server-side
    <CacheProvider cache={cache}>
      <Page />
    </CacheProvider>,
    document.getElementById('root')
  )
}

TypeScript

export { onBeforeRender }
 
import type { OnBeforeRenderAsync } from 'vike/types'
 
const onBeforeRender: OnBeforeRenderAsync = async (
  pageContext
): ReturnType<OnBeforeRenderAsync> => {
  // ...
}

Don't omit ReturnType<OnBeforeRenderAsync> (don't write const onBeforeRender: OnBeforeRenderAsync = async (pageContext) => {), otherwise TypeScript won't strictly check the return type for unknown extra properties: see this TypeScript playground and issue.

See API > pageContext > Typescript for more information on how to extend pageContext with your own extra properties.

See also