How to load all server side data on initial vue.js / vue-router load?

You can use navigation guards.

On a specific component, it would look like this:

export default {
    beforeRouteEnter (to, from, next) {
        // my ajax call
    }
};

You can also add a navigation guard to all components:

router.beforeEach((to, from, next) => {
    // my ajax call
});

One thing to remember is that navigation guards are async, so you need to call the next() callback when the data loading is finished. A real example from my app (where the guard function resides in a separate file):

export default function(to, from, next) {
    Promise.all([
        IngredientTypes.init(),
        Units.init(),
        MashTypes.init()
    ]).then(() => {
        next();
    });
};

In your case, you'd need to call next() in the success callback, of course.


My approach is to delay construction of the store and main Vue until my AJAX call has returned.

store.js

import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';

Vue.use(Vuex);

function builder(data) {
  return new Vuex.Store({
    state: {
      exams: data,
    },
    actions,
    getters,
    mutations,
  });
}

export default builder;

main.js

import Vue from 'vue';
import VueResource from 'vue-resource';
import App from './App';
import router from './router';
import store from './store';

Vue.config.productionTip = false;

Vue.use(VueResource);

Vue.http.options.root = 'https://miguelmartinez.com/api/';

Vue.http.get('data')
  .then(response => response.json())
  .then((data) => {
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      store: store(data),
      template: '<App/>',
      components: { App },
    });
  });

I have used this approach with other frameworks such as Angular and ExtJS.


Alright, I finally figured this thing out. All I'm doing is calling a synchronous ajax request within my main.js file where my root vue instance is instantiated, and assigning a data property the requested data as so:

main.js

let acfData;

$.ajax({
    async: false,
    url: 'http://localhost/placeholder/wp-json/acf/v2/page/2',
    type: 'GET',
    success: function(response) {
        console.log(response.acf);
        acfData = response.acf;
    }.bind(this)
})  

const router = new VueRouter({
    routes: [
        { path: '/', component: Home },
        { path: '/about', component: About },
        { path: '/tickets', component: Tickets },
        { path: '/sponsors', component: Sponsors },
    ],
    hashbang: false
});

exports.router = router;

const app = new Vue({
    router,
    data: {
        acfs: acfData 
    },
    created() {

    }
}).$mount('#app')

From here, I can use the pulled data within each individual .vue file / component like so:

export default {
    name: 'app',
    data () {
    return {
        acf: this.$parent.acfs,
    }
},

Finally, I render the data within the same .vue template with the following:

<template>
  <transition
      name="home"
      v-on:enter="enter"
      v-on:leave="leave"
      v-bind:css="false"
      mode="out-in"
    >
    <div class="full-height-container background-image home" v-bind:style="{backgroundImage: 'url(' + this.acf.home_background_image.url + ')'}">
      <div class="content-container">
        <h1 class="white bold home-title">{{ acf.home_title }}</h1>
        <h2 class="white home-subtitle">{{ acf.home_subtitle }}</h2>
        <div class="button-block">
          <a href="#/about"><button class="white home-button-1">{{ acf.link_title_1 }}</button></a>
          <a href="#/tickets"><button class="white home-button-2">{{ acf.link_title_2 }}</button></a>
        </div>
      </div>
    </div>
  </transition>
</template>

The most important piece of information to take away, is that all of the ACF data is only being called ONCE at the very beginning, compared to every time a route is visited using something like beforeRouteEnter (to, from, next). As a result, I'm able to get silky smooth page transitions as desired.

Hope this helps whoever comes across the same problem.


I've comprised my own version based on all the great responses to this post.. and several years having passed by as well giving me more tools.

In main.js, I use async/await to call a prefetch service to load any data that must be there on startup. I find this increases readability. After I get the data comms, I then dispatch it to the appropriate vuex store module in the beforeCreate() hook.

import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

import { prefetchAppData } from '@/services/prefetch.service';

(async () => {
    let comms = await prefetchAppData();

    new Vue({
        router,
        store,
        beforeCreate() {
            store.dispatch('communityModule/initialize', comms);
        },
        mounted() {},
        render: h => h(App)
    }).$mount('#app');
})();

I feel compelled to warn those be careful what you prefetch. Try to do this sparingly as it does delay initial app loading which is not ideal for a good user experience.

Here's my sample prefetch.service.js which does the data load. This of course could be more sophisticated.

import api from '@api/community.api';
export async function prefetchAppData() {
    return await api.getCommunities();
}

A simple vue store. This store maintains a list of 'communities' that the app requires to be loaded before application start.

community.store.js (note im using vuex modules)

export const communityModule = {
    namespaced: true,
    state: {
        communities: []
    },
    getters: {
        communities(state) {
            return state.communities;
        },
    },
    mutations: {
        SET_COMMUNITIES(state, communities) {
            state.communities = communities;
        }
    },
    actions: {
        // instead of loading data here, it is passed in 
        initialize({ commit }, comms) {
            commit('SET_COMMUNITIES', comms);
        }
    }
};