👋
Welcome to my blog!

Integrating React with Django - The Right way

Building a Modern Web Application with React and Django - Discover how to handle asset management, routing, and state management while maintaining best practices in both frameworks. Whether you're starting a new project or optimizing an existing one, this guide provides practical insights from real-world implementation.

Integrating React with Django - The Right way
Development
Code
Best Practices
Clean Code
Python
React
Django

Published At

12/21/2024

Reading Time

~ 10 min read

In the world of modern web development, combining Django's robust backend capabilities with React's dynamic frontend features is a powerful combination. However, getting this integration right can be tricky. Today, I'll share my production-tested approach to seamlessly integrating React with Django, based on my experience building a financial management platform.

Why This Integration Matters 🤔

Before diving into the technical details, let's understand why you might want to combine React with Django:

  • Django provides a battle-tested backend framework with excellent admin interfaces and ORM
  • React offers a powerful frontend ecosystem with component-based architecture
  • Together, they enable building scalable, maintainable full-stack applications

The Challenge 🎯

The main challenges when integrating React with Django include:

  • Setting up a development environment that works for both frameworks
  • Managing build processes and asset compilation
  • Handling routing between Django and React
  • Configuring proper production deployment
  • Better Developer Experience, not to manually reload the page after every change

Let's solve these challenges step by step.

The Solution: Our Architecture 🏗️

We'll use Vite for our React build process and integrate it with Django's template system. Here's how we structured our solution:

shell
rancho/
├── src/               # React application
   ├── components/
   ├── app.tsx
   └── root.tsx
├── rancho/            # Django application
   ├── settings.py
   ├── urls.py
   ├── **
├── frontend-config/
   ├── templatetags/vite.py
├── templates/         # Django templates
   ├── base.html
   ├── **
