article

Building your Django and React project using Django Webpack Loader and Webpack Bundle Tracker

When we start a monorepo project using Django and React, we face a dilemma on how to integrate both parts, specifically on how we allow Django templates to render the JavaScript assets generated by the frontend build. One nice approach to solve this is to use Django Webpack Loader with Webpack Bundle Tracker to take care of this step in the build pipeline. In this article, we’ll teach you how.

Frontend setup

We must first deal with setting up the frontend application. For that, we’ll use Webpack Bundle Tracker. It will be added to your project webpack.config.js as a new plugin, and will be responsible to gather all assets that were outputted by Webpack’s pipeline (such as JavaScript and CSS files, images, fonts, etc), as well as store their paths into a new file, which we’ll call “stats file” beyond this point.

To begin with, we must add Webpack Bundle Tracker to our project’s package.json and install it. You can do both these steps at once by running npm install --save webpack-bundle-tracker on your terminal. Running with --save instead of --save-dev will allow us to compile the frontend assets inside a deployment pipeline (more on that later).

After installing, we must configure the Webpack build. If you’re using Create React App to bootstrap the project, you must first run npm run eject. This will allow you to further customize your project build steps, as it will give you access to, among others, the Webpack configuration files. Be aware that ejecting is a one-way operation and can’t be reversed. You can read more about it here.

Below is an example of the webpack.config.js after we add Webpack Bundle Tracker. This file is located at the project root:

const path = require('path');
const webpack = require('webpack');
const BundleTracker = require('webpack-bundle-tracker');

module.exports = {
  context: __dirname,
  entry: './assets/js/index',
  output: {
    path: path.resolve('./dist/'),
    filename: "[name]-[hash].js"
  },
  plugins: [
    new BundleTracker({filename: './webpack-stats.json'})
  ],
};
webpack.config.js

The value stored on output.path represents where the compiled assets are going to be stored. On plugins, we provide an array of instances of the plugins we want to use. In our case, we’ll only use the BundleTracker plugin. On its initialization, it accepts a filename parameter that corresponds to the path of the stats file.

It’s highly recommended that you don’t keep any generated file (be it the stats file or the compiled assets) in your version control system, so make sure to add webpack-stats.json and the /dist/ directory to your .gitignore.

Backend setup

After setting up the frontend part, we must now configure Django to see the compiled assets. You can then install Django Webpack Loader by running pip install django-webpack-loader in your environment. To persist the installed packages so they can be used in other environments, you can run pip freeze > requirements.txt to save them into a file. From this point onwards, you can install the packages listed in the requirements file using pip install -r requirements.txt.

After the package is installed, we can start configuring the project. As in most Django apps, it all begins within the settings.py file. We should start by adding the webpack_loader app into the INSTALLED_APPS list.

You must also add the STATICFILES_DIRS variable to settings.py. That should point to the directory where the static files are located and will let Django know where to look.

STATICFILES_DIRS = (
  os.path.join(BASE_DIR, 'dist'),
)

Later in the same file, we must configure the Webpack Loader behavior, by instantiating the WEBPACK_LOADER variable. Below is an example of a basic configuration for it.

WEBPACK_LOADER = {
  'DEFAULT': {
    'BUNDLE_DIR_NAME': '/',
    'CACHE': not DEBUG,
    'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
    'POLL_INTERVAL': 0.1,
    'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
  }
}

Let’s do a quick round-up on what each of these properties mean.

  • DEFAULT is the name that will be used for referencing a configuration across the project. Django Webpack Loader supports multiple setups, so WEBPACK_LOADER is a dict of dicts containing these configurations. The keys of the external dictionary can be named in the way the developers intend, however, the convention is to have at least one configuration named DEFAULT;
  • BUNDLE_DIR_NAME is a string that will be prefixed to the assets files paths when loading from the static files storage. Since we have instructed Django to look at dist/ when collecting the static files, while also making Webpack store the files in the root of that directory, we provide the root of the static files directory as the value. If this key is absent from the dictionary, it will default to webpack_bundles/;
  • CACHE is a property that defines if we should cache the assets files paths (True) or if we should always read the stats file (False). The best setup for this is to define it based on the Django DEBUG variable, which indicates if we’re in a production environment or not. In production, it’s better to cache the results to improve performance, as the files won’t change after the initial compilation. In development, however, it’s better to never cache the paths, since we want to be constantly re-compiling the frontend assets as we change the code;
  • Working in tandem with CACHE is the POLL_INTERVAL. This tells Django how often in seconds it should fetch the stats file to obtain the newest assets paths. In production environments (DEBUG == False), the value on POLL_INTERVAL is ignored since it’s assumed that the compiled assets won’t change after the deployment, so there’s no need to fetch the files again;
  • STATS_FILE is the path to the stats file generated by Webpack Bundle Tracker. This must match the output.path from frontend’s webpack.config.js;
  • IGNORE is a list of regular expressions that will be matched against the files generated during the Webpack compilation. If any file matches these, it’ll be ignored.

