4

I have a vue frontend, and spring boot backend application. My application has multiple instances with different url paths (for example /instance1, /instance2), but uses the same build project - meaning every instances runs the same jar. Also the application serves static files - the index.html of the compiled vue application.

My problem is that the vue application needs to know dynamically from which url path to get all its static files, so for example when i get the index.html of instance1 it need to get all the assets from /instance1/assets

I read many answers to this problem which they all suggested basically to configure dynamically the webpack_public_path global variable like this first answer

so in my vue app I did this:

in main.js file:

import './publicpath'
import Vue from 'vue'

then in publicpath.js file:

__webpack_public_path__ = `/${window.location.pathname.split('/')[1]}/`

The problem with the answer linked above is that it only works for me when I run the application as a dev application (when running npm run serve on vue project). This doesn't work after building the project, and the build index.html is served by spring boot.

I couldn't find any answer that solved this problem so I would really appreciate any help

Thank you!

Noy Gafni
  • 1,091
  • 9
  • 19

1 Answers1

11

Changing __webpack_public_path__ may not to be enough....

Why

Vue CLI has a publicPath option:

By default, Vue CLI assumes your app will be deployed at the root of a domain, e.g. https://www.my-app.com/. If your app is deployed at a sub-path, you will need to specify that sub-path using this option. For example, if your app is deployed at https://www.foobar.com/my-app/, set publicPath to '/my-app/'

This value is used in multiple places:

  1. To configure Webpack - set it's output.publicPath option.

The value is important both at build time and runtime.

At runtime it is made available as a global JS variable __webpack_public_path__ and used (by Webpack) when creating URL's for any module requests (loading dynamic JS chunks, referencing images etc.)

At build time, it is also used for creating URL's but this time for the assets which are needed to load your app into the browser - all the <link> and <script> tags responsible to load all JavaScript and CSS of your app. Those are directly injected into the index.html as hardcoded strings

  1. The value is made available as an ENV variable process.env.BASE_URL usable inside your code. This variable is very useful

For example Vue-router, also requires some configuration to work correctly when the app is not hosted in server root (because its responsible for creating URL's for <router-link>). This is most of the time done using this line in it's config: base: process.env.BASE_URL

Or you may need to configure base url for Axios to make API calls...

All of this works thanks to Webpack's DefinePlugin. And really important thing to understand is how it works. Anytime you use process.env.BASE_URL in your code, Webpack replaces that string with configured value during build time (ie. there is no variable process.env.BASE_URL know at runtime)

How

From all the above it should be clear now that just replacing the value of __webpack_public_path__ at runtime as suggested by linked answer is just not enough. So what else is needed?

  1. Do not use process.env.BASE_URL anywhere in your code. Using __webpack_public_path__ instead should be enough to solve this problem

  2. Much bigger problem are the links in index.html generated during build time. Those are clearly not affected by __webpack_public_path__ variable. Only option I see here is to set publicPath for production build to some unique value and instead of serving index.html as a static file, serve it as dynamic response - reading file from the disk, replacing original publicPath value with correct value (for the specific deployment) and serving the result

Alternatives

  1. First and obvious alternative is just to setup multiple CI/CD pipelines, configure publicPath for each using ENV variables and build separate package for each instance

  2. Second alternative is hinted in Vue CLI documentation

The value (publicPath) can also be set to an empty string ('') or a relative path (./) so that all assets are linked using relative paths. This allows the built bundle to be deployed under any public path, or used in a file system based environment like a Cordova hybrid app.

The point is if your site uses relative link without leading slash, it should be interpreted by the browser as a path relative to the current path (source) and as a result, you can deploy under any public path...

The decision is yours as only you know the details needed to make one...

Michal Levý
  • 33,064
  • 4
  • 68
  • 86
  • So how can I change the vue publicPath dynamically? My app is not loaded when I serve the build index.html file because it doesn't know the path to access the static files, including the js files. – Noy Gafni Jan 27 '21 at 20:29
  • There is **no way** to change Vue CLI option `publicPath` at runtime. See my updated answer – Michal Levý Jan 28 '21 at 15:01
  • Thank you so much for that detailed answer! I will try a few options and update what worked best for me – Noy Gafni Jan 28 '21 at 19:18
  • So what worked for me is to use vue-cli relative option: so in ```vue.js.config``` I set ```publicPath: './'``` which tells vue to get all the assets from the relative path of the url. So thank you very much! – Noy Gafni Feb 02 '21 at 10:10
  • Spot on with all of this -- I'll add that there may well be additional plugins which further complicate things. Using server-side templates and setting the vue publicPath to `{{assetRoot}}` I was able to get part of it working, but unless I process *all* static files I still have issues from e.g. the PWA plugin as well as some others -- and that prevents me from precompressing the static assets. Definitely a convoluted issue – taxilian Jul 01 '21 at 22:13
  • Alternative 1 is really poor practice (I think). Alternative 2 works fine. Thanks! – stackoverflowed Feb 23 '23 at 12:04