In this tutorial, you will learn to create a hybrid web application using Django, react, and webpack. A hybrid web app means:

  • It's up to us to decide which page is a single-page application or a Django-powered jinja frontend or both.
  • URLs or routes are managed by Django.
  • Webpack will handle all the static files headache i.e., all the optimizing stuff such as compressing and minifying.
  • Static files bundled by webpack will be served by Django. You can refer to this django-react-webpack repository to refer to the code snippets explained in this tutorial. Follow these steps to run the project.

Step 1: Setting up the django-react-webpack boilerplate locally

  • Clone the repository
  • CD to the location where manage.py is located.
  • Create a virtual environment using python3: python -m venv venv.
  • Activate virtual environment: source venv/bin/activate
  • Install requirements: pip install -r requirements.txt.
  • CD to frontend directory cd frontend
  • Install node packages: npm install

If you struggle to install nodejs, npm and npx, follow this tutorial

Step 2: Run the project

  • Open the first terminal where manage.py is located and run python manage.py runserver, to run the Django server.
  • Open the second terminal inside the frontend directory cd frontend
  • For development purpose run npm run watch.
  • For production run: npm run build.

Now that if you want to learn to create the same project from scratch, read further.

How to integrate react with Django

To integrate react with Django you need webpack-bundle-tracker to save output generated by webpack and django-webpack-loader to consume the output saved by webpack bundle tracker.

ūüí° In this tutorial, you will learn to create a hybrid Django and react web app.

You can also watch the step-by-step video for the same.

Step 1:Install python requirements

This code is tested on Django 2.2, you can use the same version till the project is up and running and then upgrade to the newer version to avoid any version-related issues.

pip install django==2.2
pip install django-webpack-loader==0.7.0

Django Webpack Loader - It consumes the output generated by the webpack-bundle-tracker and lets you use the generated bundles in Django.

Step 2: Setup the backend

Create a Django project

django-admin startproject conf .

Create required directories

mkdir {templates,static,frontend}
  • templates: ¬†To serve Django templates.
  • static:¬†To serve Django static files.
  • frontend:¬†We will install react project inside this directory.

Your directory structure should look like the one in the repository

Now configure your settings.py and urls.py 

#settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static_root")
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)

#urls.py

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [path('admin/', admin.site.urls), ]
if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Create a new app pages

python manage.py startapp pages

Add it in INSTALLED_APPS inside settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'pages',
]

Now inside pages/views.py create two views to serve different URLs

from django.shortcuts import render


def index1(request):
    return render(request, 'index1.html', {})


def index2(request):
    return render(request, 'index2.html', {})

Create a file urls.py inside the pages app and two URLs inside pages/urls.py to serve the above two views.

touch pages/urls.py
from django.urls import path
from .views import index1, index2

app_name = 'pages'

urlpatterns = [
    path('', index1, name='index1'),
    path('index2', index2, name='index2')
]

Now update conf/urls.py to link pages app and inside the pages/urls.py link the created views (index1, index2).

#conf/urls.py

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('pages.urls', namespace='pages')), #Linked here
]

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL,
                          document_root=settings.STATIC_ROOT)

#pages/urls.py

from django.urls import path
from .views import index1, index2

app_name = 'pages'

urlpatterns = [
    path('', index1, name='index1'),
    path('index2', index2, name='index2')
]

Now create the two HTML files index1.html and index2.html inside the templates directory to be rendered by the two views, you just created.

<!-- templates/index1.html --> 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django React Webpack - Raturi.in</title></head>
<body>
<ul>
    <li><a href="{% url 'pages:index1' %}">Index1</a></li>
    <li><a href="{% url 'pages:index2' %}">Index2</a></li>
</ul>
<center> At index1</center>
</body>
</html>
<!-- templates/index2.html --> 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django React Webpack - Raturi.in</title></head>
<body>
<ul>
    <li><a href="{% url 'pages:index1' %}">Index1</a></li>
    <li><a href="{% url 'pages:index2' %}">Index2</a></li>
</ul>
<center> At index2</center>
</body>
</html>

You have configured the backend part, let's configure the frontend one.

Step 3: Setup frontend

Change your directory to frontend

