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:
- When we go to
http://prototypes.com/alpha/first-page
, our server retrieves the correct application, and successfully loads the application. - However, vue-router matches against the current path in the browser, and registers that the current path is actually
/alpha/first-page
- In contrast, our routes are registered in absolute terms, which means that the first-page should correspond to the path
/first-page
/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:
- 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. - 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. - 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!