DEV Community

Jorge Bejar
Jorge Bejar

Posted on

How to Quickly Deploy a VueJS App to Heroku

Originally published in WyeWorks blog.

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>
Enter fullscreen mode Exit fullscreen mode

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)
});
Enter fullscreen mode Exit fullscreen mode

Since this code uses Express, we need to add this dependency to our project:

npm install express --save
Enter fullscreen mode Exit fullscreen mode

You can test this server locally by running the following commands:

npm run build
node server.js
Enter fullscreen mode Exit fullscreen mode

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"
},
Enter fullscreen mode Exit fullscreen mode

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",
  ...
},
Enter fullscreen mode Exit fullscreen mode

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",
},
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

And the command to deploy our application is:

git push heroku master
Enter fullscreen mode Exit fullscreen mode

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 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",
    ...
  }
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (6)

Collapse
 
hjfitz profile image
Harry • Edited

Why would you use

"heroku-postbuild": "npm install --only=dev --no-shrinkwrap && npm run build",

Over

"postinstall": 'npm run build"

Just curious as this is what I use when deploying React apps to Heroku.

Collapse
 
jmbejar profile image
Jorge Bejar

Hey,

When deploying a React app to Heroku the command npm run build is likely to work fine because libraries needed for the build process are listed in dependencies, not in devDependencies in package.json. Since a Vue project has these libraries in devDependencies, we need to install them before run npm run build... and for this reason the command line is more complicated.

Regarding postinstall, it is also expected to work as it is explained in the post. I just prefered the Heroku-specific key named heroku-postbuild to highlight that this is a setup for this specific hosting option.

Collapse
 
hjfitz profile image
Harry

Ah cool! I remember my first time using heroku and wondering why it wouldn't build... Had some deps in Dev deps!

Why does Vue save them to devDependencies?

Thread Thread
 
jmbejar profile image
Jorge Bejar

I consider that Vue is doing the right thing. Libraries that are only used to build the site should be placed in devDependencies instead of dependencies.

I understand that dependencies should be used to list packages that should be bundled into the production build, but the tools that you use to make the build.

Collapse
 
nlxdodge profile image
NLxDoDge

Has anyone used vue-router together with Heroku? On my local development environment I get no errors when I refresh url/test, but on Heroku it gives my a weird error.
And I started looking at router.vuejs.org/guide/essentials/... but with only a fail. As I can't get it working.

Anyone got a lead for this?

Collapse
 
stefannibrasil profile image
Stefanni Brasil • Edited

thanks for sharing, this was super helpful.