Following two previous blog posts on using vue-cli
with Docker
(here and here) this post now covers on how to do a production build of a vue-cli
based app and how to deliver it via an nginx
web server, especially on a (non-root) sub-path. So this post is mostly about the difficulties regarding a sub-path configuration in vue-cli
and in nginx
. You can probably omit much of if it if you serve your app on example.com/
or on subdomain.example.com/
.
But if you are looking for something like example.com/path/to/app/
... then this is the right post for you.
Up front ... the most simple way to test/locally serve your bundled app is explained here.
Differences Between Development and a Production Build?
Since a vue-cli app is a single page client side only application without an (app) server (like e.g. express in the backend), it can be regarded as a "static" Javascript app/HTML website which needs to be delivered through a (web) server (like nginx) on the initial client request.
During development
with yarn serve
/vue-cli-service serve
webpack's dev-server handles (hot) bundling of the code on every code change and serves each hot build immediately to your browser request.
For production
with yarn build
/vue-cli-service build
and app
as the default build target your application source code gets bundled into one folder called dist
. As with static HTML websites this folder needs to be served by a web server such as nginx.
If you are developing your frontend app separately from your backend then your frontend is essentially a purely static app. You can deploy the built content in the dist directory to any static file server, but make sure to set the correct baseUrl (source).
Requirements in vue-cli
for a Sub-Path Deployment
In the root directory of the application the vue.config.js
needs a setting
for the baseUrl
environment variable if it is served on a non-root path (example.com/path/to/app/
):
module.exports = {
baseUrl: '/path/to/app',
};
# or passed in via docker build arg and env var (see below dockerfile)
# baseUrl: process.env.APP_BASEPATH,
This setting
is also needed in your router
config if you use vue router
:
export default new Router({
base: process.env.BASE_URL,
});
Also in your index.html
for links to static assets you should now use:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<script type="text/javascript" src="<%= BASE_URL %>someScript.js"></script>
If using history
mode for vue router
(which removes the hash mode) it is also advisable to have a catch-all route in your app and in your nginx location
directive:
Since our app is a single page client side app, without a proper server configuration, the users will get a 404 error if they access http://oursite.com/user/id directly in their browser (source).
So add a catch-all fallback route to your nginx location directive
: If the URL doesn't match any static assets, it should serve the same index.html
:
location / {
try_files $uri $uri/ /index.html;
}
Your server will no longer report 404 errors as all not-found paths now serve up your index.html file. To get around the issue, you should implement a catch-all route within your Vue app to show a 404 page (source).
So also add this to your vue router
:
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
});
Nginx
Settings for Serving from a Sub-Path
This is a basic example for an app.conf
(based on nginx
's current default.conf
) and inspired by the settings proposed here for reactjs
:
server {
listen 80;
server_name example.com;
location ^~ /path/to/app {
# https://stackoverflow.com/questions/10631933/nginx-static-file-serving-confusion-with-root-alias
# root /usr/share/nginx/html/dist/;
alias /usr/share/nginx/html/dist/;
expires -1;
add_header Pragma "no-cache";
add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
try_files $uri $uri/ /index.html = 404;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Using a Docker Multi-Stage Build for vue-cli Build and Nginx
If using docker
you and if you want to combine yarn build
and and nginx
in one dockerfile
then you could follow along with the pattern proposed here: A multi-stage build where your app gets built in the first step and is then copied over in the next step.
For a sub-path
configuration (already available at docker
image build-time
) pass in APP_BASEPATH
as build argument via docker-compose
args
or via docker build --build-arg APP_BASEPATH="/path/to/app" ...
(so this is passed in at image build-time and then set as ENV
for containers at container runtime):
FROM node:10.9-slim as build-stage
ARG APP_BASEPATH
ENV APP_BASEPATH=${APP_BASEPATH}
# if using git clone instead of COPY
# RUN apt-get -y update \
# && apt-get install -y git
RUN yarn global add @vue/cli -g
WORKDIR /usr/src/app
COPY app /usr/src/app
RUN yarn install
# yarn build (now in production mode) is a RUN command (image build-time) and no longer a CMD (container runtime)!
# This is why you also need APP_BASEPATH declared as ARG
RUN yarn build
FROM nginx:1.15.3
COPY --from=build-stage /usr/src/app/dist /usr/share/nginx/html/dist
# keep /etc/nginx/nginx.conf with include statement for all /etc/nginx/conf.d/*.conf
# but remove /etc/nginx/conf.d/default.conf for approbriate sub-path settings
RUN rm /etc/nginx/conf.d/default.conf
COPY app.conf /etc/nginx/conf.d/app.conf
EXPOSE 80
WORKDIR /etc/nginx
CMD ["nginx", "-g", "daemon off;"]
During development
with CMD yarn serve
a simple ENV
would be sufficient because with CMD
it only affects container runtime but with RUN yarn build
during the production
build APP_BASEPATH
already needs to be defined as ARG
because RUN yarn build
is executed during image build-time (and not during container runtime as it is the case for CMD
).
Further Information
vue-cli docs on deployment:
https://cli.vuejs.org/guide/deployment.html#general-guidelines
https://cli.vuejs.org/guide/deployment.html#previewing-locally
https://cli.vuejs.org/config/#baseurl
https://router.vuejs.org/api/#base
https://router.vuejs.org/guide/essentials/history-mode.html#nginx
https://router.vuejs.org/guide/essentials/history-mode.html#caveat
Dockerizing & serving SPA's:
https://vuejs.org/v2/cookbook/dockerize-vuejs-app.html
https://mherman.org/blog/dockerizing-a-react-app/
Docker multi-stage builds:
https://docs.docker.com/develop/develop-images/multistage-build/
Dev Server (only during development):
https://cli.vuejs.org/config/#devserver
Previous blog posts on vue-cli and docker:
https://daten-und-bass.io/blog/getting-started-with-vue-cli-on-docker/
https://daten-und-bass.io/blog/enabling-hot-reloading-with-vuejs-and-vue-cli-in-docker/