d83a18fb4f
Just a suggestion: maybe a stable version of vue-apollo can be published after this wonderful ssr refactor?
322 lines
7.4 KiB
Markdown
322 lines
7.4 KiB
Markdown
# Server-Side Rendering
|
|
|
|
::: warning
|
|
**Requires Vue 2.6+ with `serverPrefetch` support**
|
|
:::
|
|
|
|
## 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)
|
|
|
|
## Component prefetching
|
|
|
|
::: tip
|
|
Follow the [offical SSR guide](https://ssr.vuejs.org) 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:
|
|
|
|
```js
|
|
export default {
|
|
apollo: {
|
|
allPosts: {
|
|
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
|
|
}
|
|
}`,
|
|
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:
|
|
|
|
```js{12}
|
|
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{4}
|
|
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:
|
|
|
|
```js{21-30}
|
|
// 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:
|
|
|
|
```js{9,37}
|
|
// 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`:
|
|
|
|
```js
|
|
// 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](https://ssr.vuejs.org/guide/routing.html#routing-with-vue-router) and [data prefetching](https://ssr.vuejs.org/guide/data.html#data-store) in the official SSR guide.
|
|
|
|
Here is an example with vue-router and a Vuex store:
|
|
|
|
```js{3,26}
|
|
// 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](../api/ssr.md#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](https://ssr.vuejs.org/guide/#using-a-page-template), use the `renderState` helper:
|
|
|
|
```html
|
|
{{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}}
|
|
```
|
|
|
|
Here is a full example:
|
|
|
|
```html{15}
|
|
<!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>
|
|
```
|