Server-Side Rendering

WARNING

Requires Vue 2.6+ with serverPrefetch support

Vue CLI plugin

I made a plugin for vue-cli so you can transform your vue-apollo app into an isomorphic SSR app in literary two minutes! ✨🚀

In your vue-cli 3 project:

vue add @akryum/ssr

More info

Component prefetching

TIP

Follow the offical SSR guide to learn more about Server-Side Rendering with Vue.

By default with vue-server-renderer, all the GraphQL queries in your server-side rendered components will be prefetched automatically.

TIP

You have access to this in options like variables, even on the server!

Example:

export default {
  apollo: {
    allPosts: {
      query: gql`query AllPosts {
        allPosts {
          id
          imageUrl
          description
        }
      }`,
    }
  }
}

Example 2:

export default {
  apollo: {
    post: {
      query: gql`query Post($id: ID!) {
        post (id: $id) {
          id
          imageUrl
          description
        }
      }`,
      variables () {
        return {
          id: this.id,
        }
      },
    }
  }
}

Skip prefetching

You can skip server-side prefetching on a query with the prefetch option set to false.

Example that doesn't prefetch the query:












 




export default {
  apollo: {
    allPosts: {
      query: gql`query AllPosts {
        allPosts {
          id
          imageUrl
          description
        }
      }`,
      // Don't prefetch
      prefetch: false,
    }
  }
}

If you want to skip prefetching all the queries for a specific component, use the $prefetch option:




 












export default {
  apollo: {
    // Don't prefetch any query
    $prefetch: false,
    allPosts: {
      query: gql`query AllPosts {
        allPosts {
          id
          imageUrl
          description
        }
      }`,
    }
  }
}

Create Apollo client

It is recommended to create the apollo clients inside a function with an ssr argument, which is true on the server and false on the client.

If ssr is false, we try to restore the state of the Apollo cache with cache.restore, by getting the window.__APOLLO_STATE__ variable that we will inject in the HTML page on the server during SSR.

Here is an example:





















 
 
 
 
 
 
 
 
 
 
















// apollo.js

import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'

// Install the vue plugin
Vue.use(VueApollo)

// Create the apollo client
export function createApolloClient (ssr = false) {
  const httpLink = new HttpLink({
    // You should use an absolute URL here
    uri: ENDPOINT + '/graphql',
  })

  const cache = new InMemoryCache()

  // If on the client, recover the injected state
  if (!ssr) {
    if (typeof window !== 'undefined') {
      const state = window.__APOLLO_STATE__
      if (state) {
        // If you have multiple clients, use `state.<client_id>`
        cache.restore(state.defaultClient)
      }
    }
  }

  const apolloClient = new ApolloClient({
    link: httpLink,
    cache,
    ...(ssr ? {
      // Set this on the server to optimize queries when SSR
      ssrMode: true,
    } : {
      // This will temporary disable query force-fetching
      ssrForceFetchDelay: 100,
    }),
  })

  return apolloClient
}

Create app

Instead of creating our root Vue instance right away, we use a createApp function that accept a context parameter.

This function will be used both on the client and server entries with a different ssr value in the context. We use this value in the createApolloClient method we wrote previously.

Example for common createApp method:









 



























 




















// app.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'
import { sync } from 'vuex-router-sync'

import VueApollo from 'vue-apollo'
import { createApolloClient } from './apollo'

import App from './ui/App.vue'
import routes from './routes'
import storeOptions from './store'

Vue.use(VueRouter)
Vue.use(Vuex)

function createApp (context) {
  const router = new VueRouter({
    mode: 'history',
    routes,
  })

  const store = new Vuex.Store(storeOptions)

  // sync the router with the vuex store.
  // this registers `store.state.route`
  sync(store, router)

  // Vuex state restoration
  if (!context.ssr && window.__INITIAL_STATE__) {
    // We initialize the store state with the data injected from the server
    store.replaceState(window.__INITIAL_STATE__)
  }

  // Apollo
  const apolloClient = createApolloClient(context.ssr)
  const apolloProvider = new VueApollo({
    defaultClient: apolloClient,
  })

  return {
    app: new Vue({
      el: '#app',
      router,
      store,
      apolloProvider,
      ...App,
    }),
    router,
    store,
    apolloProvider,
  }
}

export default createApp

Client entry

The client entry is very simple -- we just call createApp with ssr being false:

// client-entry.js

import createApp from './app'

createApp({
  ssr: false,
})

Server entry

Nothing special is required apart from storing the Apollo cache to inject it in the client HTML. Learn more about server entry with routing and data prefetching in the official SSR guide.

Here is an example with vue-router and a Vuex store:



 






















 





// server-entry.js

import ApolloSSR from 'vue-apollo/ssr'
import createApp from './app'

export default () => new Promise((resolve, reject) => {
  const { app, router, store, apolloProvider } = createApp({
    ssr: true,
  })

  // set router's location
  router.push(context.url)

  // wait until router has resolved possible async hooks
  router.onReady(() => {
    // This `rendered` hook is called when the app has finished rendering
    context.rendered = () => {
      // After the app is rendered, our store is now
      // filled with the state from our components.
      // When we attach the state to the context, and the `template` option
      // is used for the renderer, the state will automatically be
      // serialized and injected into the HTML as `window.__INITIAL_STATE__`.
      context.state = store.state

      // ALso inject the apollo cache state
      context.apolloState = ApolloSSR.getStates(apolloProvider)
    }
    resolve(app)
  })
})

Use the ApolloSSR.getStates method to get the JavaScript code you need to inject into the generated page to pass the apollo cache data to the client.

In the page template, use the renderState helper:

{{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}}

Here is a full example:















 




<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>{{ title }}</title>
    {{{ renderResourceHints() }}}
    {{{ renderStyles() }}}
  </head>
  <body>
    <!--vue-ssr-outlet-->
    {{{ renderState() }}}
    {{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}}
    {{{ renderScripts() }}}
  </body>
</html>
Last Updated: 2/20/2019, 11:58:40 AM