In this article, we’ll cover the easiest way to integrate React with Django.

Django React hybrid architecture

This setup's main benefit is that we avoid messing with webpack configurations and let create-react-app do the heavy lifting for us. Thus, you can enjoy the best of both frameworks without worrying much about breaking changes.
You can leverage the full power of Django--server-side rendering, Django forms, templates--basically, all the Django knowledge that you have accumulated over the years, and feared you would have to trash now that React is in the picture.
You can decide whether React is needed at all on a page-by-page basis.
Finally, you do not need to implement CORS (django-cors-headers). However, in the following article, we will see how doing so makes the development process more enjoyable.

Requirements

As we're trying to integrate Django and React, you should have a basic understanding of both frameworks. Also, make sure you have python, pip, node and npm installed on your machine.
Alright! Let's do this.

Project setup and virtual environment

In your workspace, create a directory called django-react-boilerplate and navigate into it.

mkdir django-react-boilerplate && cd $_

Then create a virtual environment called env and activate it. (The following command works only for Mac and Unix users).

python3 -m venv env
source env/bin/activate

You should see (env) prefixed in your terminal, which indicates that the virtual environment was successfully activated.

Setting up Django

To Install Django and other dependencies in the virtual environment, run the following command:

pip3 install Django beautifulsoup4

This will install the latest version of Django as well as a beautifulsoup4.
Let's start a Django project called config.

django-admin startproject config .

Notice the . at the end of the command. This instructs Django to create the project in the current directory.
Here is what your project structure should look like:

├── config
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── env
└── manage.py

Django settings

Navigate to the config folder and add a new directory called settings.

cd config
mkdir settings

You will notice that there already is a config/settings.py file that was generated by Django. Let's move it to the config/settings folder and rename it base.py.

mv settings.py settings/base.py

Navigate to config/settings and add two additional files __init__.py and local.py. The config/settings/local.py file will host our local settings. Once ready to deploy your app, you will create a production.py file on your server and override the default settings.

cd settings
touch __init__.py local.py

Open up the config/settings/__init__.py file and add the following code.

# config/settings/__init__.py

try:
    from .production import *
except Exception:
    from .local import *

Next, update the config/settings/base.py file by changing the following settings:

...

BASE_DIR = Path(__file__).resolve().parent.parent.parent

TEMPLATES_DIR = BASE_DIR / 'templates'
CLIENT_DIR = BASE_DIR / 'client'  # react app location
BUILD_DIR = CLIENT_DIR / 'build'  # react app build location

CORS_ORIGIN = None  # only relevant for dev

...

DEBUG = False

...

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATES_DIR],  # Update this line
        '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',
            ],
        },
    },
]
...

In your config/settings/local.py file, add these lines:

from .base import *

SECRET_KEY = 'my-secret-key-goes-here'

CORS_ORIGIN = 'http://localhost:3000'

DEBUG = True

ALLOWED_HOSTS = ['*']

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# MEDIA & STATIC

STATIC_URL = '/static/'
STATICFILES_DIRS = [BUILD_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'static'

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

At this point, let's do a sanity check with:

cd ../../
./manage.py check

The output should be:

System check identified no issues (0 silenced).

And our project structure should look like:

├── config
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings
│   │   ├── __init__.py
│   │   ├── base.py
│   │   └── local.py
│   ├── urls.py
│   └── wsgi.py
├── env
└── manage.py

Setting up React

If you were following along, then you should be in the root folder. Now let's create a new react project called client with CRA.

npx create-react-app client

Note that you could call the react app whatever you want, however make sure it matches the CLIENT_DIR setting. For example, if you name your react app frontend, then your setting should be CLIENT_DIR = BASE_DIR / 'frontend'.
Navigate to the client folder and build the project. If you prefer to use yarn, the concept is the same.

cd client
npm run build

Django x React

This is where things start to get spicy. For this setup to work, we need to have at least one Django app in our project since we're going to use management commands to pull React static files.

So, head back to the root folder again and start a new Django app called myapp.

cd ..
./manage.py startapp myapp

Let's tell Django about our new app; open your config/settings/base.py file and update the INSTALLED_APPS array.

INSTALLED_APPS = [
    ...
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Add this line
    'myapp',
]

We want to add a new management command that will help us collect static files generated by React.

mkdir -p myapp/management/commands/
touch myapp/management/commands/build.py

Open the myapp/management/commands/build.py file and paste the following code:

import os
import subprocess
from bs4 import BeautifulSoup

from django.conf import settings
from django.core.management import call_command
from django.core.management.base import BaseCommand

index_path = os.path.join(settings.BUILD_DIR, 'index.html')
output_path = os.path.join(settings.TEMPLATES_DIR, 'base-react.html')


class Command(BaseCommand):
    help = 'Collect React index.html and static files'

    def handle(self, *args, **kwargs):
        client_dir = getattr(settings, 'CLIENT_DIR', 'client')
        if not os.path.exists(client_dir):
            self.stdout.write(self.style.ERROR(f'No client directory found'))
            return

        self.stdout.write('Building client app ...')
        subprocess.run(['npm', 'run', 'build'], cwd=client_dir)

        self.stdout.write('Collecting static files ...')
        call_command(
            'collectstatic', interactive=False,
            clear=True, verbosity=0
        )

        exists = os.path.exists(index_path)
        if not exists:
            # print or raise error
            self.stdout.write(self.style.ERROR(f'No index path'))
            return

        html = open(index_path).read()
        soup = BeautifulSoup(html, 'html.parser')

        soup.title.replace_with("{% block head %}{% endblock head %}")

        root = soup.find("div", id="root")
        root.insert_before("{% block body %}")
        root.insert_after("{% endblock body %}")

        style = soup.new_tag('style', type="text/css")
        style.string = "{% block css %}{% endblock css %}"

        soup.head.insert(len(soup.head.contents), style)

        with open(output_path, 'w', encoding='utf-8') as file:
            file.write(str(soup))

        self.stdout.write(
            self.style.SUCCESS(f'Exported file: {output_path}')
        )

Almost there

Create a templates directory in the root folder and add a base.html file.

mkdir templates
touch templates/base.html

In the templates/base.html file, paste the following snippet:

{% extends 'base-react.html' %}

{% block head %}

    <title>Django react app</title>

{% endblock head %}

<style>
    {% block css %}
        .wrapper{text-align:center;}
    {% endblock css %}
</style>

{% block body %} {% block main %}
<div class="wrapper">
    <h1>This is django</h1>
</div>

{% endblock main %}

{{ block.super }}

{% endblock body %}

Index page and static files

It's time to update the config/urls.py file.

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

from django.views.generic import TemplateView  # Our homepage

urlpatterns = [
    path('admin/', admin.site.urls),

    path('', TemplateView.as_view(template_name='base.html'), name='index')
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Finally, run the following commands:

./manage.py build

To summarize, always remember to ./manage.py build for changes to be reflected in Django.

In part two, we will build on this and see how to share initial data between our Django and React apps, Peace!