cd frontend/

Now create a react app using

npx create-react-app .

Now install the required npm packages using package.json. First create a file called package.json if does not exist, and then replace or write the entire code snippet from below. This will avoid conflicts due to different versions.

{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1"
  },
  "scripts": {
    "build": "./node_modules/.bin/webpack --mode=production --config webpack.config.js",
    "watch": "npm run start -- --watch",
    "start": "./node_modules/.bin/webpack --mode=development --config webpack.config.js"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.3",
    "babel-loader": "^8.1.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-register": "^6.26.0",
    "webpack": "^4.42.1",
    "webpack-bundle-tracker": "^0.4.3",
    "webpack-cli": "^3.3.11"
  }
}

Now install the packages using the command below. Make sure you are inside the frontend directory.

npm install

Now create two files webpack.config.js, .babelrc and a directory for storing your webpack-generated bundles inside fronted as assets/dist

touch .babelrc webpack.config.js 
mkdir assets assets/dist

webpack.config.js is required by webpack for its configuration. .babelrc is used by babel that is used as a transpiler and syntax converter for the code to be compatible with older versions of browsers.

Now configure webpack.config.js, .babelrc.

webpack.config.json

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

var config = {
    context: __dirname,
    entry: {
        'staticfiles': './src/index.js',
    },

    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src/'),
            '@django': path.resolve(__dirname, '../static/'),
        }
    },
    output: {
        path: path.join(__dirname, './assets/dist'),
        filename: "[name]-[hash].js",
        publicPath: '/static/dist/'
    },


    plugins: [
        new BundleTracker({ filename: './webpack-stats.json' }),
    ],
    devtool: 'cheap-module-eval-source-map',
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ['babel-loader'],
                include: [
                    path.resolve(__dirname, "src"),
                ],
            },
            {
                test: /\.css$/,
                use: [
                    'style-loader', 'css-loader'
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader',
                ],
            },
        ]
    }
}

module.exports = (env, argv) => {

    if (argv.mode === 'production') {
        config.devtool = 'none';
    }
    return config
};

.babelrc 

{ "presets": [ "@babel/preset-env", "@babel/preset-react" ] }

Now in your conf/settings.py add webpack-related settings.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'pages',
    'webpack_loader',
]

FRONTEND_DIR = os.path.join(BASE_DIR, 'frontend')
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static_root")
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
    os.path.join(FRONTEND_DIR, "assets"),
)

WEBPACK_LOADER = {
    'DEFAULT': {
        'BUNDLE_DIR_NAME': 'dist/',  # must end with slash
        'STATS_FILE': os.path.join(FRONTEND_DIR, 'webpack-stats.json')
    }
}

Now update your templates index1.html and index2.html to load webpack-generated files something like this

<!-- templates/index1.html -->

{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django React Webpack - Raturi.in</title>
    {% render_bundle 'staticfiles' 'css' %}
</head>

<body>
    <ul>
        <li><a href="{% url 'pages:index1' %}">Index1</a></li>
        <li><a href="{% url 'pages:index2' %}">Index2</a></li>
    </ul>

    <center>
        At index1
    </center>
    {% render_bundle 'staticfiles' 'js' %}
</body>

</html>
<!-- templates/index2.html -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django React Webpack - Raturi.in</title>
</head>

<body>
    <ul>
        <li><a href="{% url 'pages:index1' %}">Index1</a></li>
        <li><a href="{% url 'pages:index2' %}">Index2</a></li>
    </ul>

    <center>
        At index2
    </center>
</body>

</html>

render_bundle 'staticfiles': staticfiles is the name of the entrypoint and render_bundle will load the files(js,css) generated by webpack inside dist folder.

Step 4: Run the server

  • Open the first terminal where manage.py is located and run python manage.py runserver, to run the Django server.
  • Open the second terminal inside the frontend directory cd frontend
  • For development purpose run npm run watch.
  • For production run: npm run build.

npm run watch will automatically look for changes for static files and will update the dist folder. npm run build will generate the compressed and optimized files for production. Tip: You check the source code of html file in browser you can see webpack generated files inside dist folder.

That's all for this tutorial.

You can subscribe this newsletter for similar content.