Skip to content

Commit 2e6ac99

Browse files
author
Guillaume Chau
committed
Working SSR support
1 parent 0ffb99e commit 2e6ac99

File tree

8 files changed

+443
-194
lines changed

8 files changed

+443
-194
lines changed

README.md

Lines changed: 283 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Integrates [apollo](http://www.apollostack.com/) in your [Vue](http://vuejs.org)
1111

1212
[<img src="https://assets-cdn.github.com/favicon.ico" alt="icon" width="16" height="16"/> Apollo graphql server example](https://github.com/Akryum/apollo-server-example)
1313

14-
[<img src="https://assets-cdn.github.com/favicon.ico" alt="icon" width="16" height="16"/> Apollo "hello world" example app](https://github.com/Akryum/frontpage-vue-app)
14+
[<img src="https://assets-cdn.github.com/favicon.ico" alt="icon" width="16" height="16"/> Apollo "hello world" example app](https://github.com/Akryum/frontpage-vue-app) (outdated)
1515

1616
[<img src="https://cdn-static-1.medium.com/_/fp/icons/favicon-medium.TAS6uQ-Y7kcKgi0xjcYHXw.ico" alt="icon" width="16" height="16"/> Howto on Medium](https://dev-blog.apollodata.com/use-apollo-in-your-vuejs-app-89812429d8b2#.pdd4hmcrc)
1717

@@ -1068,28 +1068,298 @@ tags: {
10681068

10691069
## Server-Side Rendering
10701070

1071-
(Work in progress)
1071+
### Prefetch components
10721072

1073-
Use `apolloProvider.collect()` to being collect queries made against this provider. You get a function that returns a promise when all queries collected are ready. Note that the provider stops collecting queries when you call the function.
1073+
On the queries you want to prefetch on the server, add the `prefetch` option. It can either be:
1074+
- a variables object,
1075+
- a function that gets the context object (which can contain the URL for example) and return a variables object,
1076+
- `true` (query's `variables` is reused).
1077+
1078+
**Warning! You don't have access to the component instance when doing prefetching on the server. Don't use `this` in `prefetch`!**
1079+
1080+
Example:
1081+
1082+
```javascript
1083+
export default {
1084+
apollo: {
1085+
allPosts: {
1086+
query: gql`query AllPosts {
1087+
allPosts {
1088+
id
1089+
imageUrl
1090+
description
1091+
}
1092+
}`,
1093+
prefetch: true,
1094+
}
1095+
}
1096+
}
1097+
```
1098+
1099+
Example 2:
1100+
1101+
```javascript
1102+
export default {
1103+
apollo: {
1104+
post: {
1105+
query: gql`query Post($id: ID!) {
1106+
post (id: $id) {
1107+
id
1108+
imageUrl
1109+
description
1110+
}
1111+
}`,
1112+
prefetch: ({ route }) => {
1113+
return {
1114+
id: route.params.id,
1115+
}
1116+
},
1117+
variables () {
1118+
return {
1119+
id: this.id,
1120+
}
1121+
},
1122+
}
1123+
}
1124+
}
1125+
```
1126+
1127+
You can also tell vue-apollo that some components not used in a `router-view` (and thus not in vue-router `matchedComponents`) need to be prefetched, with the `willPrefetch` method:
10741128

10751129
```javascript
1076-
const ensureReady = apolloProvider.collect({
1077-
// If set to false, may resolve when partial/cache result is emitted
1078-
waitForLoaded: true, // default
1130+
import { willPrefetch } from 'vue-apollo'
1131+
1132+
export default willPrefetch({
1133+
apollo: {
1134+
allPosts: {
1135+
query: gql`query AllPosts {
1136+
allPosts {
1137+
id
1138+
imageUrl
1139+
description
1140+
}
1141+
}`,
1142+
prefetch: true, // Don't forget this
1143+
}
1144+
}
10791145
})
1146+
```
10801147

1081-
new Vue({
1082-
el: '#app',
1083-
apolloProvider,
1084-
render: h => h(App),
1148+
### On the server
1149+
1150+
To prefetch all the apollo queries you marked, use the `apolloProvider.prefetchAll` method. The first argument is the context object passed to the `prefetch` hooks (see above). It is recommended to pass the vue-router `currentRoute` object. The second argument is the array of component definition to include (e.g. from `router.getMatchedComponents` method). The third argument is an optional `options` object. It returns a promise resolved when all the apollo queries are loaded.
1151+
1152+
Here is an example with vue-router and a Vuex store:
1153+
1154+
```javascript
1155+
return new Promise((resolve, reject) => {
1156+
const { app, router, store, apolloProvider } = CreateApp({
1157+
ssr: true,
1158+
})
1159+
1160+
// set router's location
1161+
router.push(context.url)
1162+
1163+
// wait until router has resolved possible async hooks
1164+
router.onReady(() => {
1165+
const matchedComponents = router.getMatchedComponents()
1166+
1167+
// no matched routes
1168+
if (!matchedComponents.length) {
1169+
reject({ code: 404 })
1170+
}
1171+
1172+
let js = ''
1173+
1174+
// Call preFetch hooks on components matched by the route.
1175+
// A preFetch hook dispatches a store action and returns a Promise,
1176+
// which is resolved when the action is complete and store state has been
1177+
// updated.
1178+
Promise.all([
1179+
// Vuex Store prefetch
1180+
...matchedComponents.map(component => {
1181+
return component.preFetch && component.preFetch(store)
1182+
}),
1183+
// Apollo prefetch
1184+
apolloProvider.prefetchAll({
1185+
route: router.currentRoute,
1186+
}, matchedComponents),
1187+
]).then(() => {
1188+
// Inject the Vuex state and the Apollo cache on the page.
1189+
// This will prevent unecessary queries.
1190+
1191+
// Vuex
1192+
js += `window.__INITIAL_STATE__=${JSON.stringify(store.state)};`
1193+
1194+
// Apollo
1195+
js += apolloProvider.exportStates()
1196+
1197+
resolve({
1198+
app,
1199+
js,
1200+
})
1201+
}).catch(reject)
1202+
})
10851203
})
1204+
```
1205+
1206+
The `options` argument defaults to:
1207+
1208+
```javascript
1209+
{
1210+
// Include components outside of the routes
1211+
// that are registered with `willPrefetch`
1212+
includeGlobal: true,
1213+
}
1214+
```
10861215

1087-
ensureReady().then(results => {
1088-
console.log(results.length, 'queries ready')
1216+
Use the `apolloProvider.exportStates` method to get the JavaScript code you need to inject into the generated page to pass the apollo cache data to the client.
1217+
1218+
It takes an `options` argument which defaults to:
1219+
1220+
```javascript
1221+
{
1222+
// Global variable name
1223+
globalName: '__APOLLO_STATE__',
1224+
// Global object on which the variable is set
1225+
attachTo: 'window',
1226+
// Prefix for the keys of each apollo client state
1227+
exportNamespace: '',
1228+
}
1229+
```
1230+
1231+
### Creating the Apollo Clients
1232+
1233+
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.
1234+
1235+
Here is an example:
1236+
1237+
```javascript
1238+
// src/api/apollo.js
1239+
1240+
import Vue from 'vue'
1241+
import { ApolloClient, createNetworkInterface } from 'apollo-client'
1242+
import VueApollo from 'vue-apollo'
1243+
1244+
// Install the vue plugin
1245+
Vue.use(VueApollo)
1246+
1247+
// Create the apollo client
1248+
export function createApolloClient (ssr = false) {
1249+
let initialState
1250+
1251+
// If on the client, recover the injected state
1252+
if (!ssr && typeof window !== 'undefined') {
1253+
const state = window.__APOLLO_STATE__
1254+
if (state) {
1255+
// If you have multiple clients, use `state.<client_id>`
1256+
initialState = state.defaultClient
1257+
}
1258+
}
1259+
1260+
const apolloClient = new ApolloClient({
1261+
networkInterface: createNetworkInterface({
1262+
// You should use an absolute URL here
1263+
uri: 'https://api.graph.cool/simple/v1/cj1jvw20v3n310152sv0sirl7',
1264+
transportBatching: true,
1265+
}),
1266+
...(ssr ? {
1267+
// Set this on the server to optimize queries when SSR
1268+
ssrMode: true,
1269+
} : {
1270+
// Inject the state on the client
1271+
initialState,
1272+
// This will temporary disable query force-fetching
1273+
ssrForceFetchDelay: 100,
1274+
}),
1275+
})
1276+
1277+
return apolloClient
1278+
}
1279+
```
1280+
1281+
Example for common `CreateApp` method:
1282+
1283+
```javascript
1284+
import Vue from 'vue'
1285+
1286+
import VueRouter from 'vue-router'
1287+
Vue.use(VueRouter)
1288+
1289+
import Vuex from 'vuex'
1290+
Vue.use(Vuex)
1291+
1292+
import { sync } from 'vuex-router-sync'
1293+
1294+
import VueApollo from 'vue-apollo'
1295+
import { createApolloClient } from './api/apollo'
1296+
1297+
import App from './ui/App.vue'
1298+
import routes from './routes'
1299+
import storeOptions from './store'
1300+
1301+
function createApp (context) {
1302+
const router = new VueRouter({
1303+
mode: 'history',
1304+
routes,
1305+
})
1306+
1307+
const store = new Vuex.Store(storeOptions)
1308+
1309+
// sync the router with the vuex store.
1310+
// this registers `store.state.route`
1311+
sync(store, router)
1312+
1313+
// Apollo
1314+
const apolloClient = createApolloClient(context.ssr)
1315+
const apolloProvider = new VueApollo({
1316+
defaultClient: apolloClient,
1317+
})
1318+
1319+
return {
1320+
app: new Vue({
1321+
el: '#app',
1322+
router,
1323+
store,
1324+
apolloProvider,
1325+
...App,
1326+
}),
1327+
router,
1328+
store,
1329+
apolloProvider,
1330+
}
1331+
}
1332+
1333+
export default createApp
1334+
```
1335+
1336+
On the client:
1337+
1338+
```javascript
1339+
import CreateApp from './app'
1340+
1341+
CreateApp({
1342+
ssr: false,
10891343
})
10901344
```
10911345

1092-
[White paper](./ssr.js)
1346+
On the server:
1347+
1348+
```javascript
1349+
import { CreateApp } from './app'
1350+
1351+
const { app, router, store, apolloProvider } = CreateApp({
1352+
ssr: true,
1353+
})
1354+
1355+
// set router's location
1356+
router.push(context.url)
1357+
1358+
// wait until router has resolved possible async hooks
1359+
router.onReady(() => {
1360+
// Prefetch, render HTML (see above)
1361+
})
1362+
```
10931363

10941364
---
10951365

0 commit comments

Comments
 (0)