Environment: Browser

By default vite-plugin-ssr does Server Routing. We can do Client Routing instead by using useClientRouter() instead of getPage().

In general, we recommend Server Routing, see Server Routing VS Client Routing.

React example:

Vue example:

Example showcasing all useClientRouter()'s options:

// Environment: Browser

import { render, hydrate } from 'some-view-framework'
import { useClientRouter } from 'vite-plugin-ssr/client/router'

const { hydrationPromise } = useClientRouter({
  async render(pageContext) {
    // `pageContext.isHydration` is set by `vite-plugin-ssr` and is `true` when the page
    // is already rendered to HTML.
    if (pageContext.isHydration) {
      // When we render 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 {
      // When the user navigates to a new page.
      await render(pageContext.Page)

  // If `ensureHydration: true` then `vite-plugin-ssr` ensures that the first render is always
  // a hydration. I.e. the first `render()` call is never interrupted — even if the user clicks
  // on a link. Default value: `false`.
  // If we use Vue, we set `ensureHydration: true` to avoid "Hydration Mismatch" errors.
  // If we use React, we leave `ensureHydration: false` for a slight performance boost.
  ensureHydration: true,

  // See `Link prefetching` section below. Default value: `false`.
  prefetchLinks: true,

  // To create custom page transition animations

hydrationPromise.then(() => {
  console.log('Hydration finished; page is now interactive.')

function onTransitionStart() {
  console.log('Page transition start')
  // For example:
function onTransitionEnd() {
  console.log('Page transition end')
  // For example:

Note that pageContext is completely discarded and loaded anew upon page navigation; that's why the context object is called pageContext and not appContext.

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

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

We can use navigate('/some/url') to programmatically navigate our user to a new page.

By default, the Client-side Router scrolls to the top of the page upon page change; we can use <a keep-scroll-position /> / navigate('/some/url', { keepScrollPosition: true }) if we want to preserve the scroll position instead. (Useful for Nested Routes.)

Also see:

By default, the static assets of a page /some-url are loaded as soon as the user hovers his mouse over a link <a href="/some-url">. This means that static assets are often already loaded before even the user clicks on the link.

We can prefetch even more eagerly by setting useClientRouter({ prefetchLinks: true }): the links are then prefetched as soon as they appear in the user's browser viewport.

We can override this option for individual links: <a data-prefetch="false" href="/some-url" />.

Only static assets are prefetched; prefetching pageContext is work-in-progress, see #246.