DEV Community

Zach Taylor
Zach Taylor

Posted on • Edited on

How to Serve a React Single-Page App with Django

TL;DR

This article has been updated for Django 4, React 18 and Webpack 5. You can download the finished code from my GitHub repository. Leave a star if you found it helpful!

Intro

This is a guide on setting up Django to serve a React single-page application. Going through this process really helped me understand Webpack and Babel better, so if Django + React isn't your stack, you might still learn something!

All the commands and file paths you'll see are relative to the project root unless otherwise specified. If you don't have a project already, you can create one with

$ pip install Django
$ django-admin startproject django_react_starter
$ cd django_react_starter
$ python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Let's get to it.

Step 1 - Create a Front End App

The first thing you'll want to do is create a Django app for your front end. I called mine frontend.

$ python manage.py startapp frontend
Enter fullscreen mode Exit fullscreen mode

Add your app to INSTALLED_APPS in your project's settings.py file.

INSTALLED_APPS = [
  "frontend",
  ...
]
Enter fullscreen mode Exit fullscreen mode

Step 2 - Create the View

Now that your frontend app is created, you need to create the Django view that will serve the React app.

In your frontend folder, create a folder called templates, and inside that, create a folder called frontend. In frontend/templates/frontend/ create an index.html file and put the following inside it.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Site</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Pretty simple. This HTML file is the single page in your single-page application. The <div id="app"></div>is where you will render your React app.

Next, you need to wire up a view to your index page. In frontend/views.py add the following.

from django.shortcuts import render

def index(request):
  return render(request, "frontend/index.html")
Enter fullscreen mode Exit fullscreen mode

All this function does is render the index.html page you just created.

Now you need to tell Django the url at which it will find your index.html page. In your project level urls.py, add the following to the bottom of your urlpatterns (and make sure to import include!)

from django.urls import include, path

urlpatterns = [
  ...,
  path("", include("frontend.urls"))
]
Enter fullscreen mode Exit fullscreen mode

In your frontend folder, create a urls.py file and put the following in it.

from django.urls import path
from . import views

urlpatterns = [
  path("", views.index)
]
Enter fullscreen mode Exit fullscreen mode

These two urls.py files tell Django to call your index view when someone visits the url /. Try running the server with

$ python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Go to localhost:8000 in your browser and you should see a blank page with My Site on the tab.

Great! Now let's add React to your HTML page.

Step 3 - Set up React, Babel, and Webpack

From the root of your project, run npm init -y to create a package.json file. You'll need several packages for this setup. The first two are React itself and ReactDom.

$ npm install react react-dom
Enter fullscreen mode Exit fullscreen mode

Once you have React and ReactDom installed, you'll need to get Babel and Webpack set up.

Babel

Let's start with Babel. To install Babel, run

$ npm install --save-dev @babel/core
Enter fullscreen mode Exit fullscreen mode

If you don't already know, Babel is a JavaScript transpiler, which essentially means it lets you use things in your JavaScript code (like JSX) that the browser wouldn't understand natively.

By default, Babel does nothing. If you want Babel to transpile a specific thing in your JavaScript code, you need to install a plugin for it. Your project might need several plugins, so Babel also has this concept of presets, which are just collections of plugins. You will only need two presets for this setup: @babel/preset-env and @babel/preset-react.

$ npm install --save-dev @babel/preset-env @babel/preset-react
Enter fullscreen mode Exit fullscreen mode

@babel/preset-env is a collection of plugins that allows you to use the latest JavaScript features even if your browser doesn't support them yet. @babel/preset-react is a collection of plugins that allows you to do React things in a nice way, like use JSX instead of nested calls to React.createElement.

Once you install the presets, you need to tell Babel to use them. Create a .babelrc file in the root of your project with the following content.

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}
Enter fullscreen mode Exit fullscreen mode

Webpack

Webpack is a tool that will take your codebase and all its dependencies and transform them into one or more bundles, or files, that can be executed in a browser. The way it works is pretty simple, in concept. You give Webpack a JavaScript file (the entry point), and it will recursively gather all the dependencies of that file (indicated with import or require statements) and combine them into one, larger, file.

