Opinionated Django frontend scaffolder using ViteJS
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_frontend_kit-0.7.1.tar.gz.
File metadata
- Download URL: django_frontend_kit-0.7.1.tar.gz
- Upload date:
- Size: 14.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4af1d9ee5198d96b9436d242f5ed4be89696320c0aadcaeea38a16729d29e175
|
|
| MD5 |
df5ede305caae23a90cfa42b9474c71a
|
|
| BLAKE2b-256 |
5c351d6e79b6047c0bdfc45562be3021bd0015086a5e2c1771bfeec0e861bf28
|
File details
Details for the file django_frontend_kit-0.7.1-py3-none-any.whl.
File metadata
- Download URL: django_frontend_kit-0.7.1-py3-none-any.whl
- Upload date:
- Size: 16.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c44e036260afa7d1c0d796c8a34d200a05f8dbe0bce4261b5a365cc5863ee42
|
|
| MD5 |
c9918287ce85d2b30a182c03c7df3d43
|
|
| BLAKE2b-256 |
406b4f20d18f8c64916435b0296956d009257696c3aa710fead1cb28c9f16907
|