iTranslated by AI
A New Choice for Web Development: Building with Django, Inertia, Vite, and React
Introduction
Are you tired of microservices?
- It makes no sense to set up Django or Laravel for the backend while also setting up a separate Next.js (Node.js) instance for the frontend.
- I just want to build a modern web service, so why do I have to expose an API?
- [Django React App Construction] [Search]
- I just want to use Django for the backend and React for the frontend. Do I really have to use such a complex architecture just for that...?
With the modular monolith architecture I will introduce this time, you can build a web service quickly like this:
import django
import inertia
@intertia('ShowVersion')
def index(request):
return {
'version': django.get_version()
}
type Props = {
version: string;
}
export default Page({ version }: Props){
return (<div>
Django: {verison}
</div>);
}
Values from the backend are passed to the frontend without exposing a REST API[1].
If you felt a spark of interest in this approach, this article is for you!
In this article, I will introduce a method to build a web service with a modular monolith architecture using Django + Inertia.js + Vite + React.
Why?
In recent years, it has become common to adopt a production architecture where React and an arbitrary backend environment are launched as microservices, with communication via RESTful APIs or GraphQL. While these architectures are loosely coupled and allow for easier replacement of individual components, a notable drawback is the "increase in development man-hours."
For example, let's say you launch React and Django as independent entities. Since the frontend is hosted via static hosting and requires interaction between the two, you implement a RESTful API in Django. At this point, you might worry about:
- I've opened up an API, but I don't want end-users tampering with it... but issuing CSRF tokens is difficult.
- I want to change the base HTML dynamically... should I switch to Next.js? Wait, then I need to set up Node.js too.
- ...Thinking about it calmly, it feels like over-engineering to open up an API when I'm not providing functionality to end-users.
While thinking about these things, don't you ever feel like: "What am I doing... I just want to build a simple web app." In recent years, loosely coupled microservices have been highly regarded for large-scale projects, but for individuals building small-scale applications, might this not be the optimal solution? Is there any possibility that you will replace either part of the project you are currently developing in the future?
In contrast, the modular monolith I am introducing here requires only this:
import django
import inertia
@intertia('ShowVersion')
def index(request):
return {
'version': django.get_version()
}
type Props = {
version: string;
}
export default Page({ version }: Props){
return (<div>
Django: {verison}
</div>);
}
What do you think? Simply by separating the frontend and backend, you don't have to worry about the issues mentioned above, and best of all, you don't need to open an API for integration[1:1]!
Cases where this seems useful
It is particularly suitable for cases where the likelihood of future replacement is low and where development man-hours are critical (tight deadlines/schedules), such as:
- Personal projects
- Hackathons
- Assignments/projects at educational institutions
Personally, I feel this is a great solution for building systems at educational institutions. The reason I am adopting this architecture is because Django was specified for my school assignment.
Regarding modular monoliths, the following article goes into detail, so please refer to it as well!
Conclusion
Everything done below is contained in the repository below.
If setting it up is a hassle, please use this:
Building the project[2][3]
Currently, Inertia.js supports various backends and frontends, but the setup is a bit of a challenge 😅 It would be nice if there were an interactive setup like Vite has for this too...
The author's environment
Please use this for reference.
| Python | 3.9.6 |
| pip | 23.2.1 |
| Django | 4.2.13 |
Setting up Django
Create a directory and cd into it.
mkdir djangoReactApp
cd djangoReactApp
If using venv
python3 -m venv myenv
source ./.venv/bin/activate
Install Django and set it up.
pip install Django
django-admin startproject djangoReactApp .
Let's launch it.
python manage.py runserver 8080
If you have opened it on port 8080 as shown above, the link is http://localhost:8080.

If it starts like this, you're good to go. Go back to the terminal and stop it with ^(Ctrl) + C.
Setting up React + Vite
❯ npm create vite@latest frontend
✔ Select a framework: › React
✔ Select a variant: › TypeScript + SWC // SWC is a fast compiler from Vercel, optional
Scaffolding project in /Users/bonychops/PycharmProjects/djangoReactApp/frontend...
Done. Now run:
cd frontend
npm install
npm run dev
Run the following commands as instructed.
cd frontend
npm install
npm run dev
By default, it should launch at http://localhost:5173/.

