Recently, I have been investing some time learning VueJS and I found that it is a very interesting framework to play around with. In fact, I have been working on a new project prototype for the last few days and wanted to show it to some people, so I wanted to publish it somewhere in the Internet.
I decided to deploy the project on Heroku so I started to research what is the best way to do it. To my surprise, I did not find much about it apart from a few posts like Quick-n-clean way to deploy Vue + Webpack apps on Heroku and Easily deploy a Vue + Webpack App to Heroku in 5 Steps. Nevertheless, I ended up with a different setup and this is the topic of this post.
Assuming that a Heroku account is already created and the VueJS project already exists, the approach explained in the mentioned articles that I found in my research could be summarized in the following steps:
- Write a minimal NodeJS web server using Express
- Build the assets locally
- Add the
dist
folder to the Git repository, so it is included when pushing to Heroku
What I did not like of these solutions was the need to build the site locally and check-in the changes within the dist
folder. I wanted to have this step handled by Heroku when pushing a new version of my application.
Our solution
Let’s assume we have a VueJS project generated using vue-cli
with the webpack
template. Just to be clear, the project was created using the following command:
vue init webpack <YOUR-PROJECT-NAME-HERE>
Of course, we also need a Heroku account and a new application created there. Heroku will use the NodeJS buildpack because our project contains a package.json
in the root folder.
Step 1: Add a minimal NodeJS server
This is a step borrowed from the mentioned blog posts. We have to add a server.js
file in the project’s root folder containing the following code:
const express = require('express');
const path = require('path');
const serveStatic = require('serve-static');
let app = express();
app.use(serveStatic(__dirname + "/dist"));
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log('Listening on port ' + port)
});
Since this code uses Express, we need to add this dependency to our project:
npm install express --save
You can test this server locally by running the following commands:
npm run build
node server.js
Step 2: Setup package.json scripts
We need to tweak the scripts
section in the package.json
. If the package file provided by the Vue Webpack template was not modified, it should include two important tasks, start
and build
:
"scripts": {
...
"start": "npm run dev",
...
"build": "node build/build.js"
},
By default, the start
script will be executed by Heroku to start the server. For this reason, we will change the command associated to start
to run our custom server script:
"scripts": {
...
"start": "node server.js",
...
},
Please note you can not use npm run start
anymore to run the development server in your computer. I decided to use npm run dev
directly but you could add a new entry in the scripts
section with an alias for that.
We still have to add something to make sure that the dist
folder is built in our Heroku instance every time the code is deployed, otherwise the server script is not going to work properly. We will use a special script called heroku-postbuild
which is documented here. The idea is to build the site using this special hook, so let’s add it to our package.json
:
"scripts": {
...
"heroku-postbuild": "npm install --only=dev --no-shrinkwrap && npm run build",
},
Let’s explain the command a bit. First of all, we need to install the dependencies that are used to build the assets. In a VueJS project created with the Webpack template, all the needed dependencies are in devDependencies
, so we have to add the --only=dev
option.
The —no-shrinkwrap
option is used to avoid possible conflicts with the packages installed by Heroku during the installation process (where the production dependencies were installed). However, it could be an unnecessary option in most cases.
And of course, we are running npm run build
to actually build the site before the server is started.
Step 3: Try it and enjoy!
We are now ready to deploy to Heroku. Assuming we already have a Git repository, we need to add the Heroku remote repo:
heroku git:remote -a <YOUR-HEROKU-APP-NAME-HERE>
And the command to deploy our application is:
git push heroku master
It will push the code, trigger the build steps and start the NodeJS script that will serve our site made with VueJS
Discussion
There might be some discussion around the decision of having a build step in Heroku instead of checking in the dist
folder. Building the site locally would lead to a less complicated Heroku setup because we can just assume that the dist
folder is always present. However, having the dist
folder in our Git repository does not seem a good practice because it will make harder to read commit changes and deal with merge conflicts. Also, it will require some effort and discipline from every developer in the team to keep the right built version of the assets in the repository. For all these reasons, we prefer to build the site as an automated step into the deployment process.
Speaking about the heroku-postbuild
hook, some people are actually using post-install
which seems to also work on Heroku. The purpose of this npm hook is to be invoked when a package is installed and, in my opinion, it should be used in the context of the library project, not in an application project. I would rather use the most specific hook provided by Heroku.
Regarding the need to run npm install
in the heroku-postbuild
hook to install our devDependencies
, we could discuss a few available alternatives to solve the problem:
Do not use devDependencies
The simplest approach would be to move everything to dependencies
and do not use devDependencies
at all.
In fact, I was comparing the process to deploy a React project created with [create-react-app](GitHub - facebookincubator/create-react-app: Create React apps with no build configuration.) to Heroku and realized that all the scripts and dependencies needed to build the site are actually in the dependencies
section. This is what you find in the package.json
file in such cases (the react-scripts
package contains all the dependencies used to build the site):
"dependencies": {
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-scripts": "1.0.17"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
...
}
Note that there is no devDependencies
there. Therefore, it is safe to just run npm run build
in the heroku-postbuild
hook, since all the necessary packages to build the site were already installed at that point by the default deployment process in Heroku.
In any case, I think it is a good practice to keep dependencies well organized in both categories, dependencies
and depDependencies
. As a consequence, we opted for the inclusion of the additional npm install
in the heroku-postbuild
hook step instead of changing the default configuration provided by vue-cli
.
Set NPM_CONFIG_PRODUCTION to false
Setting the environment variable NPM_CONFIG_PRODUCTION
to false
causes that packages from devDependencies
will also be installed by default in the deployment process in Heroku. The default value is true
because the most common case would be to install only the items from the dependencies
list.
It would be a valid solution to tweak this value and have the heroku-postbuild
script just running npm run build
. Even so, note this change also affects the value of NODE_ENV
as explained here . There is a chance that it could cause some side effect in the build process but this is unlikely to happen using the default Webpack configuration for VueJS projects, as far as I can tell.
Hope you find this post useful and have your VueJS project finally deployed to Heroku! If you have any problem following the steps please leave a comment so we can find a solution together and improve this article.