Django Frontend Kit
An opinionated way to structure and ship modern frontend assets in Django using Vite. It provides:
- A
manage.py scaffoldcommand to bootstrap afrontend/workspace. - Template tags for injecting Vite assets (dev server in
DEBUG, manifest in production). - A lightweight “Page” abstraction that keeps templates, JS/CSS entrypoints, and views aligned.
Why this exists
Most Django + Vite integrations solve “include the scripts”. Django Frontend Kit also nudges you into a consistent project layout:
frontend/layouts/...for shared shells (base template + base JS/CSS).frontend/pages/...for page-level templates and entrypoints.- Optional “custom entries” for React/Vue/Alpine widgets without adopting a full SPA.
Features
- Vite dev server in development (
DEBUG=True) with automatic@vite/clientinjection. - Production asset resolution via Vite
manifest.json+ Djangostatic()URLs. - Modulepreload + stylesheet tags generated from the manifest (better performance by default).
- Scaffolding for a working
frontend/structure and avite.config.js.
Requirements
- Python
>= 3.9 - Django
>= 4.2(including 6.0) - Node.js + npm/pnpm/yarn (for Vite)
Installation
pip install django-frontend-kit
Alternative installers:
uv add django-frontend-kit
poetry add django-frontend-kit
Quickstart
1) Add the app
settings.py:
INSTALLED_APPS = [
# ...
"frontend_kit",
]
2) Scaffold the frontend workspace
From the same directory as manage.py:
python manage.py scaffold
This creates:
frontend/(templates + entrypoints + Python modules)vite.config.js(configured for this kit)
3) Install JS dependencies
npm init -y
npm install --save-dev vite @iamwaseem99/vite-plugin-django-frontend-kit
Add scripts to package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
4) Configure Django settings
At minimum:
DJFK_FRONTEND_DIR = BASE_DIR / "frontend"
VITE_OUTPUT_DIR = BASE_DIR / "dist"
VITE_DEV_SERVER_URL = "http://localhost:5173/"
DJFK_DEV_ENV = True
TEMPLATES = [
{
# ...
"DIRS": [DJFK_FRONTEND_DIR],
}
]
STATICFILES_DIRS = [VITE_OUTPUT_DIR]
Notes:
DJFK_FRONTEND_DIRmust exist on disk (the scaffold command creates it).VITE_OUTPUT_DIRmust match your Vitebuild.outDir(seevite.config.js).DJFK_DEV_ENVcontrols dev behavior. WhenTrue, assets are served from the Vite dev server; whenFalse, assets are resolved from the manifest.
5) Run in development
In two terminals:
npm run dev
python manage.py runserver
How it works
- When
DJFK_DEV_ENV=True, asset tags point to the Vite dev server and HMR works as usual. - When
DJFK_DEV_ENV=False, Django Frontend Kit readsVITE_OUTPUT_DIR/.vite/manifest.jsonand emits:<link rel="modulepreload" ...>for imported chunks<link rel="stylesheet" ...>for CSS<script type="module" ...></script>for the entry module
CSP nonce behavior (dev only)
If DJFK_DEV_ENV=True and request.csp_nonce is available in the template context, Django Frontend Kit (Django 6+ compatible) adds a nonce attribute to:
<script type="module"><link rel="stylesheet">
No nonce is added when DJFK_DEV_ENV=False (production/manifest mode), and modulepreload tags never receive a nonce.
Project layout
After scaffolding, you’ll have a structure like:
frontend/
layouts/
base/
__init__.py
index.html
entry.head.ts # optional, loaded in <head>
entry.ts # loaded at end of <body>
main.css
pages/
home/
__init__.py
index.html
entry.ts
Important: frontend/ is a Python package. Keep __init__.py files so Django can import your Page classes.
Creating pages and layouts
Layouts (frontend/layouts/...)
Layouts are just Page subclasses. By convention they define shared HTML + shared entrypoints.
frontend/layouts/base/__init__.py:
from frontend_kit.page import Page
class BaseLayout(Page): ...
frontend/layouts/base/index.html (scaffolded):
{% load fk_tags %}
<!doctype html>
<html>
<head>
{% fk_preloads %}
{% fk_stylesheets %}
{% fk_head_scripts %}
</head>
<body>
{% block body %}{% endblock %}
{% fk_body_scripts %}
</body>
</html>
Pages (frontend/pages/...)
frontend/pages/home/__init__.py:
from frontend.layouts.base import BaseLayout
class HomePage(BaseLayout):
def __init__(self, name: str) -> None:
super().__init__()
self.name = name
frontend/pages/home/index.html:
{% extends "layouts/base/index.html" %}
{% block body %}
<h1>Hello {{ page.name }}</h1>
{% endblock %}
And in a view:
from django.http import HttpRequest, HttpResponse
from django.views import View
from frontend.pages.home import HomePage
class HomeView(View):
def get(self, request: HttpRequest) -> HttpResponse:
return HomePage(name="User").as_response(request=request)
Template tags
Load tags with {% load fk_tags %}. All tags expect a page object in template context (the Page base class provides it).
{% fk_preloads %}: modulepreload links (production only){% fk_stylesheets %}: CSS links{% fk_head_scripts %}:<script type="module">tags intended for<head>{% fk_body_scripts %}:<script type="module">tags intended for end of<body>{% fk_custom_entry "name" %}: loadsname.entry.ts/name.entry.jsrelative to the current page directory
Guides
Tailwind CSS
The included example project uses Tailwind v4 via the official Vite plugin.
- Install dependencies:
npm install --save-dev tailwindcss @tailwindcss/vite
- Add Tailwind to
vite.config.js(beforeDjangoFrontendKit()):import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [tailwindcss(), DjangoFrontendKit()], });
- Add a Tailwind config (example
tailwind.config.cjs):export default { content: ["./frontend/**/*.{html,js,ts,jsx,tsx,vue,py}"], theme: { extend: {} }, plugins: [], };
- Import Tailwind in your base CSS (scaffolded
frontend/layouts/base/main.css):@import "tailwindcss";
- Ensure the CSS is imported by a Vite entrypoint that’s loaded on your pages (scaffolded
frontend/layouts/base/entry.head.tsis a good place):import "./main.css";
React (widgets / islands)
- Install dependencies:
npm install react react-dom npm install --save-dev @vitejs/plugin-react
- Enable the React plugin in
vite.config.js:import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react(), DjangoFrontendKit()], });
- Create a custom entry file in a page directory (must end with
.entry.jsor.entry.tsso it’s included in the manifest):frontend/pages/home/react.entry.js:import React from "react"; import { createRoot } from "react-dom/client"; function App() { return <div>Hello from React</div>; } const el = document.getElementById("react-app"); if (el) createRoot(el).render(<App />);
- Add a mount point + include the entry in your template:
<div id="react-app"></div> {% fk_custom_entry "react" %}
Vue (widgets / islands)
- Install dependencies:
npm install vue npm install --save-dev @vitejs/plugin-vue
- Enable the Vue plugin in
vite.config.js:import vue from "@vitejs/plugin-vue"; export default defineConfig({ plugins: [vue(), DjangoFrontendKit()], });
- Create a Vue component + an entry file (entry must end with
.entry.jsor.entry.ts):frontend/pages/home/HelloVue.vue:<template> <div>Hello from Vue</div> </template>
frontend/pages/home/vue.entry.ts:import { createApp } from "vue"; import HelloVue from "./HelloVue.vue"; const el = document.getElementById("vue-app"); if (el) createApp(HelloVue).mount(el);
- Add a mount point + include the entry in your template:
<div id="vue-app"></div> {% fk_custom_entry "vue" %}
Notes about entrypoints
The Vite plugin bundled with this project only treats these files as build inputs:
entry.js/entry.tsentry.head.js/entry.head.ts*.entry.js/*.entry.ts
So keep “entry” files as .js/.ts (they can import .jsx, .tsx, .vue, CSS, etc.).
Production checklist
- Build assets:
npm run build
- Ensure
DEBUG=False. - Ensure Django can serve static assets in production (e.g. WhiteNoise, CDN, or your platform’s static hosting).
- Collect static files:
python manage.py collectstatic
If you use WhiteNoise, consider CompressedManifestStaticFilesStorage so hashed assets are served efficiently.
Troubleshooting
DJFK_FRONTEND_DIR is not set / does not exist: set it insettings.pyand runpython manage.py scaffold.VITE_OUTPUT_DIR is not set / does not exist: set it insettings.pyand ensure it matches your Vite build output directory.manifest.jsonnot found: runnpm run buildand verifyVITE_OUTPUT_DIR/.vite/manifest.jsonexists....was not included in Vite manifest: ensure the file is part of Vite build inputs (entrypoints must be reachable/declared).
Example project
This repo includes a working example in example/ showing:
- A base layout (
frontend/layouts/base/) withentry.head.tsandentry.ts - A page (
frontend/pages/home/) that renders Django template HTML and mounts optional JS widgets
Versioning & stability
This project is currently in Beta. Expect occasional breaking changes until 1.0.0.
Contributing
- Run linting:
ruff check . - Run type checking:
mypy .
PRs are welcome. If you’re proposing a behavior change, include a short rationale and an example project diff.
License
MIT — see LICENSE.
Metadata
Release files for django-frontend-kit 0.7.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_frontend_kit-0.7.1.tar.gz | 14.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_frontend_kit-0.7.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.4 kB
Release files / django_frontend_kit-0.7.1.tar.gz
| Download URL | django_frontend_kit-0.7.1.tar.gz |
|---|---|
| Size | 14.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4af1d9ee5198d96b9436d242f5ed4be89696320c0aadcaeea38a16729d29e175
|
|
BLAKE2b-256 checksum How to use checksums |
5c351d6e79b6047c0bdfc45562be3021bd0015086a5e2c1771bfeec0e861bf28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.2
|
Release files / django_frontend_kit-0.7.1-py3-none-any.whl
| Download URL | django_frontend_kit-0.7.1-py3-none-any.whl |
|---|---|
| Size | 16.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0c44e036260afa7d1c0d796c8a34d200a05f8dbe0bce4261b5a365cc5863ee42
|
|
BLAKE2b-256 checksum How to use checksums |
406b4f20d18f8c64916435b0296956d009257696c3aa710fead1cb28c9f16907
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.2
|