Overview

In this tutorial we will create a multipage web application using django, react and webpack.

This means:

  • Instead of handling routes by react, django will handle all the routes and once a route has been loaded, its upto you whether you want react to create different routes for that particular page or not.
  • Webpack will handle all the staticfiles headache i.e., all the optimizing stuff such as compressing and minifying.
  • Static files bundled by webpack will be served by django.

Let's start

Repository: https://github.com/nitinraturi/django-react-webpack

Dependencies

How to create it from scratch

Install python requirements:

  • Django  (you can install any version, I prefer 2.2 because of its long term support)
  • pip install django==2.2
  • Django Webpack Loader - It consumes the output generated by webpack-bundle-tracker and lets you use the generated bundles in django.
  • pip install django-webpack-loader==0.7.0

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 will be something like this:

. ├── conf │   ├── __init__.py │   ├── settings.py │   ├── urls.py │   └── wsgi.py ├── frontend ├── manage.py ├── static └── templates

Now configure 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

# pages/views.py from django.shortcuts import render def index1(request): return render(request, 'index1.html', {}) def index2(request): return render(request, 'index2.html', {})

Create a files urls.py inside pages app and two urls inside pages/urls.py to serve above two views.

touch pages/urls.py

# 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 and link pages/urls.py

# 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')), ] if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Now create the above two html files index1.html and index2.html inside templates directory

<!-- 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>

Now django's part is done, we have to configure react and webpack. you can verify the changes by running the server

python manage.py runserver 0:8080

Let's setup react part

Change your directory to frontend

cd frontend/

Now create a react app using

npx create-react-app .

Now install required npm packages

npm install --save-dev webpack webpack-cli webpack-bundle-tracker babel-cli babel-core babel-loader babel-preset-env babel-preset-es2015 babel-preset-react babel-register

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

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

Now configure webpack.config.js .babelrc package.json 

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" ] }

package.json

{ "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 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', # add this ] FRONTEND_DIR = os.path.join(BASE_DIR,'frontend') # path to frontend directory 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"), # path to webpack generated files ) # Status of files generated by webpack 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

{% 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>

In the above code, render_bundle 'staticfiles' : staticfiles is the name of the entrypoint and render_bundle will load the files generated by webpack inside dist folder.

How to run the server

Run django normally with runserver command

To run react open a seperate terminal inside frontend directory  and run

  • npm run watch # this command will automatically look for changes for static files and will update the dist folder. # You have to just refresh your browser where django is running.
  • In case of production run 
  • npm run build
  • Finally check the source code of html file in browser you can see webpack generated files inside dist folder.

That all.