It has started! 🎉
Once confirmed, type ^ + C to return to the root directory.
cd ..
Setting up the adapter and integration
Django side
Next, build the bridge between Django and React.
pip install inertia-django django-vite
Edit djangoReactApp/djangoReactApp/settings.py as follows.
# ...
INSTALLED_APPS = [
# ...
'django.contrib.staticfiles', # Add below the Django apps
+ "django_vite",
+ "inertia",
# ... Describe other installed apps here
]
MIDDLEWARE = [
# ...
'django.middleware.clickjacking.XFrameOptionsMiddleware', # Add below the Django Middleware
+ "inertia.middleware.InertiaMiddleware",
# ... Describe other installed middleware here
]
Next, create djangoReactApp/templates/base.html.
{% load django_vite %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!-- vite hmr -->
{% vite_hmr_client %}
{% vite_react_refresh %}
{% vite_asset 'src/main.tsx' %}
<title>djangoReactApp</title>
</head>
<body>
<!-- inertia -->
{% block inertia %}{% endblock %}
</body>
</html>
Specify the templates/base.html you just created in settings.py.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
+ 'DIRS': [BASE_DIR / 'templates'],
# ...
},
]
Next, add the following to the end of the file.
USE_I18N = True
USE_TZ = True
+# Static files (CSS, JavaScript, images)
+# https://docs.djangoproject.com/en/4.2/howto/static-files/
+
+STATIC_URL = 'static/'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+INERTIA_LAYOUT = 'base.html'
+
+# Required for Django form submissions
+CSRF_HEADER_NAME = 'HTTP_X_XSRF_TOKEN'
+CSRF_COOKIE_NAME = 'XSRF-TOKEN'
+
+# Where ViteJS assets are built
+DJANGO_VITE_ASSETS_PATH = BASE_DIR / 'frontend' / 'dist'
+
+# Whether to use HMR
+DJANGO_VITE_DEV_MODE = DEBUG
+
+# Vite server port settings
+DJANGO_VITE_DEV_SERVER_PORT = 3000
+
+# Static file folder name (after running python manage.py collectstatic)
+STATIC_ROOT = BASE_DIR / 'static'
+
+# Include DJANGO_VITE_ASSETS_PATH in STATICFILES_DIRS to ensure it is copied internally when running collectstatic
+STATICFILES_DIRS = [DJANGO_VITE_ASSETS_PATH]
Create views.py.
from inertia import inertia
@inertia('Index/index') # Select the React component to render
def index(request):
return {} # Empty Props
Finally, add the created view to urls.py.
from django.contrib import admin
from django.urls import path
+from djangoReactApp import views
urlpatterns = [
+ path('', views.index, name='home'),
path('admin/', admin.site.urls),
]
React side
Install @types/node as it is needed for vite.config.ts.
cd frontend
npm install -D @types/node
Edit vite.config.ts.
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react-swc'
+import { resolve } from 'path'
// https://vitejs.dev/config/
-export default defineConfig({
+export default defineConfig((env) => ({
plugins: [react()],
+ base: 'static/',
+ server: {
+ host: '127.0.0.1',
+ port: 3000,
+ open: false,
+ watch: {
+ usePolling: true,
+ disableGlobbing: false,
+ },
+ origin: env.mode === "development" ? 'http://127.0.0.1:3000' : "",
+ },
+ resolve: {
+ alias: {
+ '@': resolve(__dirname, './src')
+ }
+ },
+ build: {
+ outDir: resolve('./dist'),
+ manifest: "manifest.json",
+ assetsDir: "assets",
+ target: 'es2015',
+ rollupOptions: {
+ input: {
+ main: resolve('./src/main.tsx'),
+ },
+ output: {
+ entryFileNames: `assets/[name]/bundle.js`,
+ },
+ },
+ },
-})
+}))
Inertia uses <div id="app"> as the React root, so we adjust it.
-#root {
+#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
npm i -D @inertiajs/react
Replace the entire content of main.tsx as follows:
import 'vite/modulepreload-polyfill';
import {createRoot} from 'react-dom/client';
import {createInertiaApp} from '@inertiajs/react';
import './index.css'
import {StrictMode} from "react";
document.addEventListener('DOMContentLoaded', () => {
createInertiaApp({
resolve: (name) => {
const pages = import.meta.glob('./pages/**/*.tsx', {eager: true});
return pages[`./pages/${name}.tsx`];
},
setup({el, App, props}) {
createRoot(el).render(
<StrictMode>
<App {...props} />
</StrictMode>
);
}
}).then(() => {
});
});
mkdir -p src/pages/Index
mv src/App.tsx src/pages/Index/index.tsx
import { useState } from 'react'
-import reactLogo from './assets/react.svg'
+import reactLogo from '@/assets/react.svg'
import viteLogo from '/vite.svg'
-import './App.css'
+import '@/App.css'
Startup
Finally, let's start the app and verify it. Run the following command sets in separate terminals.
python manage.py runserver 8080
cd frontend
npm run dev
With this state, access http://localhost:8080.

