Hydration Apollo Client
-
Hi all,
I’ve been tinkering around trying to learn ssr/apollo client better and was wondering if there is a better solution then what I have. Is there a cleaner way to hydrate the front end client without the use a vuex? I’m also using vueApollo and it says that it attempts to do prefetch automatically but I’m not sure how I would set the APOLLO_STATE.
Dev mode… ssr + pwa
Pkg quasar… v1.0.5
Pkg @quasar/app… v1.0.4Apollo Boot File
import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import VueApollo from 'vue-apollo' import fetch from 'node-fetch' import { createHttpLink } from 'apollo-link-http' const createApolloClient = function (ssr = false) { const httpLink = createHttpLink({ uri: process.env.API_URL, fetch: fetch }) const cache = new InMemoryCache() if (!ssr && typeof window !== 'undefined') { const state = window.__APOLLO_STATE__ if (state) { cache.restore(state) } } // Create the apollo client const apolloClient = new ApolloClient({ link: httpLink, cache, connectToDevTools: true, ...(ssr ? { ssrMode: true } : { ssrForceFetchDelay: 100 }) }) return apolloClient } export default ({ app, Vue, ssrContext }) => { const apolloClient = createApolloClient(!!ssrContext) const apolloProvider = new VueApollo({ defaultClient: apolloClient, errorHandler ({ graphQLErrors, networkError }) { if (graphQLErrors) { graphQLErrors.map(({ message, locations, path }) => console.log( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` ) ) } if (networkError) { console.log(`[Network error]: ${networkError}`) } } }) Vue.use(VueApollo) app.provide = ({ $apolloProvider: apolloProvider }) if (ssrContext) { ssrContext.apolloState = JSON.stringify(apolloProvider.defaultClient.extract()) ssrContext.apollo = apolloProvider } }
The apolloClient.extract() here always returns {} but I still need to call it here because if ssrContext.apolloState is null quasar complains when it tries to inject apolloState into index.template.html
index.template.html
<% if (htmlWebpackPlugin.options.ctx.mode.ssr) { %> <script> window.__APOLLO_STATE__ = {{{ apolloState }}}; </script> <% } %>
listItems component
<template> <div class="col"> <ul> <li v-for="item in listItems" :key="item.id"> <router-link :to="{ name: 'anotherPage', params: { name: item.name } }">{{ item.name }}</router-link> </li> </ul> </div> </template> <script> import GET_LIST_ITEMS from '@graphql/queries/listItems.gql' export default { async preFetch ({ store, currentRoute, previousRoute, redirect, ssrContext }) { if (ssrContext) { await ssrContext.apollo.defaultClient.query({ query: GET_LIST_ITEMS, variables: { where: {} } }) ssrContext.apolloState = JSON.stringify(ssrContext.apollo.defaultClient.extract()) } }, apollo: { listItems: { query: GET_LIST_ITEMS, variables: { where: {} } } } } </script>
Do I have to use quasar prefetch on every component? Is there a cleaner/better way? Am I missing something? This currently works, client properly hydrated, no additional graphql requests, apollo cache is loaded without the use of vuex. I want to use apolloClient for local state management and so it would be better to not have vuex in the build.
Would love your input! Thanks!
-
I was able to figure this out, with the help of the vue-apollo docs. You can set a rendered function on your ssrContext that quasar provides. I was confused by what the docs meant by context. With this you can remove the entire prefetch from the component.
Updating files incase someone else gets stuck on this like I did.
Updated apollo boot file
import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import VueApollo from 'vue-apollo' import fetch from 'node-fetch' import { createHttpLink } from 'apollo-link-http' const createApolloClient = function (isServerSide) { const httpLink = createHttpLink({ uri: process.env.API_URL, fetch: fetch }) const cache = new InMemoryCache() if (!isServerSide) { const state = window.__APOLLO_STATE__ if (state) { cache.restore(state) } } const apolloClient = new ApolloClient({ link: httpLink, cache, connectToDevTools: true, ...(isServerSide ? { ssrMode: true } : { ssrForceFetchDelay: 100 }) }) const apolloProvider = new VueApollo({ defaultClient: apolloClient, errorHandler ({ graphQLErrors, networkError }) { if (graphQLErrors) { graphQLErrors.map(({ message, locations, path }) => console.log( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` ) ) } if (networkError) { console.log(`[Network error]: ${networkError}`) } } }) return apolloProvider } export default ({ app, Vue, ssrContext }) => { const apolloProvider = createApolloClient(!!ssrContext) Vue.use(VueApollo) app.provide = ({ $apolloProvider: apolloProvider }) if (ssrContext) { ssrContext.rendered = () => { ssrContext.apolloState = JSON.stringify(apolloProvider.defaultClient.extract()) } } }
listItems component
<template> <div class="col"> <ul> <li v-for="item in listItems" :key="item.id"> <router-link :to="{ name: 'anotherPage', params: { name: item.name } }">{{ item.name }}</router-link> </li> </ul> </div> </template> <script> import GET_LIST_ITEMS from '@graphql/queries/listItems.gql' export default { apollo: { listItems: { query: GET_LIST_ITEMS, variables: { where: {} } } } } </script>