Hosting static files with Heroku

2018 June 2

Generally speaking, Heroku is not the best platform to host static files. It's much better to host them on a CDN instead. But it depends what we mean by static files.

If what we want to achieve is to deploy a JavaScript frontend app, like a React or Angular, then it may still be a good idea to use Heroku because it provides good tools to build and deploy this app. It also simplifies the continuous delivery process and, let's not forget: free dynos can be great for non-prod environments.

Solution: Node.js & Express

Heroku supports Nodejs and Express is a great facility to serve our static files. We can write a server app quickly using Express to serve our files:

    const express = require('express');
    const serveStatic = require('serve-static');
    const compression = require('compression');

    // the port is arbitrary in Heroku and supplied via
    // environment variable PORT, so we need to read it and use it
    const port = process.env.PORT || 3000;

    const app = express();

    // default to .html (you can omit the extension in the URL)
    // serves the static files from the dist folder
    app.use(serveStatic(`${__dirname}/dist`, {
        'extensions': ['html'],
        etag: true

    app.listen(port, () => {
        console.log(`Server running on port ${port}...`);

To ensure the server runs when deployed to Heroku, save the code in file called server.js and add this to your package.json

    "scripts": {
        "start": "node server.js"

So when Heroku deploys our code, it detects it's a NodeJs application and executes npm install. This will build the web application and put the code in the dist folder. After that, Heroku will run npm start and this will simple execute the code in server.js file.

Push state (for deep links)

Some js web frameworks have routing facilities. Some even offer the possiblity to use the routes as browser urls, but this requires push state support from the server.

Push state is a fancy term to describe that the server will return the index.html file regardless of which html file is requested by the browser.

We can amend server.js to add support for this simply by adding this (right before the app.listen(port, () => { line):

    // always return index.html to support push state
    app.get('*', function(req, res){