If you're not used to JavaScript, it might not make sense why Webpack is needed. Historically, there was no way to import or require resources in JavaScript running in the browser. You either had to put all your JavaScript into one file or put it in several files along with a <script> tag for each in your HTML. That's fine if your web site doesn't have much JavaScript, but it quickly becomes messy and hard to maintain as the amount of JavaScript you have grows. Webpack allows you to separate your JavaScript code into reusable files and import or require what you need.

And Webpack isn't just for JavaScript. It also allows you to import JSON by default as well, and it can be configured to allow imports from .css, .sass, .hbs and more with loaders.

For this Webpack setup, you'll need several packages.

$ npm install --save-dev webpack webpack-cli webpack-bundle-tracker babel-loader css-loader style-loader clean-webpack-plugin
Enter fullscreen mode Exit fullscreen mode

That's quite a few! Let's break it down:

  • webpack is... well, Webpack
  • webpack-cli allows you to run Webpack commands from the command line
  • webpack-bundle-tracker is a plugin that writes some stats about the bundle(s) to a JSON file.
  • babel-loader is a loader that tells Webpack to run Babel on the file before adding it to the bundle.
  • css-loader and style-loader are loaders that allow you to import .css files into your JavaScript
  • clean-webpack-plugin is a plugin that deletes old bundles from Webpack's output directory every time a new bundle is created.

Now create a file called webpack.config.js in the root of your project. This is where you'll configure Webpack to use the plugins and loaders we just installed.

