docs: new SSR docs

This commit is contained in:
Guillaume Chau
2019-02-04 19:56:09 +01:00
parent b335ca2993
commit 43ff770c03
3 changed files with 100 additions and 225 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ module.exports = {
link: 'https://www.patreon.com/akryum',
},
],
sidebarDepth: 3,
sidebarDepth: 2,
sidebar: {
'/guide/': [
'',
-76
View File
@@ -6,45 +6,6 @@ See [SSR guide](../guide/ssr.md).
## Methods
### install
Install the SSR plugin only on the server with:
```js
Vue.use(ApolloSSR)
```
You can pass additional options like this:
```js
Vue.use(ApolloSSR, {
fetchPolicy: 'network-only',
suppressRenderErrors: false,
})
```
#### fetchPolicy
When an Apollo query is prefetched, it's recommended to override `fetchPolicy` to force the queries to happen.
Default value: `'network-only'`.
#### suppressRenderErrors
Silent the fake render errors.
Default value: `false`.
### prefetchAll
Prefetches all queued component definitions and returns a promise resolved when all corresponding apollo data is ready.
```js
await ApolloSSR.prefetchAll (apolloProvider, componentDefs, context)
```
`context` is passed as the argument to the `prefetch` options inside the smart queries. It may contain the route and the store.
### getStates
Returns the apollo stores states as JavaScript objects.
@@ -82,40 +43,3 @@ const js = ApolloSSR.exportStates(apolloProvider, options)
exportNamespace: '',
}
```
### globalPrefetch
Allow you to register a component to be prefetched explicitly.
Simple example:
```js
import MyComponent from '@/components/MyComponent.vue'
ApolloSSR.globalPrefetch(() => MyComponent)
```
You can disable prefetching depending on context:
```js
ApolloSSR.globalPrefetch(context => {
if (context.route.name === 'foo'){
return MyComponent
}
})
```
### mockInstance
During `prefetchAll`, the app components tree is re-created with fake instances so the process is faster. You can apply plugins to modify the fake instances to prevent their render functions to crash if you have helpers like `this.$http` that is accessed in the template or render function (typically `Undefined error`). It's recommended to mock those helpers to improve performance.
```js
const noop = () => {}
ApolloSSR.mockInstance({
apply: vm => {
// Mock $http
vm.$http = noop
},
})
```
+99 -148
View File
@@ -1,8 +1,10 @@
# Server-Side Rendering
**Requires Vue 2.6+**
::: warning
**Requires Vue 2.6+ with `serverPrefetch` support**
:::
## Vue CLI Plugin
## 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! ✨🚀
@@ -14,17 +16,16 @@ vue add @akryum/ssr
[More info](https://github.com/Akryum/vue-cli-plugin-ssr)
## Prefetch components
## Component prefetching
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.
::: tip
Follow the [offical SSR guide](https://ssr.vuejs.org) to learn more about Server-Side Rendering with Vue.
:::
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.
By default with `vue-server-renderer`, all the GraphQL queries in your server-side rendered components will be prefetched automatically.
::: danger
You don't have access to the component instance when doing prefetching on the server.
::: tip
You have access to `this` in options like `variables`, even on the server!
:::
Example:
@@ -33,7 +34,6 @@ Example:
export default {
apollo: {
allPosts: {
// This will be prefetched
query: gql`query AllPosts {
allPosts {
id
@@ -59,11 +59,6 @@ export default {
description
}
}`,
prefetch: ({ route }) => {
return {
id: route.params.id,
}
},
variables () {
return {
id: this.id,
@@ -74,11 +69,13 @@ export default {
}
```
### Skip prefetching
## 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
```js{12}
export default {
apollo: {
allPosts: {
@@ -98,7 +95,7 @@ export default {
If you want to skip prefetching all the queries for a specific component, use the `$prefetch` option:
```js
```js{4}
export default {
apollo: {
// Don't prefetch any query
@@ -116,128 +113,16 @@ export default {
}
```
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
<ApolloQuery no-prefetch>
```
## 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, {
// SSR config
fetchPolicy: 'network-only',
suppressRenderErrors: false,
})
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
## 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
// src/api/apollo.js
```js{21-31}
// apollo.js
import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
@@ -285,16 +170,24 @@ export function createApolloClient (ssr = false) {
}
```
Example for common `CreateApp` method:
## 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
```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 { createApolloClient } from './apollo'
import App from './ui/App.vue'
import routes from './routes'
@@ -315,6 +208,12 @@ function createApp (context) {
// 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({
@@ -338,23 +237,34 @@ function createApp (context) {
export default createApp
```
On the client:
## Client entry
The client entry is very simple -- we just call `createApp` with `ssr` being `false`:
```js
import CreateApp from './app'
// client-entry.js
CreateApp({
import createApp from './app'
createApp({
ssr: false,
})
```
On the server:
## Server entry
```js
import CreateApp from './app'
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({
const { app, router, store, apolloProvider } = createApp({
ssr: true,
})
@@ -363,9 +273,50 @@ export default () => new Promise((resolve, reject) => {
// wait until router has resolved possible async hooks
router.onReady(() => {
// Prefetch, render HTML (see above)
// 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)
})
})
```
See the [SSR API](../api/ssr.md) for more details and other features.
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>
```