Data prefetch

Please install version 0.15.2+ to use more powerful data prefetching methods.

Vapper provides more intuitive and powerful data prefetching capabilities, allowing you to perform data prefetching just like the SPA application.

The needSerialize option

When developing SPA applications, we usually fetch data in the component's created or mounted hooks, for example:

// Created hook
async created () {
  // Suppose the return value of the `fetchApi` function is a `Promise` instance.
  this.res = await fetchApi('/list')
}

But this code can't run properly in the SSR application, because during the server rendering process, the application can't know when the request ends, and it can't know which data needs to be serialized and sent to the client. So in order for the above code to run in the SSR application, you just need to add the needSerialize: true option:


 







export default {
  needSerialize: true,
  // Created hook
  async created () {
    // Suppose the return value of the `fetchApi` function is a `Promise` instance.
    this.res = await fetchApi('/list')
  }
}

The above code is all the code for prefetching data in the Vapper application. Is it very simple?

Please note: Since the component's mounted hook function will not be executed during server-side rendering, you can only perform data prefetching in the created hook.

Don't forget await

If you forget await, you will not get the expected result:





 
 








export default {
  needSerialize: true,
  // created hook
  async created () {
    // Forgot `await` here
    this.getData()  // The correct way is: `await this.getData ()`
  },
  methods: {
    async getData() {
      this.res = await fetchApi('/list')
    }
  }
}

Avoid duplicate data prefetching

Reading the code above, you may have questions: "Is the code inside the created hook function not executed on the server and client respectively? Does this lead to duplicate data prefetching?", in fact, no, Vapper automatically helps you avoid duplicate data prefetching, so you don't have to do anything.

Store(Vuex)

Avoid state singletons

Vapper allows you to optionally use Vuex, we also need to create a new Store instance for each request. Usually we will wrap the createStore factory function:

// store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import fetch from './fetch'

Vue.use(Vuex)

export default function createStore () {
  // Return the Store instance
  return new Vuex.Store({
    state: { /* ... */ },
    mutations: { /* ... */ },
    actions: { /* ... */ }
  })
}

Then create the store instance in the entry file:







 




 









// src/main.js

export default function createApp () {
  // ...

  // Create store instance
  const store = createStore()

  // Create a root component
  const app = {
    router,
    store,
    // This is necessary, it is for vue-meta
    head: {},
    render: h => h(App)
  }

  // return the root component
  return app
}

Data prefetch - dispatch

action needs to returnPromise instance, then we can prefetch the data like this:


 






export default {
  needSerialize: true,
  // Created hook
  async created () {
    this.res = await this.$store.dispatch('fetchData')
  }
}

If we use async/await, the code for action looks like this:

new Vuex.Store({
  actions: {
    async fetchData({ commit }) {
      // Send an asynchronous request
      const res = await fetch()
      commit('setData', res.data)
    }
  }
})

The needPrefetch option

If the needSerialize option is set totrue, then it will do two things:

  • Serialize the component's data and send it to the client.
  • Waiting for async created hook to prefetch data.

But sometimes the async created hook only involves prefetching of data in the store, and does not involve the component's own data(data option). At this time, it is meaningless if we still serialize the component's own data and send it to the client. And it will waste traffic. In this case, you can use the needPrefetch: true option, which is different fromneedSerialize:

  • It will only wait for async created hooks to prefetch data, but it will not serialize the data of the component itself.

Therefore, we can modify the above example to:


 







export default {
  needPrefetch: true,
  // created hook
  async created () {
    // This only involves the prefetching of data in the `store`.
    this.res = await this.$store.dispatch('fetchData')
  }
}

Practice: Use the needPrefetch option if the created hook only involves prefetching of data in the store, otherwise use the needSerialize option.

mapActions function

If you use the mapActions function to map actions to the component's methods, the code will look more intuitive:







 

 



import { mapActions } from 'vuex'

export default {
  methods: {
    ...mapActions(['fetchData'])
  },
  needSerialize: true,
  async created () {
    await this.fetchData()
  }
}

Use Apollo

Vapper allows you to use vue-apollo and automatically supports SSR.

Manually install dependencies

The following dependencies are required to be installed manually:

yarn add vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag

For more information see: vue-apollo Manual installation

createApolloClient

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'
import fetch from 'isomorphic-fetch'

// Install the vue plugin
Vue.use(VueApollo)

// Create the apollo client
export default function createApolloClient ({ type }) {
  const isServer = type === 'server'
  const httpLink = new HttpLink({
    fetch,
    // You should use an absolute URL here
    uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn'
  })

  const cache = new InMemoryCache()

  // If on the client, recover the injected state
  if (!isServer) {
    if (typeof window !== 'undefined') {
      const state = window.__INITIAL_STATE__.$$apolloState
      if (state) {
        // If you have multiple clients, use `state.<client_id>`
        cache.restore(state.defaultClient)
      }
    }
  }

  const apolloClient = new ApolloClient({
    link: httpLink,
    cache,
    ...(isServer ? {
      // Set this on the server to optimize queries when SSR
      ssrMode: true,
    } : {
      // This will temporary disable query force-fetching
      ssrForceFetchDelay: 100,
    }),
  })

  return apolloClient
}

Return apolloProvider in the entry file



 

 









 
 
 
 



 





 


// Entry file
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import createRouter from './createRouter'
import createApolloClient from './createApolloClient'
import App from './App.vue'

Vue.config.productionTip = false

// Export factory function
export default function createApp (context) {
  // 1. Create a router instance
  const router = createRouter()

  const apolloClient = createApolloClient(context)
  const apolloProvider = new VueApollo({
    defaultClient: apolloClient,
  })

  // 2. Create a app instance
  const app = new Vue({
    apolloProvider,
    router,
    render: h => h(App)
  })

  // 3. return
  return { app, router, apolloProvider }
}

You can see and try to run the example here: examples/with-vue-apollo.