const path = require('path')
const BundleTracker = require('webpack-bundle-tracker')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  entry: {
    frontend: './frontend/src/index.js',
  },
  output: {
    path: path.resolve('./frontend/static/frontend/'),
    filename: '[name]-[hash].js',
    publicPath: 'static/frontend/',
  },
  plugins: [
    new CleanWebpackPlugin(),
    new BundleTracker({
      path: __dirname,
      filename: './webpack-stats.json',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
}
Enter fullscreen mode Exit fullscreen mode

Let's break it down:

  • entry tells Webpack where to start gathering your code
  • output is where Webpack will put the finished bundle.
  • plugins tells Webpack which plugins to use
  • module is where you configure your loaders. Each rule tells Webpack that whenever it comes across a file that matches the test regex, it should use the specified loaders to process it.

Now that Webpack is set up, you'll want to add a couple scripts to your package.json to run Webpack.

{
  ...,
  "scripts": {
    ...,
    "dev": "webpack --config webpack.config.js --watch --mode development",
    "build": "webpack --config webpack.config.js --mode production"
  }
}
Enter fullscreen mode Exit fullscreen mode

These scripts allow you to create a development bundle with npm run dev and a production bundle with npm run build.

Step 4 - Add the Bundle to your HTML

Now that you have a process to create a JavaScript bundle, you need to include the bundle in your HTML page. To do that, you'll need to install one more package.

$ pip install django-webpack-loader
Enter fullscreen mode Exit fullscreen mode

This package allows Django to use the stats produced by webpack-bundle-tracker to load the correct bundle in your HTML page. In your settings.py file, add the following configuration.

import os

...

INSTALLED_APPS = [
  "webpack_loader",
  ...
]

...

WEBPACK_LOADER = {
  "DEFAULT": {
    "BUNDLE_DIR_NAME": "frontend/",
    "STATS_FILE": os.path.join(BASE_DIR, "webpack-stats.json")
  }
}
Enter fullscreen mode Exit fullscreen mode

Then in your frontend/templates/frontend/index.html file, add a template tag to load the bundle into your page.

<!DOCTYPE html>
+ {% load render_bundle from webpack_loader %}
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Site</title>
</head>
<body>
  <div id="app"></div>
+ {% render_bundle 'frontend' %}
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Step 5 - Create Your React App

We now have all the pieces in place for you to begin writing your React application! In your frontend folder, create a folder called src, and inside that, create a file called App.js with the following content.

import React from 'react'

const App = () => {
  return (
    <div>Hello, World!</div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

In your frontend/src folder, create another file called index.js with the following.

import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

const root = createRoot(document.getElementById("app"));
root.render(<App />);
Enter fullscreen mode Exit fullscreen mode

In the terminal navigate to your project and run

$ npm run dev
Enter fullscreen mode Exit fullscreen mode

In another terminal window or tab, navigate to your project and run

$ python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

The order you run these two commands is important. Make sure you do npm run dev first.

Navigate to localhost:8000 in your browser and you should see Hello, World! printed on the screen. Awesome! You've successfully set up Django to serve a React single-page application. You can view or download the finished code on my GitHub repository.

Going through the process of setting this up was so helpful to me in understanding Webpack and Babel. I hope you found it enlightening as well!

Top comments (18)

Collapse
 
svandegar profile image
Seraphin Vandegar

Hey, thanks for the article, that really helped me!

I had to bump the webpack-bundle-tracker version to 1.0.0 because of a breaking change in the python package webpack_loader specifically made to match the v1 of the js lib (you see the nice circle here :o).

Collapse
 
mfsi profile image
mfsi-shubhankar

thanks man!

Collapse
 
ckot profile image
Scott Silliman

Hi Zack, Great Post! I ended up having to add

publicPath: "static/frontend/"
Enter fullscreen mode Exit fullscreen mode

to the config passed to the BundleTracker constructor in my webpack.config.js

by default my webstat-stats.json was being generated with:

"publicPath": "auto"  
Enter fullscreen mode Exit fullscreen mode

which got prepended as

chunks: {
   frontend:  [{ ...
      publicPath: "autofrontend-<HASH>.js",
    ...
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ckot profile image
Scott Silliman

I just compared your package.json to mine, and perhaps this has to do with running:
npm install --save-dev webpack webpack-cli (without specifing versions) installed:

"webpack": "^4.44.2",
"webpack-cli": "^3.3.12"
Enter fullscreen mode Exit fullscreen mode

several months ago, but running it today installed

"webpack": "^5.15.0",
"webpack-cli": "^4.3.1"
Enter fullscreen mode Exit fullscreen mode

? Just a guess

Collapse
 
zachtylr21 profile image
Zach Taylor

I just tested this and yes, it looks like with the new versions of webpack and webpack-cli, the use of publicPath is required to make Django serve the bundle at the correct url. I've updated the article and the GitHub repo. Thanks for pointing this out!

Collapse
 
sbalasa profile image
Santhosh Balasa

Thanks but it fails:

14:08 ~/rapyd (main)$ npm run dev

> rapyd@1.0.0 dev /home/rapyd/rapyd
> webpack --config webpack.config.js --watch --mode development

[webpack-cli] SyntaxError: Invalid regular expression: /(\p{Uppercase_Letter}+|\p{Lowercase_Letter}|\d)(\p{Uppercase_Letter}+)/: Invalid escape
    at pathToArgumentName (/home/rapyd/rapyd/node_modules/webpack/lib/cli.js:66:4)
    at addFlag (/home/rapyd/rapyd/node_modules/webpack/lib/cli.js:170:16)
    at traverse (/home/rapyd/rapyd/node_modules/webpack/lib/cli.js:243:21)
    at traverse (/home/rapyd/rapyd/node_modules/webpack/lib/cli.js:299:23)
    at traverse (/home/rapyd/rapyd/node_modules/webpack/lib/cli.js:248:24)
    at Object.getArguments (/home/rapyd/rapyd/node_modules/webpack/lib/cli.js:308:2)
    at WebpackCLI.getBuiltInOptions (/home/rapyd/rapyd/node_modules/webpack-cli/lib/webpack-cli.js:681:47)
    at loadCommandByName (/home/rapyd/rapyd/node_modules/webpack-cli/lib/webpack-cli.js:838:38)
    at Command.program.action (/home/rapyd/rapyd/node_modules/webpack-cli/lib/webpack-cli.js:1462:23)
    at Command.listener [as _actionHandler] (/home/rapyd/rapyd/node_modules/webpack-cli/node_modules/commander/index.js:922:31)

npm ERR! Linux 5.4.0-1038-aws
npm ERR! argv "/usr/bin/node" "/usr/bin/npm" "run" "dev"
npm ERR! node v8.16.2
npm ERR! npm  v4.3.0
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! rapyd@1.0.0 dev: `webpack --config webpack.config.js --watch --mode development`
npm ERR! Exit status 2
npm ERR! 
npm ERR! Failed at the rapyd@1.0.0 dev script 'webpack --config webpack.config.js --watch --mode development'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the rapyd package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     webpack --config webpack.config.js --watch --mode development
npm ERR! You can get information on how to open an issue for this project with:
npm ERR!     npm bugs rapyd
npm ERR! Or if that isn't available, you can get their info via:
npm ERR!     npm owner ls rapyd
npm ERR! There is likely additional logging output above.

npm ERR! Please include the following file with any support request:
npm ERR!     /home/rapyd/.npm/_logs/2021-06-20T14_08_07_461Z-debug.log
Enter fullscreen mode Exit fullscreen mode
Collapse
 
saiid-hc profile image
Saiid El Hajj Chehade

Thanks for the tutorial! I had problems finding the static files too. I fixed it by

  1. changing publicPath: "static/frontend" to publicPath: "/static/frontend" in webpack.config.js.
  2. changing STATIC_URL = "static/" to STATIC_URL = "/static/".
Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
cbirchby profile image
cbirchby • Edited

Great article! Thank you for sharing.

I'm getting an error from the server console and browser console when the template is called. Has anyone come across this?

Browser Console:

GET http://127.0.0.1:8001/frontend/static/frontend/frontend-dc3188e75be82e0a01e0.js net::ERR_ABORTED 404 (Not Found)


127.0.0.1/:1 Refused to execute script from 
'http://127.0.0.1:8001/frontend/static/frontend/frontend-dc3188e75be82e0a01e0.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
Enter fullscreen mode Exit fullscreen mode

Server Console:

Not Found: /frontend/static/frontend/frontend-dc3188e75be82e0a01e0.js
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dlj profile image
Dolores Lopez Joffre

Hi, Zach! I'm trying to figure out how to make this all work with a Typescript setup, do you have an example of the .babelrc or webpack-config.js changes I should make for it to be up and running? Thanks in advance, great post!

Collapse
 
zachtylr21 profile image
Zach Taylor

Hey! I know it's been a while but you can find a TypeScript example here

Collapse
 
samar08 profile image
Samar • Edited

I have followed the entire article as it is. But i am getting the below mentioned error. Does anyone know how to fix it?

django-react-starter\frontend\templates\frontend\index.html, error at line 11
Exception Value:

expected string or bytes-like object

8

9



10

11 ... render_bundle 'frontend' ..

12

13
Collapse
 
matt_b1f8ccb21f55eb profile image
matt_b1f8ccb21f55eb • Edited

UPDATE: I did end up solving this, but I'm leaving my original message up. See below for the fix.

Hey @samar08 I'm receiving the same error. I've been trying to find a solution for a couple of days now and I'm coming up empty. I wasn't sure if it was a version thing or not. I'm using Python 3.9 and Django 4, but I tried Django 3 and had the same error. Are you using Python 3.9 or a different version? The babel and webpack versions I'm using seem to be releases from 2021 or earlier, so I don't think it's that. Have you solved it yet?

EDIT: I solved it! I had a newer version of webpack that was causing the issue. Python 3.9 and Django 4 seem to work fine so far.

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

Collapse
 
dexoon profile image
Dexoon • Edited

On 26th line of webpack.config.js redundant '},'

Collapse
 
zachtylr21 profile image
Zach Taylor

Good catch. Fixed!

Collapse
 
theophylline profile image
Mike Qiu

Thanks for the great post. I was wondering if you can achieve the same thing with create-react-app so that you don't have to do the webpack and babel setup?

Collapse
 
surajmeena profile image
Suraj Meena

yeah, I am interested in knowing the same. Why is this article so complex when compared with this one. Essentially both try to achieve the same thing and atleast the latter one is tested by me and is a simpler approach