Server-Side Rendering
Outdated
This guide is outdated and needs rework for Vue 3 and vue-apollo 4. Contributions welcome!
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/ssrComponent prefetching
Install the SSR utils with:
npm install --save @vue/apollo-ssrOr:
yarn add @vue/apollo-ssrTIP
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 { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core'
// 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 { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createStore } from 'vuex'
import VueApollo from '@vue/apollo-option'
import { createApolloClient } from './apollo'
import App from './ui/App.vue'
import routes from './routes'
import storeOptions from './store'
function createMyApp (context) {
const router = createRouter({
history: createWebHistory(),
routes,
})
const store = createStore(storeOptions)
// 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 = createApolloProvider({
defaultClient: apolloClient,
})
const app = createApp(App)
app.use(router)
app.use(store)
app.use(apolloProvider)
return {
app,
router,
store,
apolloProvider,
}
}
export default createMyAppClient 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,
}).mount('#app')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 * as 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.clients)
}
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>