There are also some extra, more advanced properties that can be used for fine-tuning the configurations.

  • TIMEOUT is the amount of seconds that Webpack Loader will wait for the compilation to finish before raising an exception. Providing 0, None or leaving this key out of the dictionary will disable timeouts;
  • LOADER_CLASS is a string with the name of a Python class that implements a custom Webpack loader. This can be leveraged to implement a new behavior for how the stats file can be loaded, such as from a database or an external url. Below is an example of how to load the stats file from an external source, by extending the webpack_loader.loader.WebpackLoader class.
import requests
from webpack_loader.loader import WebpackLoader

class ExternalWebpackLoader(WebpackLoader):
  def load_assets(self):
    url = self.config['STATS_URL']
    return requests.get(url).json(

Rendering to a template

After we have the application setup, we can now proceed to leverage Django Webpack Loader utilities to render whatever was developed in the frontend application. What we’ll do is to render the JavaScript files to a Django HTML template using the tools provided by us by the libraries.

Django Webpack Loader has the built-in template tag render_bundle which is responsible for including all files from a given entrypoint present on the stats file. It’s used on a Django template as shown below.

{% load render_bundle from webpack_loader %}
{% render_bundle 'main' %}

It’s recommended to have a single entrypoint per Django template, but you can call render_bundle as many times as you wish in order to render the necessary files since the loader will deduplicate the repeated chunks.

{% load render_bundle from webpack_loader %}
{% render_bundle 'main' %}
{% render_bundle 'other_entrypoint' %}

You are also allowed to include files from a given extension within an entrypoint. For example, you may wish to separately include the CSS and JavaScript files from the main entrypoint, in order to include the former in the template <head> section and the latter before </body>. This can be achieved by providing a second argument (you can also pass it using the extension keyword) to render_bundle that represents the desired file extension.

{% load render_bundle from webpack_loader %}
<html>
  <head>
    {% render_bundle 'main' 'css' %}
  </head>
  <body>
    {% render_bundle 'main' extension='js' %}
  </body>
</head>

Running in the development environment

If you’ve started the project using Create React App, you may compile and serve the assets by running npm run start. This will generate the stats file. If a different setup was used, the files can be compiled and served through the npx webpack --config webpack.config.js --watch command.

For the backend application, it will run as a common Django application, by using the python manage.py runserver command.

Usage in production

Since the frontend generated files (compiled assets, stats file, etc) are left out of the version control system, it’s necessary to set up a production pipeline that will compile and collect the assets for you during the deployment phase.

The way you set up may vary depending on the platform you’re using (Heroku, AWS, etc), but the configuration should run at least the commands below.

npm run build
python manage.py collectstatic --noinput

The first command will run the frontend build pipeline, which will make Webpack Bundle Tracker create and fill the stats file. The second command will follow-up by manually collecting the static files.

It’s important to note that in platforms such as Heroku, the collectstatic step is automatically executed by default during the deployment. We recommend that you disable it by setting the DISABLE_COLLECTSATIC=1 environment variable. After that, you must create a post_compile hook to run this step for you. Here’s an example on how to implement this flow.

Code splitting

Webpack Bundle Tracker after release 1.0.0 supports code splitting for the frontend assets, in order to generate many bundles that can be separately loaded. You can further read about this topic in the excellent Webpack documentation. Below is an example of how to use code splitting in your project.

Create your entrypoint file to add elements to the DOM. To leverage code splitting, in this example we’ll be using lazy loading inside our component to dynamically import lodash.

function getComponent() {
  return import('lodash')
    .then(({ default: _ }) => {
      const element = document.createElement('div');
      element.innerHTML = _.join(['Hello', 'webpack'], ' ');
      return element;
    }).catch(error => 'An error occurred while loading the component');
}

getComponent().then((component) => {
  document.body.appendChild(component);
});
assets/js/principal.js

Then, set up your Webpack configuration file like this:

module.exports = {
  context: __dirname,
  entry: {
    principal: './assets/js/principal',
  },
  output: {
    path: path.resolve('./dist/'),
    // publicPath should match your STATIC_URL config.
    // This is required otherwise webpack will try to fetch 
    // our chunk generated by the dynamic import from "/" instead of "/dist/".
    publicPath: '/dist/', 
    chunkFilename: '[name].bundle.js',
    filename: "[name]-[fullhash].js"
  },
  plugins: [
    new BundleTracker({ filename: './webpack-stats.json' })
  ]
};
webpack.config.js

For Webpack 4 users, please change filename: "[name]-[fullhash].js" into filename: "[name]-[hash].js", while also changing return import('lodash') into return import(/* webpackChunkName: "lodash" */ 'lodash').

After setting this up, you can render the bundle on your template as usual.

{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My test page</title>
  </head>
  <body>
    <p>This is my page</p>
    {% render_bundle 'principal' 'js' %}
  </body>
</html>
index.html

Handling S3 paths

In order to use S3 paths within the project, the values for STATIC_URL (on Django settings) and publicPath (on the Webpack production configuration file) must match.

To keep the values consistent across the project, we’ll use environment variables to store them. In your Django production settings, add the following code.

from decouple import config

AWS_STATIC_BUCKET_NAME = config("AWS_STATIC_BUCKET_NAME")
AWS_STATIC_DIRECTORY = config("AWS_STATIC_DIRECTORY")

STATIC_URL = f"https://{AWS_STATIC_BUCKET_NAME}.s3.amazonaws.com/{AWS_STATIC_DIRECTORY}/"

We’re using python-decouple to manage the environment variables in the Django side.

Here, we build the STATIC_URL value using the values from the environment variables, such as the AWS bucket name and the directory where the files are stored.

In the Webpack production configuration file, we do the same but using the Javascript notation and syntax.

const AWS_STATIC_BUCKET_NAME = process.env.AWS_STATIC_BUCKET_NAME;
const AWS_STATIC_DIRECTORY = process.env.AWS_STATIC_DIRECTORY;

module.exports = {
  // ...
  output: {
    // ...
    publicPath: `https://${AWS_STATIC_BUCKET_NAME}.s3.amazonaws.com/${AWS_STATIC_DIRECTORY}/`,
    // ...
  },
  // ...
};
webpack.config.js

Using multiple configurations

As previously mentioned in the backend setup section, you can have multiple Webpack configurations in your project. You may add a new entry to the WEBPACK_LOADER dictionary in the settings file like it’s done below:

WEBPACK_LOADER = {
    'DEFAULT': {
        'BUNDLE_DIR_NAME': 'bundles/',
        'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
    },
    'OTHER': {
        'BUNDLE_DIR_NAME': 'other_bundles/',
        'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats-other.json'),
    }
}

After that, you can reference that configuration when rendering a bundle in the template, by using the config keyword argument.

{% load render_bundle from webpack_loader %}
<html>
  <body>
    {% render_bundle 'main' config='DASHBOARD' %}

    <!-- you can even pair it with other arguments, such as the files’ extension -->
    {% render_bundle 'main' 'css' 'DASHBOARD' %}
    {% render_bundle 'main' config='DASHBOARD' extension='css' %}
  </body>
</head>

What’s next?

For your next project using Django and React, you may want to try using Django React Boilerplate. It’s already loaded with the essential packages and libraries for these kinds of projects and has the initial configuration for using both Django Webpack Loader and Webpack Bundle Tracker along with all their features.

We’d like to thank Luciano Ratamero for kindly reviewing this blogpost. You can follow Luciano on Twitter @lucianoratamero.

Renato Vieira

Fullstack Developer at Vinta Software.