Client Hydration

If data is retrieved on the server during SSR and is used to generate any fragment of the rendered markup, the same data needs to be made available on the client prior to initialization. Or the same data needs to be retrieved, on the client, prior to initialization. Of course this is non-ideal, if an API call already took place on the server, there's no reason to repeat it client-side for first-render.

A common technique for solving this problem is to append a <script> snippet to the rendered markup containing serialized data, so it can be picked up (hydrated) during app initialization. Nuxt.js and Next.js use __NUXT__ and __NEXT_DATA__ for this, respectively.

TIP

This very nicely covered in this Google Developers article by Jason Miller and Addy Osmani.

getHydrationScript()

fastify-vite uses a similar approach, while allowing for a deep level of customization. For instance, below is the code used to generate the serialized hydration data for fastify-vite-vue and fastify-vite-react renderer adapters. The function used in both is identical, and can be overriden if you set the render.getHydrationScript option with a function of your own.

getHydrationScript()

Generates <script> tag with serialized data obtained during SSR


function getHydrationScript (req, context, hydration) {
  const globalData = req.$global
  const data = req.$data
  const payload = req.$payload
  const api = req.api ? req.api.meta : null

  let hydrationScript = ''

  if (globalData || data || payload || api) {
    hydrationScript += '<script>'
    if (globalData) {
      hydrationScript += `window[Symbol.for('kGlobal')] = ${devalue(globalData)}\n`
    }
    if (data) {
      hydrationScript += `window[Symbol.for('kData')] = ${devalue(data)}\n`
    }
    if (payload) {
      hydrationScript += `window[Symbol.for('kPayload')] = ${devalue(payload)}\n`
    }
    if (api) {
      hydrationScript += `window[Symbol.for('kAPI')] = ${devalue(api)}\n`
    }
    hydrationScript += '</script>'
  }

  return hydrationScript
}

The first parameter it receives is Fastify's Request object, followed by the SSR rendering context and the hydration key from the plugin options. Typically you won't want to change these.

TIP

I found out later SolidJS has a similar abstraction for this. Sadly, we do not have a renderer adapter for SolidJS yet but it certainly looks very feasible. Pull Requests welcome.

hydrate()

On the client, you can use the hydrate() function provided by fastify-vite-vue/client:

hydrate()

Vue version

function hydrate (app) {
  const hydration = {
    $global: window[kGlobal],
    $data: window[kData],
    $payload: window[kPayload],
    $payloadPath: () => `/-/payload${document.location.pathname}`,
    $api: new Proxy({ ...window[kAPI] }, {
      get: manifetch({
        prefix: '',
        fetch: (...args) => fetch(...args),
      }),
    }),
  }
  assign(app.config.globalProperties, hydration)
  delete window[kGlobal]
  delete window[kData]
  delete window[kPayload]
  delete window[kAPI]
}

Or fastify-vite-react/client:

hydrate()

React version

function hydrate (app) {
  const context = {
    $global: window[kGlobal],
    $payloadPath: () => `/-/payload${document.location.pathname}`,
    $payload: window[kPayload],
    $data: window[kData],
    $api: new Proxy({ ...window[kAPI] }, {
      get: manifetch({
        prefix: '',
        fetch: (...args) => fetch(...args),
      }),
    }),
  }
  delete window[kGlobal]
  delete window[kData]
  delete window[kPayload]
  delete window[kAPI]
  return context
}

Both are practically the same, and like getHydrationScript(), can also very easily be replaced with your own if you need to. In that case you'd provide it directly in your application's client entry point instead of importing from fastify-vite-vue/client or fastify-vite-react/client.