# How to setup Django with React using InertiaJS

The official Django Inertia adapter was released in December 2022 but there was 0 front-end documentation and only the Django part of documentation - even now (as of 2nd February 2025) it says "Django specific frontend docs coming soon." with references to 2 other repos from where we need to pull our hair to try to get it to work. I finally landed on [Mujahid Anuar's repo](https://github.com/mujahidfa/inertia-django-vite-vue-minimal) which was for Vue but managed to port it to React with bits and pieces from StackOverflow, [Claude.ai](http://Claude.ai) and online documentation.

This is not a tutorial on Django or React - this article shows how to bind React in Django using Inertia instead of using API endpoints using DRF. So I am cutting short adding models in Django etc to delve directly into the front-end usage sending hardcoded props to React code.

```bash
cd workspace/django
mkdir inertia-django-vite-react-minimal
cd inertia-django-vite-react-minimal
python3 -m venv venv
source venv/bin/activate
pip install django==5.1.5
django-admin startproject inertia_django_vite_react_minimal .
pip install django-vite==3.0.6 inertia-django==1.1.0 whitenoise==6.8.2
python manage.py startapp app
```

Install Node if you haven't already have - minimum version required is version 22.0.0

```bash
touch package.json
code .
```

Add this to `package.json` :

```json
{
	"scripts": {
		"dev": "vite",
		"build": "vite build"
	},
	"devDependencies": {
		"vite": "^6.0.11"
	},
	"dependencies": {
		"@inertiajs/progress": "^0.2.7",
		"@inertiajs/react": "^2.0.3",
		"@types/node": "^22.10.10",
		"@vitejs/plugin-react": "^4.3.4",
		"react": "^19.0.0",
		"react-dom": "^19.0.0"
	}
}
```

```bash
npm install
```

If you get something like this because you already have an older NodeJS installed, either re-install NodeJS version 22.0 or later or use [nvm](https://github.com/nvm-sh/nvm).

```bash
npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE   package: 'vite@6.0.11',
npm warn EBADENGINE   required: { node: '^18.0.0 || ^20.0.0 || >=22.0.0' },
npm warn EBADENGINE   current: { node: 'v21.7.3', npm: '10.9.0' }
npm warn EBADENGINE }
npm warn deprecated lodash.isequal@4.5.0: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
```

I have nvm installed so I had to do `nvm install 22`

```bash
touch vite.config.ts
```

Add this to `vite.config.ts`

```javascript
import { defineConfig } from "vite";
import { resolve } from "path";
import react from "@vitejs/plugin-react";

export default defineConfig({
  root: resolve("./app/static/src"),
  base: "/static/",
  plugins: [react()],
  build: {
    outDir: resolve("./app/static/dist"),
    assetsDir: "",
    manifest: "manifest.json",
    emptyOutDir: true,
    rollupOptions: {
      // Overwrite default .html entry to main.tsx in the static directory
      input: resolve("./app/static/src/main.tsx"),
    },
  },
});
```

```bash
python manage.py migrate
python manage.py runserver
# Open a new tab in the terminal in the same project directory and run
npm run dev
```

In `inertia_django_vite_react_minimal/`[`urls.py`](http://urls.py) you should have URLs of `app`

```python
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path("", include("app.urls")), # Include app's URLs 
]
```

Create a file called [urls.py](http://urls.py) in the `app` folder.

In app/[urls.py](http://urls.py) let's create the `home` and `about` URLs:

```python
from django.urls import path

from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("about", views.about, name="about"),
]
```

And in the [views.py](http://views.py) :

```python
from django.http import HttpRequest
from django.shortcuts import render
from inertia import render as inertia_render # So that we can keep using django's default render() for non Inertia/React pages
from time import sleep

def index(request):
    return inertia_render(request, "Index", props={"name": "World"})


def about(request):
    sleep(2.5) # This is to show the loading progress indicator on the front-end using the @inertiajs/progress package
    return inertia_render(request, "About", props={"pageName": "About"})
```

Create a folder called `templates` in the `app` folder.

In the `templates` folder create file called `index.html` paste this content in it :

```html
{% load django_vite %}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="csrf-token" content="{{ csrf_token }}">

    {% if debug %}
    <script type="module">
    import RefreshRuntime from 'http://localhost:5173/static/@react-refresh'
    RefreshRuntime.injectIntoGlobalHook(window)
    window.$RefreshReg$ = () => {}
    window.$RefreshSig$ = () => (type) => type
    window.__vite_plugin_react_preamble_installed__ = true
    </script>
    {% endif %}

  {% vite_hmr_client %}
  {% vite_asset 'main.tsx' %}

  <title>Inertia + Django + Vite + React minimal</title>
</head>

<body>
  {% block inertia %}{% endblock %}
</body>

</html>
```

The [`http://localhost:5173/static/@react-refresh`](http://localhost:5173/static/@react-refresh) is for hot reload in development mode - when `DEBUG` is true is in [settings.py](http://settings.py) - when you want the front-end to reload automatically on changes in the React files. Reference: [https://stackoverflow.com/a/77971927/126833](https://stackoverflow.com/a/77971927/126833)

Create a folder called `static` in the `app` folder and create a sub-folder called `src` In `src` create a file called `main.tsx` and have this in it :

```typescript
import "vite/modulepreload-polyfill";
import { createRoot } from "react-dom/client";
import { createInertiaApp } from "@inertiajs/react";
import { InertiaProgress } from '@inertiajs/progress';
import axios from 'axios';
import { Page } from "@inertiajs/core";
import React from 'react';  // Added this import
document.addEventListener('DOMContentLoaded', () => {

    const csrfToken = document.querySelector('meta[name=csrf-token]').content;
    axios.defaults.headers.common['X-CSRF-Token'] = csrfToken;

    InertiaProgress.init();

    createInertiaApp({
        resolve: (name) => import(`./pages/${name}.tsx`),
        setup({ el, App, props }: {
            el: HTMLElement,
            App: React.ComponentType<{ page: Page }>,
            props: any
        }) {
            const root = createRoot(el);
            root.render(<App {...props} />);
        },
    });

});
```

Create 2 files in the `app/static/src/pages` folder - one named `Index.tsx` and the other named `About.tsx` - these are pure React code in TypeScript.

**Index.tsx**

```typescript
import React from 'react';
import { Link, usePage } from '@inertiajs/react';

export default function Index() {

  const { app_name } = usePage().props;

  return (
      <div>
          <h1>{app_name}</h1>
          <h1>Welcome to the Home Page</h1>
          <Link href="/about">About</Link>
      </div>
  );
}
```

**About.tsx**

```typescript
import React from 'react';
import {Link, usePage} from '@inertiajs/react';

export default function About() {

  const { app_name } = usePage().props;

  return (
      <div>
          <h1>{app_name}</h1>
          <h1>About Page</h1>
          <Link href="/">Back to Home</Link>
      </div>
  );
}
```

Create a folder in app called middleware and in middleware create a file called [mInertia.py](http://mInertia.py) (I purposely didn't want to name it [inertia.py](http://inertia.py) incase of conflicts due to the existing file of the same name in packages)

app/middleware/[mInertia.py](http://mInertia.py)

```python
from inertia import share
from django.conf import settings
from django.contrib.auth import get_user_model


class InertiaShareMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # Share data that should be available to all components
        share(
            request,
            app_name=settings.APP_NAME, # This is set in settings.py
            user=lambda: self._get_user_data(request),
            user_count=lambda: get_user_model().objects.count(),
            # Add more shared data as needed
        )

        return self.get_response(request)

    def _get_user_data(self, request):
        """Format user data for frontend components"""
        if request.user.is_authenticated:
            return {
                'id': request.user.id,
                'email': request.user.email,
                'name': request.user.get_full_name(),
                'is_staff': request.user.is_staff,
            }
        return None
```

Create a file called context\_[processors.py](http://processors.py) in inertia\_django\_vite\_react\_minimal - this is to send the `DEBUG` value with the keyname `debug` to the front-end template to use as `{% if debug %}` ... `{% endif %}`

inertia\_django\_vite\_react\_minimal/context\_[processors.py](http://processors.py) :

```python
from django.conf import settings

def debug_mode(request):
    return {'debug': settings.DEBUG}
```

There a number of edits in [settings.py](http://settings.py) (inertia\_django\_vite\_react\_minimal/[settings.py](http://settings.py)) :

```bash
import os
import re
.
.
.
DEBUG = True
APP_NAME = "Django with Inertia using React" # This was added
ALLOWED_HOSTS = ["*"]

# Application definition

INSTALLED_APPS = [
    'whitenoise.runserver_nostatic', # This was added
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_vite', # This was added
    'inertia', # This was added
    'app', # This was added
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'inertia.middleware.InertiaMiddleware', # This was added
    'app.middleware.mInertia.InertiaShareMiddleware', # This was added
]
.
.
.
# django-vite settings
# https://github.com/MrBin99/django-vite
DJANGO_VITE_DEV_MODE = DEBUG  # DEBUG - follow Django's dev mode

# Where ViteJS assets are built.
DJANGO_VITE_ASSETS_PATH = BASE_DIR / "app" / "static" / "dist"

# Vite 3 defaults to 5173. Default for django-vite is 3000, which is the default for Vite 2.
DJANGO_VITE_DEV_SERVER_PORT = 5173

# Output directory for collectstatic to put all your static files into.
STATIC_ROOT = BASE_DIR / "staticfiles"

DJANGO_VITE_MANIFEST_PATH = os.path.join(STATIC_ROOT, "manifest.json")

# Include DJANGO_VITE_ASSETS_PATH into STATICFILES_DIRS to be copied inside
# when run command python manage.py collectstatic
STATICFILES_DIRS = [DJANGO_VITE_ASSETS_PATH]

# Inertia settings
INERTIA_LAYOUT = BASE_DIR / "app" / "templates/index.html"

# Vite generates files with 8 hash digits
# http://whitenoise.evans.io/en/stable/django.html#WHITENOISE_IMMUTABLE_FILE_TEST
def immutable_file_test(path, url):
    # Match filename with 12 hex digits before the extension
    # e.g. app.db8f2edc0c8a.js
    return re.match(r"^.+\.[0-9a-f]{8,12}\..+$", url)
```

Now when you goto [http://localhost:8000/](http://localhost:8000/) (I’m assuming that `python manage.py runserver` and `npm run dev` are already running) you should see this which is served by app/static/src/Pages/Index.tsx :

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1738521027839/9b73dad1-1905-4009-a0f3-0d8e48d62809.png align="center")

And on clicking About, you should be able to see the About Page page loading in 2 and half seconds with a blue progress indicator showing at the top of the page which is processed by `@inertiajs/progress` which we added in `package.json`.

GitHub repo : [https://github.com/anjanesh/inertia-django-vite-react-minimal/](https://github.com/anjanesh/inertia-django-vite-react-minimal/)
