# Server-Side Rendering ## Vue CLI Plugin I made a plugin for [vue-cli](http://cli.vuejs.org) so you can transform your vue-apollo app into an isomorphic SSR app in literary two minutes! ✨🚀 In your vue-cli 3 project: ```bash vue add @akryum/ssr ``` [More info](https://github.com/Akryum/vue-cli-plugin-ssr) ## Prefetch components On the queries you want to prefetch on the server, add the `prefetch` option. It can either be: - a variables object, - a function that gets the context object (which can contain the URL for example) and return a variables object, - `false` to disable prefetching for this query. If you are returning a variables object in the `prefetch` option, make sure it matches the result of the `variables` option. If they do not match, the query's data property will not be populated while rendering the template server-side. ::: danger You don't have access to the component instance when doing prefetching on the server. ::: Example: ```js export default { apollo: { allPosts: { // This will be prefetched query: gql`query AllPosts { allPosts { id imageUrl description } }`, } } } ``` Example 2: ```js export default { apollo: { post: { query: gql`query Post($id: ID!) { post (id: $id) { id imageUrl description } }`, prefetch: ({ route }) => { return { id: route.params.id, } }, variables () { return { id: this.id, } }, } } } ``` ### Skip prefetching Example that doesn't prefetch the query: ```js 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: ```js export default { apollo: { // Don't prefetch any query $prefetch: false, allPosts: { query: gql`query AllPosts { allPosts { id imageUrl description } }`, } } } ``` You can also put a `no-prefetch` attribute on any component so it will be ignored while walking the tree to gather the Apollo queries: ```vue ``` ## On the server In the server entry, you need to install `ApolloSSR` plugin into Vue: ```js import Vue from 'vue' import ApolloSSR from 'vue-apollo/ssr' Vue.use(ApolloSSR) ``` To prefetch all the apollo queries you marked, use the `ApolloSSR.prefetchAll` method. The first argument is the `apolloProvider`. The second argument is the array of component definition to include (e.g. from `router.getMatchedComponents` method). The third argument is the context object passed to the `prefetch` hooks (see above). It is recommended to pass the vue-router `currentRoute` object. It returns a promise resolved when all the apollo queries are loaded. Here is an example with vue-router and a Vuex store: ```js import Vue from 'vue' import ApolloSSR from 'vue-apollo/ssr' import App from './App.vue' Vue.use(ApolloSSR) 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(() => { const matchedComponents = router.getMatchedComponents() // no matched routes if (!matchedComponents.length) { reject({ code: 404 }) } let js = '' // Call preFetch hooks on components matched by the route. // A preFetch hook dispatches a store action and returns a Promise, // which is resolved when the action is complete and store state has been // updated. // Vuex Store prefetch Promise.all(matchedComponents.map(component => { return component.asyncData && component.asyncData({ store, route: router.currentRoute, }) }) // Apollo prefetch // This will prefetch all the Apollo queries in the whole app .then(() => ApolloSSR.prefetchAll(apolloProvider, [App, ...matchedComponents], { store, route: router.currentRoute, }) .then(() => { // Inject the Vuex state and the Apollo cache on the page. // This will prevent unnecessary queries. // Vuex js += `window.__INITIAL_STATE__=${JSON.stringify(store.state)};` // Apollo js += ApolloSSR.exportStates(apolloProvider) resolve({ app, js, }) }).catch(reject) }) }) ``` Use the `ApolloSSR.exportStates(apolloProvider, options)` method to get the JavaScript code you need to inject into the generated page to pass the apollo cache data to the client. It takes an `options` argument which defaults to: ```js { // Global variable name globalName: '__APOLLO_STATE__', // Global object on which the variable is set attachTo: 'window', // Prefix for the keys of each apollo client state exportNamespace: '', } ``` You can also use the `ApolloSSR.getStates(apolloProvider, options)` method to get the JS object instead of the script string. It takes an `options` argument which defaults to: ```js { // Prefix for the keys of each apollo client state exportNamespace: '', } ``` ### Creating the Apollo Clients 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. Here is an example: ```js // src/api/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 on the client, recover the injected state if (typeof window !== 'undefined') { const state = window.__APOLLO_STATE__ if (state) { // If you have multiple clients, use `state.` 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 } ``` Example for common `CreateApp` method: ```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 './api/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) // 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 ``` On the client: ```js import CreateApp from './app' CreateApp({ ssr: false, }) ``` On the server: ```js 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(() => { // Prefetch, render HTML (see above) }) }) ```