If it displays without issues, the minimal setup is complete. 🎉 Good job!
Production Environment
To use it in a production environment, follow the steps below.
-DEBUG = True
+DEBUG = False
-ALLOWED_HOSTS = []
+ALLOWED_HOSTS = [
+ "localhost" # You need to configure the host to be public in production; here we use localhost
+]
Please note that you must run the following every time there is a change on the Frontend side:
cd frontend
npm run build
cd ../
python manage.py collectstatic
Start it up:
python manage.py runserver 8080 # By default it doesn't serve static files, add --insecure if necessary
Appendix
Below are some column-style elements. Feel free to check them out if interested.
Adding the Django Logo
+import django
from inertia import inertia
@inertia('Index/index') # Selecting the React component to render
def index(request):
- return {}
+ return {
+ "services": [
+ {
+ "version": django.get_version(),
+ "name": "Django",
+ "url": "https://www.djangoproject.com/",
+ "iconUrl": "https://github.com/django.png"
+ }
+ ]
+ }
import {useState} from 'react'
import reactLogo from '@/assets/react.svg'
import viteLogo from '/vite.svg'
import '@/App.css'
import {Link} from "@inertiajs/react";
+type Props = {
+ services: {
+ version: string;
+ name: string;
+ url: string;
+ iconUrl: string;
+ }[]
+}
-function App() {
+function App(props: Props) {
+ const {services} = props;
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo"/>
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo"/>
</a>
+ {services.map(v => (
+ <a href={v.url} target="_blank">
+ <img src={v.iconUrl} className="logo" alt={`${v.name} logo`}/>
+ </a>
+ ))}
</div>
- <h1>Vite + React</h1>
+ <h1>{["Vite", "React", ...services.map(v => v.name)].join(" + ")}</h1>

You can see that even the version is displayed.
Code splitting
Inertia naturally supports code splitting. In the current configuration, it fetches all components during the first request before rendering. To change this to only fetch the currently viewed page, simply modify the following:
document.addEventListener('DOMContentLoaded', () => {
createInertiaApp({
resolve: (name) => {
- const pages = import.meta.glob('./pages/**/*.tsx', {eager: true});
+ const pages = import.meta.glob('./pages/**/*.tsx', {eager: false});
- return pages[`./pages/${name}.tsx`];
+ return pages[`./pages/${name}.tsx`]();
},
However, Inertia does not strictly recommend code splitting. The logic seems to be: "Is one component of the web app you're building really that heavy? Fetching everything at once might be more efficient so that you don't have to send requests for every single page."
For reference, here is the bundle size when code splitting is enabled versus disabled:
| Code splitting: Disabled | Code splitting: Enabled |
|---|---|
![]() |
![]() |
The above is an example of when 2 pages (components) were created. How is it? Despite the main/bundle.js difference being only 200B, you can see that with code splitting enabled, the size actually increases slightly when considering the split files. It's probably better to consider this with the knowledge that even with code splitting enabled, the original bundle.js is about 200kB.
Well, I still enable code splitting, though. [4]
-
Strictly speaking, Inertia.js handles it for you in this case. Even so, it performs optimizations without requiring user effort, such as embedding into HTML during the initial fetch and using JSON responses via API for page transitions. For details, see The protocol - Inertia.js. ↩︎ ↩︎
-
Build Web Fullstack Apps with DIRT: Django, Inertia, React & Tailwind CSS aka D.I.R.T Stack - DEV Community ↩︎ ↩︎
-
It just doesn't feel right to fetch components for pages I'm not looking at... ↩︎


Discussion