React is awesome. Django is great. And together, they can provide a clean separation of frontend and backend concerns. An ideal way to host React app is to serve it over a CDN like CloudFront and have it make API calls to the backend API, possibly on a different subdomain. For small apps (in terms of scope and usage), this is an overkill. And if you’re using Heroku on top of that, it doesn’t make sense, financial-wise, to have two separate paid apps for backend and frontend. Following tutorial will make Django serve its usual URLs and have it also serve React builds with GZIP compression.


  • Working Heroku CLI installation and some familiarities with Heroku ecosystem
  • Yarn and Virtualenv available on path
  • We're assuming Python 3 and a Django project named datasets

Final Result

Development Workflow

  • python runserver -> Runs Django
  • yarn start on frontend/ folder -> Runs CRA 's server locally

Production Features

  • React builds are done on Heroku, no need to track build artifacts
  • The app is available on the root URL
  • The app generated by CRA (create-react-app) is untouched. Even Git Submodules can be used to link the project
  • Static assets are served with GZIP compression
  • Both pip packages and node_modules are cached in Heroku, thereby speeding up subsequent deploys.
  • Even Session authentication can be used because it is being served from the same domain and no CORS issues

Project Setup

Let's start by creating the project directory and changing to it:

mkdir datasets 
cd datasets/

Django Setup


Create a virtualenv and source it:

virtualenv -p python3 venv
source venv/bin/activate

Edit the above command if you need to use Python 2. See Virtualenv -p reference.


Install Django and some required dependencies which we'll use later on:

pip install Django whitenoise gunicorn

Save requirements file:

pip freeze > requirements.txt

You should run the above command again when you add a Python package using pip install so that the requirement file will be in sync with your local virtual environment.

Let's create a Django project in the current directory and a new app called data:

django-admin startproject datasets .
./ startapp data

The last . makes django-admin not create a new subdirectory as project root, which it does normally.

Add the created app to

    ....other apps....

# Ideally, this should be the domains that you'll be running the app for
# for the purpose of this tutorial, let's set this to catch-all value

Static Files Configuration

Add Whitenoise middleware to

    ....other middlewares....

And set the following configuration for static files:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

REACT_APP_DIR = os.path.join(BASE_DIR, 'frontend')

    os.path.join(REACT_APP_DIR, 'build', 'static'),


Create a file called Procfile with the following contents:

web: gunicorn datasets.wsgi


We'll be using Git for version control and Heroku deploys. Let's create a .gitignore file:


# don't track virtualenv, ever!

# don't track node_modules because we'll be using yarn install and it will create this

# On our project configuration, the output of the collectstatic will be stored in 
# staticfiles directory. We are ignoring files inside staticfiles but we'll track this # folder itself so that collectstatic can work on Heroku

Now, initialize the Git itself and create our first commit:

git init
git add .

The staticfiles configuration in this project will make collectstatic command copy all static files to staticfiles. This directory should exist beforehand. As Heroku docs say,

Django won’t automatically create the target directory (STATIC_ROOT) that collectstatic uses, if it isn’t available. You may need to create this directory in your codebase, so it will be available when collectstatic is run. Git does not support empty file directories, so you will have to create a file inside that directory as well.

But, we shouldn't track this folder in Git as it can be easily regenerated, and it is not tracked as it is in .gitignore. To fix this with Git, we create a .gitkeep file inside the staticfiles folder, and add it with -f as:

mkdir staticfiles/
touch staticfiles/.gitkeep
git add -f staticfiles/.gitkeep

Now, finally create the first commit:

git commit -m "initial commit"

React setup

Let's create a React app in frontend/ folder:

create-react-app frontend

and add this to git as well:

git add .
git commit -m "Add React"

On the package.json configuration of React, add the following:

  "proxy": "http://localhost:8000"

This will make Create-React-App's development server proxy requests to our Django app. So, on our code, we could do something like:


and it will proxy the request to http://localhost:8000/api/datasets. Very useful for development.

Serving React build from Django

Since we have the frontend/build/static folder already on the static files configuration, Django will serve them if it exists. One remaining piece of configuration is serving React's index.htmlon the root of the application. To do so, let's create a view:

import os
import logging
from django.http import HttpResponse
from django.views.generic import View
from django.conf import settings

class FrontendAppView(View):
    Serves the compiled frontend entry point (only works if you have run `yarn
    index_file_path = os.path.join(settings.REACT_APP_DIR, 'build', 'index.html')

    def get(self, request):
            with open(self.index_file_path) as f:
                return HttpResponse(
        except FileNotFoundError:
            logging.exception('Production build of app not found')
            return HttpResponse(
                This URL is only used when you have built the production
                version of the app. Visit http://localhost:3000/ instead after
                running `yarn start` on the frontend/ directory

And, a URL at root to serve this:

from django.contrib import admin
from django.urls import path, include, re_path
from data.views import FrontendAppView

urlpatterns = [
	.... other urlpatterns.....
	# have it as the last urlpattern for BrowserHistory urls to work
    re_path(r'^', views.FrontendAppView.as_view()),

Heroku Deployment


Since we'll be using heroku/nodejsbuildpack as well, it expects a package.json file in the root directory. We can utilize it to specify commands to build the React app and even ask it to cache node_modules inside the frontend/folder so that subsequent React builds are faster.

Create the file:

touch package.json

and add the following contents:

  "name": "datasets",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "",
  "author": "Your Name",
  "license": "MIT",
  "private": true,
  "scripts": {
    "heroku-prebuild": "NODE_ENV=production cd frontend/ && yarn install && yarn build && cd .."
  "cacheDirectories": [


Create an empty yarn.lock file so that the Heroku will also make yarn available when using heroku/nodejs buildpack:

touch yarn.lock

Now, let's try deployment to Heroku by creating an app and setting multiple buildpacks:

heroku apps:create -a datasets
heroku buildpacks:set heroku/python
heroku buildpacks:add --index 1 heroku/nodejs

git push heroku master

The Heroku app creation command automatically adds a git remote as heroku. The project name datasets is not available. So, you should come up with something catchy.

With the --index flag, we add heroku/nodejs as the first buildpack so that the React build is done before the Python buildpack runs collectstatic.

Once deployed, open heroku open and you should see your React app available on root path.

Many thanks to a blog post by Gavin at FusionBox for the idea which I expanded upon for Heroku.