Static GitHub Issues

[2568] Loading asyncData in SPA fashion on client

prev: question about 'nuxt build'
next: Location of index.js

Backstory

So we've been using Nuxt.js quite successfully in one of our projects and at some point we wanted to go for user experience optimizations. The one I will be talking about concerns loading asyncData on client, i.e. when the app is already loaded in browser and you navigate it by, say, clicking on links.

asyncData is definitely the core feature distinguishing Nuxt.js SSR app from classic SPA Vue.js apps. But while it's critical to run it on server to get SEO value, the ways asyncData is handled on client may not be desired. The thing is, when you navigate to the page with asyncData on client, you won't see the page until the data is loaded completely. Yes, there is a progress bar which soothes the thing, but still, common SPA apps are usually built in a way that users see the partial content up front and the data is loaded asynchronously. Needless to say, there is Time To Interactive metric that is much more preferable for "pages with spinners" compared to "pages with loading bar".

Proposal

Then @shentao came up with the following concept: instead of using asyncData we could move the whole data fetching logic to Vuex store action and then dispatch the action in fetch method. Instead of returning the data object as done in asyncData, you would have it all stored in Vuex and you'll simply have to map the state into your component. Now, if fetch is run on server, we would wait for the action to finish synchronously, and on client we would just dispatch a promise and let it run in the background. Here's some code summing up the concept:

async fetch ({ isServer, store }) {
  if (isServer) {
    await store.dispatch('fetchApiData')
  } else {
    store.dispatch('fetchApiData')
  }
}

One extra thing you have to think about in this case is coming up with the default values for returned data and a way to present it (e.g. spinners). You don't normally have to do it in Nuxt.js app, but in a classic SPA app it's a part of a drill. Oh, and if you actually want to load some data synchronously for some reason you can always use asyncData, they can coexist just fine.

I've created a repo with the full example which shows two pages: /current with asyncData "done the Nuxt.js way" and /spa using the aforementioned concept. The API request is a mock that runs for 2 seconds and returns a text response. You'll notice the difference in the behavior when navigating to both pages from the main page. However, if you try to go to each page directly by URL, you'll see that the SSR behavior is preserved in both cases and data is loaded on server, as you'd expect it to.

Question

My question is: can we make it a part of Nuxt.js API somehow? I feel like this could be a very common use case out in the field. And in case the presented solution is good enough, can we at least add this use case to the documentation?

P.S. Speaking of the proposed approach, one possible improvement would be to move the fetching logic somewhere closer to the component, since you would have to create a separate Vuex module just for the sake of fetching data for every page. Another way I just came up with would be as follows:

async function fetchData() {
  // logic here
  return {
    fetchedData: 'fetched!'
  }
}

export default {
  async asyncData({ isServer }) {
    if (isServer) {
      return fetchData()
    }
    return {}
  },
  data () {
    return {
      fetchedData: 'loading...'
    }
  },
  mounted () {
    fetchData()
  }
}

This would encapsulate the logic in the component itself.

P.P.S Just noticed that isServer is deprecated :smile:

<!--cmty--><!--cmty_prevent_hook--><div align="right"><sub><em>This feature request is available on <a href="https://nuxtjs.cmty.io">Nuxt.js</a> community (<a href="https://nuxtjs.cmty.io/nuxt/nuxt.js/issues/c2234">#c2234</a>)</em></sub></div>