Data Fetching

We recommend taking the React Tour or Vue Tour before reading this guide. The tour explains what the files /renderer/_default.page.* are about.

onBeforeRender()

The usual way to fetch data is to use a onBeforeRender() hook.

// /pages/movies.page.server.js
// Environment: Node.js

import fetch from 'node-fetch'

export { onBeforeRender }

async function onBeforeRender(pageContext) {
  // `.page.server.js` files always run in Node.js; we could use SQL/ORM queries here.
  const response = await fetch('https://movies.example.org/api')
  let movies = await response.json()

  // `movies` will be serialized and passed to the browser; we select only the data we
  // need in order to minimize what is sent to the browser.
  movies = movies.map(({ title, release_date }) => ({title, release_date}))

  // We make `movies` available as `pageContext.pageProps.movies`
  const pageProps = { movies }
  return {
    pageContext: {
      pageProps
    }
  }
}
// /renderer/_default.page.server.js
// Environment: Node.js

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml, createElement } from 'some-ui-framework'

// We tell `vite-plugin-ssr` to make `pageContext.pageProps` available in the browser.
export const passToClient = ['pageProps']

export { render }

async function render(pageContext) {
  const { Page, pageProps } = pageContext
  const pageHtml = await renderToHtml(
    // We pass `pageProps` to `Page`
    createElement(Page, pageProps)
  )
  /* JSX:
  const pageHtml = await renderToHtml(<Page {...pageProps} />)
  */

  return escapeInject`<html>
    <div id='view-root'>
      ${dangerouslySkipEscape(pageHtml)}
    </div>
  </html>`
}
// /renderer/_default.page.client.js
// Environment: Browser

export { render }

import { hydrateDom, createElement } from 'some-ui-framework'

async function render(pageContext) {
  // `Page` is also available in the browser
  const { Page } = pageContext
  // Thanks to `passToClient = ['pageProps']` our `pageContext.pageProps` is
  // available here in the browser.
  const { Page, pageProps } = pageContext
  await hydrateDom(
    // We pass `pageProps` to `Page`
    createElement(Page, pageProps),
    document.getElementById('view-root')
  )
  /* JSX:
  await hydrateDom(<Page {...pageProps} />, document.getElementById('view-root'))
  */
}
// /pages/movies.page.js
// Environment: Browser, Node.js

export { Page }

// In the `render()` functions above, we pass `pageContext.pageProps` to `Page`
function Page(pageProps) {
  const { movies } = pageProps
  // ...
}

Note that vite-plugin-ssr doesn't know anything about pageProps: it's just an object we create in order to conveniently pass all the page's props at once.

Custom Exports

For more convenience, instead of creating a new onBeforeRender() hook for each page, we can define onBeforeRender() once in /renderer/_default.page.server.js while using a Custom Export/Hook.

For example:

// /pages/user.page.js
export const query = { modelName: 'User', select: ['firstName', 'lastName'] }
// /pages/product.page.js
export const query = { modelName: 'Product', select: ['name', 'price'] }
// /renderer/_default.page.server.js
// Environment: Node.js

export { onBeforeRender }

import { runQuery } from 'some-sql-engine'

async function onBeforeRender(pageContext) {
  // Our `query` export values are available at `pageContext.exports.query`
  const { query } = pageContext.exports
  const data = await runQuery(query)
  const pageProps = { data }
  return { pageContext: { pageProps } }
}

Client Routing

The .page.server.js files are loaded only in Node.js; our data fetching code is always executed in Node.js. This is convenient as it makes writing data fetching code easier.

If we use Client Routing, then we have the option to define onBeforeRender() in .page.js instead of .page.server.js. In that case, onBeforeRender() is not only called in Node.js but also in the browser (upon page navigation).

In general, we recommend defining onBeforeRender() in .page.server.js (even when using Client Routing) but, if we want to minimize requests made to our Node.js server, then we can define onBeforeRender() in .page.js instead.

GraphQL

When using GraphQL, we define GraphQL queries/fragments on a component-level, while we fetch the GraphQL data in onBeforeRender().

In general, with vite-plugin-ssr, we have full control over rendering which means that integrating GraphQL is mostly only a matter of following the official SSR guide of the tool we want to use.

Store (Vuex/Redux...)

When using a global store (Vuex, Redux, PullState, ...), our components don't access the fetched data directly. Instead, our components only access the store, while the fetched data merely determines the initial state of the store.

In general, with vite-plugin-ssr, we have full control over rendering which means that integrating a global store is mostly only a matter of following the official SSR guide of the tool we want to use.