Django and React - How to use InertiaJS in your Django and React Apps
This guide covers InertiaJS fundamentals and how to use it with Django and React. We cover middleware, Inertia decorators, and practical steps to build your own app. Enjoy,
Themba Mahlangu

Table of Contents
- What is InertiaJS
- How does InertiaJS this work?
- Inertia.js Protocol Summary
- A Deep dive - The Inertia decorator
- Building your own InertiaJS - React - Django app
- How to structure your application
- Setup the Django Backend
- Setup the frontend
- Conclusion
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.
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 on the backend 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
Feel free to skip this if you know how decorators work.
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 Decorator Pattern 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:
def 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” the say_hello function. The decorator function my_decorator takes a function as an argument, defines a new function wrapper that calls the original function with some additional behavior, and returns this new function.
@inertia('component/path/typically-no-extension')
def some_view(request):
return {'key':'some value'}
# here is a snippet of the decorator used above.
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 decoratorAt 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 works together.
The main components of this stack
-
Backend - Django
-
Frontend
- 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.txtSetup 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-viteto 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-djangoto 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 middlewareCustom 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.
If you have any questions, comments reach out twitter/X @AdvantchHQ.