Data Fetching

To fetch data:

  1. We create a .page.server.js file. (For a page movies.page.js, we create movies.page.server.js.)
  2. We export function onBeforeRender () { /*...*/ } in our newly created .page.server.js.
  3. (Optionally) We add new property names to passToClient: string[] in _default.page.server.js.

For example:

// /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 over the network.
  movies = movies.map(({ title, release_date }) => ({title, release_date}))

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

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml, createElement } from 'some-view-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(
    createElement(Page, pageProps)
  )
  /* JSX:
  const pageHtml = await renderToHtml(<Page {...pageProps} />)
  */

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

import { getPage } from 'vite-plugin-ssr/client'
import { hydrateToDom, createElement } from 'some-view-framework'

hydrate()

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

export { Page }

// In our `render()` and `hydrate()` 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 an object we create to conveniently hold all props of the root component Page.

We could have defined movies directly as pageContext.movies instead of pageContext.pageProps.movies, but it's cumbersome: our render/hydrate function would then need to know what pageContext should be passed to the root component Page.

Stateful Component

We can also fetch data by using a stateful component. But note that with this technique, the fetched data is not rendered to HTML.

We usually need to make the route parameters available to the data-fetching stateful component:

  1. Make pageContext.routeParams available in the browser by adding it to passToClient.push('routeParams').
  2. Pass pageContext.routeParams to the stateful component.

GraphQL

When using GraphQL, e.g. with Apollo GraphQL or Relay, 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 a matter of following the official SSR guide of the tool we want to use:

Store (Vuex/Redux...)

When using a global store (e.g. with Vuex, Redux, or PullState), our components don't access the fetched data directly. Instead, our components access the store, while the fetched data determines the store state.

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

Edit this page