# Server-Side Rendering
WARNING
Requires Vue 2.6+ with serverPrefetch
support
# Vue CLI plugin
I made a plugin for vue-cli (opens new window) so you can transform your vue-apollo
app into an isomorphic SSR app in literally two minutes! ✨🚀
In your vue-cli 3 project:
vue add @akryum/ssr
# Component prefetching
TIP
Follow the offical SSR guide (opens new window) 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 (opens new window) and data prefetching (opens new window) 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 (opens new window), 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>