3

I would like to load my routes from an external API. Some users might not have the permissions to access a module.

So my navbar makes an API call and gets all the modules returned. These module objects contain the path to the view file.

I tried to create a small sandbox to reproduce the problem

https://codesandbox.io/s/vue-routing-example-i5z1h

If you open this url in your browser

https://i5z1h.codesandbox.io/#/First

you will first get the following error

Url /First not found

but after clicking on the First module link in the navbar, the First view should get rendered.

I think the problem is related to the fact that the page has not yet started the navigation created event after loading and the module page is therefore not found. After changing a router URL the navigation component had enough time to add all the required routes to the router.

How can I load these URLs before the router leads to the first route and responds a 404 error?

4
  • The first answer of this question might help you: stackoverflow.com/questions/46341016/… Solution is basically to initialize the Vue instance after the routes have been loaded Commented Jun 5, 2019 at 10:59
  • Thanks Simon but I think the given example uses the same code as I do no? I use this.$router.addRoutes too. Would you mind providing an example for Solution is basically to initialize the Vue instance after the routes have been loaded Commented Jun 5, 2019 at 12:34
  • @hrp8sfH4xQ4 Your router setup is rather unusual. The routes are added lazily in Navbar.vue. Why do you setup the routes there instead of in router.js? Commented Jun 7, 2019 at 19:07
  • 2
    @hrp8sfH4xQ4 See the difference in behavior when you setup the routes in router.js. Commented Jun 7, 2019 at 19:09

1 Answer 1

3
+50

The key idea here is to load the routes asynchronously which means you must defer loading of your SPA till that time. In your index.js or main.js, your code would be something like this:

// Some functions are assumed and not defined in the below code.
import Vue from 'vue';
import VueRouter from 'vue-router';

// Application root component
import App from './App.vue';
import { getRoutes } from './api';

// Register Vue plugins
Vue.use(VueRouter);


// Make API call here

// Some animation before the app is fully rendered.
showLoader();

getRoutes(/* Optional User data */)
  .then((routesData) => {
    // Stop the animation
    stopLoader();
    return routesData;
  })
  .then((routesData) => {

    // processRoutes returns an array of `RouteConfig`
    const routes = processRoutes(routesData);

    const router = new Router({
      routes: [
        ...routes,
        {
          path: '*',
          component: NotFound
        }
      ]
    });
  })
  .then((router) => {
    const app = new Vue({
      el: '#app',
      router,
      template: '<App/>',
      components: { App }
    });
  });

Additionally, there are a few things you need to do:

  • Routing is generally the higher-level concern. So if you consider DIP - Dependency Inversion and the stateful + singleton nature of the router, then it makes sense to bootstrap it at the very beginning. Thus, anything that router needs should be available. This means that the navbar component should not be responsible for making the API call. You must take it out.
  • Another possible solution is to use $router.addRoutes() method. But it is inadequate for your needs. It will not work considering authorization in mind. It will not prevent navigation.
  • On a philosophical level, when you are using SPA with client-side routing, then client-side routing is its own source of truth. It is reasonable to know all the routes upfront and hence most routers are designed with this idea in mind. Thus, a requirement like this is a poor fit for this paradigm. If you need something like this, then a server should possess the knowledge of client-side routes and during page refresh, the server should decide what to do - Load the SPA or reject with 404/403 page. And if the access is allowed, the server should inject routing data in the HTML page which will then be picked by Vue.js on the browser side. Many sophisticated SSR - Server-Side Rendering techniques exist to achieve this.

Alternative strategy: Use guards

  1. Define all the routes upfront in your router for all the possible views of all the users.
  2. Define guards for each authorized routes. All these guards would be resolved asynchronously.
  3. Instead of loading routing data from API, use the API to return an Authorization Matrix. Use this API response in your route guards to determine the access.
  4. To prevent calls to the same API multiple times, you can use some sort of caching like Proxy, Memoization, store etc. Generally, for a user, the Auth Matrix will not vary between the calls.

As an advantage of this, you can still load the application partially if required leading to meaningful user experience by reducing the user's time to interact with the application.

Sign up to request clarification or add additional context in comments.

Comments