Edit

+onRenderClient() hook

Environment: client

The onRenderClient() hook defines how pages are rendered/hydrated on the client-side.

The hooks onRenderClient() and onRenderHtml() are essentially the glue code between Vike and your UI framework (React/Vue/Solid/...).

If you use a UI framework Vike extension vike-react/vike-vue/vike-solid, then you don't need to (and shouldn't) define onRenderClient() as it's already defined by vike-react/vike-vue/vike-solid.

// +onRenderClient.js
// Environment: client
 
export { onRenderClient }
 
import { renderToDom, hydrateDom } from 'some-ui-framework'
 
async function onRenderClient(pageContext) {
  const { Page } = pageContext
 
  // SPA:
  await renderToDom(Page)
 
  // SSR:
  await hydrateDom(Page)
}
// +onRenderClient.ts
// Environment: client
 
export { onRenderClient }
 
import type { PageContextClient } from 'vike/types'
import { renderToDom, hydrateDom } from 'some-ui-framework'
 
async function onRenderClient(pageContext: PageContextClient) {
  const { Page } = pageContext
 
  // SPA:
  await renderToDom(Page)
 
  // SSR:
  await hydrateDom(Page)
}

Where:

  • pageContext.Page is the +Page value of the page that is being rendered.
  • pageContext is a subset of the pageContext defined on the server-side.

You can use passToClient to determine what subset of pageContext is sent to the browser.

You should resolve onRenderClient() only after the page is rendered and visible to the user, because Vike then triggers actions such as setting the scroll position and calling the +onPageTransitionEnd hook (typically used for page transition animations).

See Render Modes (SPA, SSR, SSG, HTML-only) and Client Routing for illustrations of conditional DOM hydration, for supporting both SPA and SSR.

SPA vs SSR

When implementing SSR, the client-side onRenderClient() hook works in tandem with the server-side onRenderHtml() hook: the server-side onRenderHtml() hook renders the page to HTML and the client-side onRenderClient() hook hydrates the HTML.

When implementing an SPA, then the client-side onRenderClient() hook is solely responsible for rendering the page. (There is still a server-side onRenderHtml() hook but it only renders the HTML shell; it doesn't render pageContext.Page to HTML.)

See Render Modes (SPA, SSR, SSG, HTML-only).

Multiple onRenderClient() hooks

If you create /pages/star-wars/+onRenderClient.js then you define how /pages/star-wars/+Page.js is rendered while overriding the global onRenderClient() hook.

By defining multiple onRenderClient() hooks, you can define different renderings for different pages. See Multiple renderer/.

Abort

Upon client-side navigation, Vike waits for the previous onRenderClient() call to finish before rendering the next page.

To prevent the previous onRenderClient() call from delaying the next page, you can use pageContext.abortSignal to abort it:

// +onRenderClient.js
// Environment: client
 
import { renderToDom } from 'some-ui-framework'
 
export async function onRenderClient(pageContext) {
  const { Page } = pageContext
  const rendering = renderToDom(Page)
 
  await new Promise((resolve) => {
    // Resolve onRenderClient() once the page is rendered:
    rendering.onPageRendered(resolve)
    // Also resolve (i.e. unblock) if the user navigates to another page:
    pageContext.abortSignal.addEventListener('abort', resolve)
  })
}
// +onRenderClient.ts
// Environment: client
 
import type { PageContextClient } from 'vike/types'
import { renderToDom } from 'some-ui-framework'
 
export async function onRenderClient(pageContext: PageContextClient) {
  const { Page } = pageContext
  const rendering = renderToDom(Page)
 
  await new Promise((resolve) => {
    // Resolve onRenderClient() once the page is rendered:
    rendering.onPageRendered(resolve)
    // Also resolve (i.e. unblock) if the user navigates to another page:
    pageContext.abortSignal.addEventListener('abort', resolve)
  })
}

Your other hooks (e.g. +data) can also use pageContext.abortSignal, see API > pageContext.abortSignal.

See also