├── infrastructure/**  # AWS CDK files for deployment
├── package.json
├── vite.config.ts
└── manage.py
shell
rancho/
├── src/               # React application
   ├── components/
   ├── app.tsx
   └── root.tsx
├── rancho/            # Django application
   ├── settings.py
   ├── urls.py
   ├── **
├── frontend-config/
   ├── templatetags/vite.py
├── templates/         # Django templates
   ├── base.html
   ├── **
├── infrastructure/**  # AWS CDK files for deployment
├── package.json
├── vite.config.ts
└── manage.py

With this approach, we can run our all the commands from root directory and it is easy to integrate any tool for both frontend and backend.

Step 1: Setting Up the Base 📄

First install Django and create base project and in the same base directory install react with vite.

Once installed, let's look at our Django base template that will serve as the entry point for our React application:

base.html
html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="shortcut icon" href="{% static 'favicon.ico' %}">
 
	<!-- This condition can be updated based on your local and dev/prod setup -->
  {% if debug %}
    <!--This enables hot-reloading in lower env for better DX-->
    <script type="module">
      import RefreshRuntime from 'http://localhost:5173/@react-refresh'
 
      RefreshRuntime.injectIntoGlobalHook(window)
      window.$RefreshReg$ = () => {
      }
      window.$RefreshSig$ = () => (type) => type
      window.__vite_plugin_react_preamble_installed__ = true
    </script>
 
    <script type="module" src="http://localhost:5173/@vite/client"></script>
    <script type="module" src="http://localhost:5173/src/main.tsx"></script>
  {% else %}
    <!--This is for deployed env, where template will get proper JS and CSS compiles files from Vite-->
    {% load vite %}
    <!--We are create vite and vite_asset tags in next step-->
    {% vite_asset 'src/main.tsx' 'css' as css_files %}
    {% for css_file in css_files %}
      <link rel="stylesheet" href="{{ css_file }}">
    {% endfor %}
 
    {% vite_asset 'src/main.tsx' 'js' as js_files %}
    {% for js_file in js_files %}
      <script type="module" src="{{ js_file }}"></script>
    {% endfor %}
  {% endif %}
 
</head>
<body>
<div id="root"></div>
</body>
</html>
base.html
html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="shortcut icon" href="{% static 'favicon.ico' %}">
 
	<!-- This condition can be updated based on your local and dev/prod setup -->
  {% if debug %}
    <!--This enables hot-reloading in lower env for better DX-->
    <script type="module">
      import RefreshRuntime from 'http://localhost:5173/@react-refresh'
 
      RefreshRuntime.injectIntoGlobalHook(window)
      window.$RefreshReg$ = () => {
      }
      window.$RefreshSig$ = () => (type) => type
      window.__vite_plugin_react_preamble_installed__ = true
    </script>
 
    <script type="module" src="http://localhost:5173/@vite/client"></script>
    <script type="module" src="http://localhost:5173/src/main.tsx"></script>
  {% else %}
    <!--This is for deployed env, where template will get proper JS and CSS compiles files from Vite-->
    {% load vite %}
    <!--We are create vite and vite_asset tags in next step-->
    {% vite_asset 'src/main.tsx' 'css' as css_files %}
    {% for css_file in css_files %}
      <link rel="stylesheet" href="{{ css_file }}">
    {% endfor %}
 
    {% vite_asset 'src/main.tsx' 'js' as js_files %}
    {% for js_file in js_files %}
      <script type="module" src="{{ js_file }}"></script>
    {% endfor %}
  {% endif %}
 
</head>
<body>
<div id="root"></div>
</body>
</html>

Managing Vite Assets in Django Templates 🔧

One crucial aspect of our React-Django integration is handling Vite's asset serving correctly, especially in production. We implemented a custom template tag that intelligently serves the right assets based on your environment.

First, let's create a template tags file:

frontend-config/templatetags/vite_assets.py
python
# this templatetags can be added as part of the apps,
# but I created frontend-config app for managing this. In future,
# I can also manage any new settings I need to configure for frontend.
 
import json
from pathlib import Path
 
from django import template
from django.conf import settings
 
register = template.Library()
 
@register.simple_tag
def vite_asset(entry, filetype):
    if settings.DEBUG:
        return []
    manifest_path = settings.BASE_DIR / "static/dist/.vite/manifest.json"
    try:
        with Path(manifest_path).open("r") as f:
            manifest = json.load(f)
    except FileNotFoundError:
        return []
 
    entry_name = entry if entry in manifest else entry + ".js"
 
    base_path = "/static/dist/"
    if filetype == "css":
        return [base_path + css_file for css_file in manifest[entry_name].get("css", [])]
    if filetype == "js":
        return [base_path + manifest[entry_name]["file"]]
    return []
frontend-config/templatetags/vite_assets.py
python
# this templatetags can be added as part of the apps,
# but I created frontend-config app for managing this. In future,
# I can also manage any new settings I need to configure for frontend.
 
import json
from pathlib import Path
 
from django import template
from django.conf import settings
 
register = template.Library()
 
@register.simple_tag
def vite_asset(entry, filetype):
    if settings.DEBUG:
        return []
    manifest_path = settings.BASE_DIR / "static/dist/.vite/manifest.json"
    try:
        with Path(manifest_path).open("r") as f:
            manifest = json.load(f)
    except FileNotFoundError:
        return []
 
    entry_name = entry if entry in manifest else entry + ".js"
 
    base_path = "/static/dist/"
    if filetype == "css":
        return [base_path + css_file for css_file in manifest[entry_name].get("css", [])]
    if filetype == "js":
        return [base_path + manifest[entry_name]["file"]]
    return []

This implementation provides several key benefits:

  1. Environment Awareness: Automatically handles both development and production environments
  2. Error Handling: Provides clear error messages if the manifest is missing or invalid
  3. Type Safety: Supports different asset types (JS/TS and CSS)
  4. Cache Busting: Uses Vite's manifest for automatic cache busting in production
  5. Better DX: Hot reloading as part of Vite with Django

Step 2: Configuring Vite 🛠️

Let's set up our Vite configuration to work seamlessly with Django:

vite.config.ts
ts
import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite'
 
import path from 'path'
 
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@/assets': path.resolve(__dirname, './static'),
      '@': path.resolve(__dirname, './src'),
    },
  },
  build: {
    manifest: true,
    outDir: './static/dist',
    emptyOutDir: true,
    rollupOptions: {
      input: 'src/main.tsx',
    },
  },
})
vite.config.ts
ts
import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite'
 
import path from 'path'
 
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@/assets': path.resolve(__dirname, './static'),
      '@': path.resolve(__dirname, './src'),
    },
  },
  build: {
    manifest: true,
    outDir: './static/dist',
    emptyOutDir: true,
    rollupOptions: {
      input: 'src/main.tsx',
    },
  },
})

Step 3: Django Settings Configuration ⚙️

Configure your Django settings to handle static files and templates:

settings.py
python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # Updated: Template directory
        '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',
            ],
        },
    },
]
 
# Updated: Static files configuration
STATIC_URL = 'static/'
STATIC_ROOT = 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / "static/", BASE_DIR / "src/assets/"]
settings.py
python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # Updated: Template directory
        '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',
            ],
        },
    },
]
 
# Updated: Static files configuration
STATIC_URL = 'static/'
STATIC_ROOT = 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / "static/", BASE_DIR / "src/assets/"]

Step 4: Django URL Configuration 🎯

First, let's set up our Django URLs to handle both API routes and serve our React application:

urls.py
python
from django.contrib import admin
from django.urls import include, path, re_path
from django.views.generic import TemplateView
from django.conf import settings
from django.conf.urls.static import static
 
urlpatterns = [
    # Updated: Admin interface
    path("admin/", admin.site.urls),
 
    # Updated: API endpoints
    path("api/accounts/", include("allauth.urls")),
    path("api/finance/", include("finance.urls")),
 
    # Updated: Health check endpoint
    path("health/", include("health.urls")),
 
    # Updated: Serve our React app
    re_path(
        r"^.*$",  # Match all other URLs
        TemplateView.as_view(
            template_name="base.html",
            extra_context={"debug": settings.DEBUG}
        ),
        name="home",
    ),
]
 
# Updated: Serve static files in development
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urls.py
python
from django.contrib import admin
from django.urls import include, path, re_path
from django.views.generic import TemplateView
from django.conf import settings
from django.conf.urls.static import static
 
urlpatterns = [
    # Updated: Admin interface
    path("admin/", admin.site.urls),
 
    # Updated: API endpoints
    path("api/accounts/", include("allauth.urls")),
    path("api/finance/", include("finance.urls")),
 
    # Updated: Health check endpoint
    path("health/", include("health.urls")),
 
    # Updated: Serve our React app
    re_path(
        r"^.*$",  # Match all other URLs
        TemplateView.as_view(
            template_name="base.html",
            extra_context={"debug": settings.DEBUG}
        ),
        name="home",
    ),
]
 
# Updated: Serve static files in development
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Why This Approach Works 🤔

Our URL configuration achieves several important goals:

  1. API First: All our API endpoints are prefixed with /api/, making them easily distinguishable
  2. React Router Compatibility: The catch-all pattern (^.*$) ensures React Router handles client-side routing
  3. Development Friendly: Static files are served automatically in development
  4. Clean Separation: Admin interface remains accessible while not interfering with React routes

Step 4: Development Workflow 🔄

Here's our development workflow:

  1. Run Django development server:
bash
python manage.py runserver
bash
python manage.py runserver
  1. In a separate terminal, run Vite development server:
bash
pnpm install
pnpm dev
bash
pnpm install
pnpm dev

The magic happens in our base template - it automatically detects the development environment and loads either the Vite dev server assets or the production build files.

Best Practices and Tips 💡

  1. Asset Management
  • Use Vite's build manifest for cache-busting
  • Configure proper CORS settings for development
  • Implement proper static file serving in production
  1. Development Experience
  • Utilize hot module replacement for faster development
  • Set up proper source maps for debugging
  • Use path aliases for cleaner imports
  1. Performance Optimization
  • Enable code splitting for better load times
  • Implement proper caching strategies
  • Use compression for static assets

Conclusion 🎉

Integrating React with Django doesn't have to be complicated. With the right setup and configuration, you can leverage the best of both worlds - Django's powerful backend capabilities and React's dynamic frontend features.

Remember to:

  • Keep development and production environments consistent
  • Utilize proper build tools and optimization techniques
  • Follow best practices for both Django and React development

The complete code for this integration can be found in our project structure, serving as a real-world example of this architecture in action.

I've tried other way as well, where each django template will behave as new page for react, just like NextJS. But this can be improved a lot. https://github.com/abheist/integratedReact

Happy coding! 🚀

Do you have any questions, or simply wish to contact me privately? Don't hesitate to shoot me a DM on Twitter.

Have a wonderful day.
Abhishek 🙏

Join My Exclusive Newsletter Community

Step into a world where creativity intersects with technology. By subscribing, you'll get a front-row seat to my latest musings, full-stack development resources, and exclusive previews of future posts. Each email is a crafted experience that includes:

  • In-depth looks at my covert projects and musings to ignite your imagination.
  • Handpicked frontend development resources and current explorations, aimed at expanding your developer toolkit.
  • A monthly infusion of inspiration with my personal selection of quotes, books, and music.

Embrace the confluence of words and wonder, curated thoughtfully and sent straight to your inbox.

No fluff. Just the highest caliber of ideas.