clientRouting

Environment: client.

If you don't use a UI framework Vike extension (vike-react/vike-vue/vike-solid) then Vike does Server Routing by default.

You can opt into Client Routing by:

  1. Setting clientRouting: true.
    // +config.js
    export default {
      // Enable Client Routing
      clientRouting: true
    }
  2. Adapting your onRenderClient() hook to hydrate the DOM only when rendering the first page.

Instead of manually integrating Client Routing yourself, you can use a UI framework Vike extension (vike-react/vike-vue/vike-solid) which already integrates Client Routing. You can use Bati to scaffold an app that uses vike-react/vike-vue/vike-solid.

Examples

React example:

Vue example:

Usage

  1. Set clientRouting to true:

    // /pages/+config.js
    export default {
      clientRouting: true
    }
  2. Adapt your onRenderClient() hook:

    // /renderer/+onRenderClient.js
    // Environment: browser
     
    export { onRenderClient }
     
    import { renderToDom, hydrateDom } from 'some-ui-framework'
     
    async function onRenderClient(pageContext) {
      // `pageContext.isHydration` is set by Vike and is `true` when the page
      // is already rendered to HTML.
      if (pageContext.isHydration) {
        // We hydrate the first page. (Since we do SSR, the first page is already
        // rendered to HTML and we merely have to hydrate it.)
        await hydrate(pageContext.Page)
      } else {
        // We render a new page. (When the user navigates to a new page.)
        await renderToDom(pageContext.Page)
      }
    }

    Note that pageContext is completely discarded and created anew upon page navigation. That's why it's called pageContext (and not appContext).

    See Render Modes for another illustration of conditional DOM hydration.

You can keep using <a href="/some-url"> links: the Client Router automatically intercepts clicks on <a> elements.

You can skip the Client Router for individual <a> links by adding the rel="external" attribute, e.g. <a rel="external" href="/some/url">The Client Router won't intercept me</a>.

Hooks

You can use the following hook to implement initialization after the hydration has finished:

And following hooks ot implement page transition animations:

These hooks are only available if you use Client Routing.

Settings

Page settings:

Anchor link options:

Programmatic navigation

You can use the function navigate('/some/url') to programmatically navigate the user to a new page.

See API > navigate().

State initialization

Usually, when using tools such as Apollo GraphQL, Redux or Vuex, you determine the initial state of your UI on the server-side while rendering HTML, and then initialize the client-side with that initial state.

Depending on the tool, you do either one of the following:

  • You initialize the state once.
  • You re-initialize the state on every page navigation.

Reading Recommendation.

To initialize once:

// /renderer/+onRenderHtml.js
// Environment: server
 
export { onRenderHtml }
 
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
import { renderToHtml } from 'some-ui-framework'
import { getInitialState } from './getInitialState'
 
// The `onRenderHtml()` hook is called only for the first page.
// (Whereas `onBeforeRender()` is called as well upon page navigation.)
async function onRenderHtml(pageContext) {
  const initialState = await getInitialState()
 
  // We use `initialState` for rendering the HTML, so that the HTML contains
  // the content of `initialState`.
  const pageHtml = await renderToHtml(pageContext.Page, initialState)
 
  const documentHtml = escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div>${dangerouslySkipEscape(pageHtml)}</div>
      </body>
    </html>`
 
  return {
    documentHtml,
    pageContext: {
      initialState
    }
  }
}
// /renderer/+config.js
// Environment: config
 
export default {
  passToClient: ['initialState']
}
// /renderer/+onRenderClient.js
// Environment: browser
 
export { onRenderClient }
 
import { initClientSide } from './initClientSide'
 
async function onRenderClient(pageContext) {
  // The first page is rendered to HTML and `pageContext.isHydration === true`
  if (pageContext.isHydration) {
    // `pageContext.initialState` is available here
    initClientSide(pageContext.initialState)
  } else {
    // Note that `pageContext.initialState` isn't available here,
    // since our `onRenderHtml()` hook is only called for the first page.
  }
 
  // ...
}

To initialize on every page navigation:

// /renderer/+onBeforeRender.js
// Environment: server
 
export { onBeforeRender }
 
import { getInitialState } from './getInitialState'
 
// The `onBeforeRender()` hook is called for the first page as well as upon page navigation.
// (Whereas `onRenderHtml()` is called only for the first page.)
async function onBeforeRender() {
  const initialState = await getInitialState()
  return {
    pageContext: {
      initialState
    }
  }
}
// /renderer/+onRenderClient.js
// Environment: browser
 
export { onRenderClient }
 
import { initClientSide } from './initClientSide'
 
async function onRenderClient(pageContext) {
  // We initialize the state for every page rendering. So not only
  // the first page but also any subsequent page navigation.
  initClientSide(pageContext.initialState)
 
  // ...
}

See also