Using Vue SPA (created with webpack) in subfolder

There is a common use case where Vue SPA will run in some subfolder in production environment.

When you develop Vue app using Vue-cli all your links are targeting root folder. There are some topics on Vue forums about that problem but there are not complete, so here is a solution that works. I am not saying that it is the best or. only possible, but it works!

Solution:

I am using two index.html files. One for development (index.html) and the other for production (index_prod.html). Files are 95% the same, except some minor changes in index_prod.html. So, if this is basic index.html

<!DOCTYPE html>
<html>
  <head>
    <metacharset="utf-8">
    <metaname="viewport" content="width=device-width,initial-scale=1.0">
  </head>

  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <script src="/static/config.js"></script>
  </body>
</html>

For production you must use base tag and change src link (if you need one!)

<!DOCTYPE html>
<html>
  <head>
    <strong>&lt;basehref="/sub-folder/" /&gt;</strong>
    &lt;metacharset="utf-8"&gt;
    &lt;metaname="viewport" content="width=device-width,initial-scale=1.0"&gt;
  &lt;/head&gt;
  
  &lt;body&gt;
    &lt;div id="app"&gt;&lt;/div&gt;
    &lt;!-- built files will be auto injected --&gt;
    &lt;script src="<strong>/sub-folder</strong>/static/config.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;

Because I am using index_prod for production I must inform webpack about that. So, I need to change webpack.prod.config.js, by adding template property to HtmlWebpackPlugin:
newHtmlWebpackPlugin({
  filename: config.build.index,
<strong>  template: 'index_prod.html',</strong>
  inject: true,
  ...

Next, I must inform webpack to use new public path, so I must again change webpack.prod.config.js, by adding publicPath property to webpackConfig.output:
output: {
  path: config.build.assetsRoot,
<strong>  publicPath: '/sub-folder/',</strong>
  filename:utils.assetsPath('js/[name].[chunkhash].js'),
  chunkFilename:utils.assetsPath('js/[id].[chunkhash].js')
},

Then I must inform router to use new public path, so I must add base property to router object
let router = new Router({
  routes: […]
  base: ‘/sub-folder/’,
  mode: ‘history’
})
And that’s it! Now I can copy DIST folder to /sub-folder/ on production.