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.
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:
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
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:
{% 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>
{% 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:
# 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 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:
- Environment Awareness: Automatically handles both development and production environments
- Error Handling: Provides clear error messages if the manifest is missing or invalid
- Type Safety: Supports different asset types (JS/TS and CSS)
- Cache Busting: Uses Vite's manifest for automatic cache busting in production
- 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:
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',
},
},
})
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:
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/"]
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:
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)
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:
- API First: All our API endpoints are prefixed with
/api/
, making them easily distinguishable - React Router Compatibility: The catch-all pattern (
^.*$
) ensures React Router handles client-side routing - Development Friendly: Static files are served automatically in development
- Clean Separation: Admin interface remains accessible while not interfering with React routes
Step 4: Development Workflow 🔄
Here's our development workflow:
- Run Django development server:
python manage.py runserver
python manage.py runserver
- In a separate terminal, run Vite development server:
pnpm install
pnpm dev
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 💡
- Asset Management
- Use Vite's build manifest for cache-busting
- Configure proper CORS settings for development
- Implement proper static file serving in production
- Development Experience
- Utilize hot module replacement for faster development
- Set up proper source maps for debugging
- Use path aliases for cleaner imports
- 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 🙏