Django and React - How to use InertiaJS in your Django and React Apps
When building an app with Django and React the conventional wisdom suggests building an API using Django Rest Framework, and then layering React on top as a Single Page Application (SPA). While this approach is certainly effective, itβs not the only path you can take.
In this high-level tutorial, we will explore a simpler, yet equally powerful alternative: the combination of Django, React, and InertiaJS. This makes it easier to use React in Django and reduces complexity without sacrificing functionality.
What is InertiaJS
Inertia allows you to create fully client-side rendered, single-page apps, without the complexity that comes with modern SPAs. It does this by leveraging existing server-side patterns that you already love.
In plain english? Inertia connects your fancy frontend (React, Svelte, or Vue) frontend with your Django backend.
This means you can keep all the good things that Django already offers, authentication, permissions, python π etc and also get that fast, beautiful UI without building an API, if you donβt want to.
How does InertiaJS this work?

Inertia replaces server-side templates with JavaScript components. It acts as a client-side router for smoother page transitions.
-
Page visits are intercepted by Inertia and forwarded to the server via XHR.
-
The server responds to Inertia visits with a JSON response, not a full HTML response.
-
Inertia dynamically updates the client-side page and the browser's history state.
Technical details aka βThe protocolβ
InertiaJS is not a framework. It is a protocol. It specifies how requests and responses should be made between a client and a server. On the client side, there is a router for handling requests and responses, the backend is configured, (usually via middleware) to respond with the appropriate response.
Inertia.js Protocol Summary
-
HTML Responses : The initial request to an Inertia app is a full-page browser request. The server returns a full HTML document with a root
<div>
that includes a JSON-encoded page object for the initial page. -
Inertia Responses : Subsequent requests are made via XHR with a
X-Inertia
header. The server responds with a JSON-encoded page object instead of a full HTML document. -
Page Object : Inertia uses a page object to share data between the server and the client. It includes the component name, props (data), URL, and current asset version.
-
Asset Versioning : Inertia tracks the current version of the siteβs assets. If an asset changes, Inertia makes a full-page visit instead of an XHR visit.
-
Partial Reloads : Inertia allows requesting a subset of the props from the server on subsequent visits to the same page component for performance optimization.
In future posts, we will dive deeper and write our custom middleware. You can find the more technical bits here .
A Deep dive - The Inertia decorator
To understand how InertiaJS works, we can look at the view decorator and how it processes requests.
Decorators
In Python, a decorator is a design pattern that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate. Here are some important points about decorators:
Decorators provide a simple syntax for calling higher-order functions.
A Python decorator is a specific change to the Python syntax that allows us to more conveniently alter functions and methods (and possibly classes in a future version).
This supports more readable applications of the DecoratorPattern but also other uses as well.
Itβs a function that takes another function and extends the behavior of the latter function without explicitly modifying it.
In Django, decorators are used in various places, for example, to decorate view functions to restrict access to certain views based on conditions, or to pass some arguments based on conditions.
Here is an example of a simple Python decorator:
textdef my_decorator(func): def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper @my_decorator def say_hello(): print("Hello!") # Output: # Something is happening before the function is called. # Hello! # Something is happening after the function is called.
In the above example,
@my_decorator
is used to βdecorateβ thesay_hello
function. The decorator functionmy_decorator
takes a function as an argument, defines a new functionwrapper
that calls the original function with some additional behavior, and returns this new function.
A typical Django inertia view looks something like the one below.
@inertia('component/path/typically-no-extension')
def some_view(request):
return {'key':'some value'}
# the decorator is this:
def inertia(component, layout=None, ssr_enabled=False):
def decorator(func):
@wraps(func)
def inner(request, *args, **kwargs):
props = func(request, *args, **kwargs) # <- view function is run first
if not isinstance(props, dict):
return props
# we call the 'render' function. This is where the magic happens.
return render(
request, component, props, layout=layout, ssr_enabled=ssr_enabled
)
return inner
return decorator
-
At first glance, the view is returning a dictionary, but that is not the end of the story.
-
The decorator processes the value that is returned from the view. Notice how the view function is run first.
-
the render function is not the Django render shortcut. This is a custom render function that adheres to the Inertia protocol.
Letβs look at the inertia render function in detail.
def render(request, component, props={}, template_data={}):
def is_a_partial_render():
"""
Partial renders
"""
return 'X-Inertia-Partial-Data' in request.headers and request.headers.get('X-Inertia-Partial-Component', '') == component
def partial_keys():
return request.headers.get('X-Inertia-Partial-Data', '').split(',')
def deep_transform_callables(prop):
...
return prop
def build_props():
...
return deep_transform_callables(_props)
def render_ssr():
"""
If ssr is enabled, render the server side render the page.
"""
data = json_encode(page_data(), cls=settings.INERTIA_JSON_ENCODER)
# this is a post request to a nodejs process that is running.
response = requests.post(
f"{settings.INERTIA_SSR_URL}/render",
data=data,
headers={"Content-Type": "application/json"},
)
response.raise_for_status()
return base_render(request, 'inertia_ssr.html', {
'inertia_layout': settings.INERTIA_LAYOUT,
**response.json()
})
def page_data():
"""
Returns the data that will be stored injected into the page
"""
return {
'component': component,
'props': build_props(),
'url': request.build_absolute_uri(),
'version': settings.INERTIA_VERSION,
}
# if this is an inertia request only return the JSON data.
# the inertiajs is already loaded in the frontend.
if 'X-Inertia' in request.headers:
return JsonResponse(
data=page_data(),
headers={
'Vary': 'Accept',
'X-Inertia': 'true',
},
encoder=settings.INERTIA_JSON_ENCODER,
)
# use this if ssr is enabled
if settings.INERTIA_SSR_ENABLED:
try:
return render_ssr()
except Exception:
pass
# if inertiajs is not loaded(all initial requests)
# render a normal django template (shell) + embedd page data.
return base_render(request, 'inertia.html', {
'inertia_layout': settings.INERTIA_LAYOUT,
'page': json_encode(page_data(), cls=settings.INERTIA_JSON_ENCODER),
**template_data,
})
The
render
function is a crucial part of integrating Inertia.js with Django and React. It handles the rendering of Inertia.js pages in your Django application and supports both full and partial updates, as well as server-side rendering. Here's a high-level overview of what is happening.
-
Partial Rendering : The function checks if the current request is a partial render, meaning only a portion of the page needs to be updated. This is determined by specific headers in the request.
-
Properties Building : The function builds the properties object that will be passed to the Inertia.js page. It includes
global properties
those specific to this render. If the render is partial, it only includes the necessary properties. -
Server-Side Rendering : If server-side rendering is enabled, the function sends a request to the Inertia SSR server with the page data and retrieves the rendered HTML.
-
Page Data Construction : The function constructs the data object for the Inertia.js page, including the component name, the properties, the current URL, and the Inertia version.
-
Response : Depending on the request, the function returns a JSON response with the page data or the rendered HTML.
Building your own InertiaJS - React - Django app
The remainder of this article will focus on how you would go about installing inertia and building an app. The focus is on how everything fits together. In the next article, we will build a full app.
The main components of this stack
-
Backend - Django
-
Frontend Stack
-
Framework - Svelte, Vue React
-
InertiaJS - the client-side router
-
Vite - bundler and dev server
-
How to structure your application
/myproject
|β- /apps
| |β- /base
| | |β- __init__.py
| | |β- admin.py
| | |β- apps.py
| | |β- /migrations
| | |β- models.py
| | |β- tests.py
| | |β- views.py
| | |β- urls.py
|β- /config
| |β- __init__.py
| |β- settings.py
| |β- urls.py
| |β- wsgi.py
|β- /frontend
| |β- /src
| | |β- /pages
| | | |β- /base
| | | | |β- components
| | | | |β- page.jsx
| | |β- app.tsx
| |β- package.json
| |β- vite.config.js
|β- manage.py
|β- requirements.txt
Setup the Django Backend
The following libraries will make it easier to work with InertiaJS and Vite.
-
Django Vite - ViteJS is a frontend bundler that helps manage dependencies for your frontend app. Run pip install
django-vite
to add the package. -
inertia-django - This library provides a few scripts or βadaptersβ (namely, Django middleware, a render function (already discussed above), config, and templates) for handling InertiaJS requests. Run pip install
inertia-django
to add the package.
Add the installed apps to your settings file
# Installed Apps
INSTALLED_APPS = [
# django apps,
'inertia',
'django_vite'
# your project's apps,
]
MIDDLEWARE = [
# other middleware (security, csrf etc)
'inertia.middleware.InertiaMiddleware',
'apps.common.inertia_share.middleware', # this does not come with the library see below
]
INERTIA_LAYOUR = 'layouts/inertia.html'
-
Add both Django Vite and InertiaJS to the installed apps.
-
The
Middleware
the list includes our inertia share middle. See the implementation below. -
Custom middleware
from inertia import share
from django.conf import settings
from .models import User
def inertia_share(get_response):
def middleware(request):
share(request,
app_name=settings.APP_NAME,
userId=lambda: request.user.id, # evaluated lazily at render time
)
return get_response(request)
return middleware
-
Custom middleware for sharing data that is common across views. These are the global properties that we referred to earlier in the deep dive section.
-
In this case, we include the
userId
but this could be anything. Just make sure not to overload this with logic that could slow the request down.
Inertia Templates
{% extends inertia_layout %}{% load django_vite %}
{% vite_asset 'frontend/src/app.tsx' %} <- entry point. See ViteConfig later
{% block inertia %}
<div id="app" data-page="{{page|escape}}"></div>
{% endblock inertia %}
-
This is the base layout template. This is the template that is returned on the initial request.
-
<div id="app" data-page="{{page|escape}}"></div>
--
the app will be mounted on the
app
root element. This means that a component is created and inserted into the DOM. -
The data-page attribute includes the data from the backend. Recall (from the Deep Dive) how the render function builds this JSON dict and either injects it into the template.
-
Creating an inertia view
from inertia import lazy, inertia
from .models import Data
@inertia('pages/base/page')
def data_view(request):
return {
'data': Data.objects.all()
}
-
Instead of returning html, this view will return a dictionary.
-
@inertia('pages/base/page')
- this is the path of the component, without the extension. This path is relative to where the entry point. -
For normal requests, the middleware will return the layout template, so that html can be rendered and inject the
data
context into the page.
Setup the frontend
We need to set up the ViteJS config and the entry point or InertiaJS app on the frontend.
ViteJS Config
Here is an example config file for vite.js. For a detailed tutorial on Django and Vite, you can visit this post .
const { resolve } = require('path');
import react from '@vitejs/plugin-react'
module.exports = {
plugins: [react()],
root: resolve('./assets'),
base: '/static/',
server: {
host: 'localhost',
port: 5173, <-- default server for vite
open: false,
watch: {
usePolling: true,
disableGlobbing: false,
},
},
resolve: {
extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'],
},
build: {
outDir: resolve('./static/dist'),
assetsDir: '',
manifest: true,
emptyOutDir: true,
target: 'es2015',
rollupOptions: {
input: {
main: resolve('./frontend/src/app.tsx'), <- entry point
}
},
},
};
InertiaJS Root Component
Run
pnpm i @inertiajs/react
to install inertia for react.
import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'
createInertiaApp({
resolve: name => {
// `name` will be the path of the component defined in
const pages = import.meta.glob('./pages/**/*.jsx', { eager: true })
return pages[`./pages/${name}.jsx`]
},
setup({ el, App, props }) {
createRoot(el).render(<App {...props} />)
},
})
-
resolve - this is where you import the page. The
name
is supplied by the backend view. For example -
the name will be path of the component , relative to
app.tsx
.
Add the react component
Finally we can add a react component.
function MyButton() {
return (
<button>
I'm a button
</button>
);
}
export default function Page() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
-
this page is imported in the resolve section of app.tsx.
Conclusion
In this guide, we explored building applications Django, React, and InertiaJS. Weβve looked at how these can work together to simplify the process of building modern dynamic web applications with Django and React.
A quick recap of what we covered:
-
InertiaJS : We introduced InertiaJS, a client side router and protocol that allows us to create single-page applications using classic server-side routing.
-
How InertiaJS Works : We explained the working mechanism of InertiaJS and how it bridges the gap between server-side and client-side frameworks.
-
-
Application Structure : We discussed how to structure your application when using these technologies together.
-
Django Middleware and Inertia Decorators : We looked into the Django middleware and Inertia decorators, crucial components in this setup.
-
Building Your Own App : We walked through the steps of building your own application using Django, React, and InertiaJS.
-
Installation : We covered the installation process for all necessary tools and libraries.
-
Inertia Templates and Creating Views : We explained how to create Inertia templates and views in Django.
-
-
Frontend Setup : We outlined how to set up the frontend using Vite, InertiaJS root, and React.
By leveraging the power of Django, React, and InertiaJS, you can streamline your development process and build efficient, modern web applications. Give it a try in your next project and let me know your thoughts on Twitter/X. @advantchHQ.
Cheers,
Themba.