vue-logo

Problem

We want to be able to access different VueJS frontend applications from the same host, differentiated by a base-path for each app.

For example, on an example host prototypes.com, we want to host 2 applications: alpha and beta. Both these applications should be accessible from separate base paths, prototypes.com/alpha and prototypes.com/beta, routed through the same reverse-proxy.

Example

Let us illustrate the above with a simple example. Let us assume we have the following barebones application structure.

Project
|
+-- server.js    // proxy server
|
+-- alpha/       // a vue.js application
|
+-- beta/        // another vue.js application

From the proxy server, we want to serve both alpha and beta on difference base urls, http://prototypes.com/alpha and http://prototypes.com/beta respectively. The proxy server will route the request to each application, retrieving their built distributions for the browser to parse.

Since we are using vue, we will use vue-router to set up routing for each application. Taking alpha as an example, we have the following routes:

const routes = [
  { path: '/first-page', ... },
  { path: '/second-page', ... },
]

To access the first page of application alpha, we will hence want to access the url http://prototypes.com/alpha/first-page.

Failed Attempt

We create a router using vue-router, passing in routes from above. We also enable history using createWebHistory, which is the recommended mode. This mode allows the URL to look “normal”, without the ugly /#/ in the URL.

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes
})

Unfortunately the above will not work even if all network calls are successfully made, and will render a blank page.

Why?

This happens for the following reason:

  1. When we go to http://prototypes.com/alpha/first-page, our server retrieves the correct application, and successfully loads the application.
  2. However, vue-router matches against the current path in the browser, and registers that the current path is actually /alpha/first-page
  3. In contrast, our routes are registered in absolute terms, which means that the first-page should correspond to the path /first-page
  4. /alpha/first-page and /first-page do not match, so our application does not render anything from our first page.

Solution 1: Brute-Force Approach

What we need to do then is ensure that each page has a path with the following structure /alpha/<page_path>. To achieve this we can simply append /alpha to all our paths, like so:

const routes = [
  { path: '/alpha/first-page', ... },
  { path: '/alpha/second-page', ... },
]

This works, but of course it comes with a few problems:

  1. It is verbose. We have to always remember to append /alpha to all our paths. All other developers that work with this code in the future also have to be aware of this limitation.
  2. This solution requires the application to be aware of the final route it will be given by the proxy server. If the proxy server decides to change the base url of alpha to /alpha2, the application will break.
  3. This application also breaks on our development server, because /alpha is hardcoded into the application.

Solution 2: Injection Approach

We can solve the first problem with an option to createWebHistory.

const router = createRouter({
  history: createWebHistory('/alpha/),
  routes,
})

The option will automatically append /alpha/ to all our paths. It is like solution 1, except /alpha/ is injected automatically.

However, problem 2 and 3 still exist — we still need to know what base url the proxy server will be using to route to our application, and it breaks in development.

Final Solution: Elegant Approach

We can eliminate this inter-service dependency by making the base url dynamic. Instead of specifying /alpha/ as the option, we use window.location.pathname instead.

const router = createRouter({
  history: createWebHistory(window.location.pathname),
  routes,
})

But how does this work? Doesn’t the location change depending on the route we are on?

It doesn’t! The above is what I thought as well, until I thought about the order of execution of the vue application on page load.

When the application loads, the routes of each page are first registered, before being assigned to their respective components. This means that window.location.pathname is run once, at the very start of the application, when the base url is still /alpha. Therefore, routes do not change at all regardless of the paths one navigates to, unless in the situation of a page load. This is because vue-router navigates to other routes without a page refresh!