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.4

    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 (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>
    

Log in to reply