- Published at
Integrating a Vite Frontend with Django
A guide to integrating a Vite frontend with a Django backend, focusing on static assets and automating HTML generation.
- Authors
-
-
- Name
- James Lau
- Indie App Developer at Self-employed
-
Table of Contents
: A Step-by-Step Guide
This post details how to integrate a Vite-built frontend with a Django backend, focusing on serving static assets correctly and automating the generation of the main HTML file.
Project Structure and Assumptions
- Your Vite build output directory is
dist, located inside Django’sstaticfolder (static/dist). This means the static URL root for your build is/static/dist. - Your Django project’s root URL for the vendor CMS is
/vendor.
1. Base Template Setup (templates/vendor/base.html)
Create a base HTML template within your Django app (e.g., djangoapp/templates/vendor/base.html). This template will serve as the foundation for your frontend. It includes a div with id="root" where your Vite app will be mounted, and crucially, the {% render_vite_bundle %} template tag.
{% load render_vite_bundle %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Myapp CMS</title>
</head>
<body>
<div id="root"></div>
{% render_vite_bundle %}
</body>
</html>
Important: The render_vite_bundle tag is key to injecting the necessary <script> and <link> tags for your Vite assets in production. You’ll need to configure settings.py with the appropriate Vite app directory. (See below the render_vite_bundle.py code for explanation)
2. Handling Static Files and manifest.json
Vite generates a manifest.json file in the dist/.vite directory. This file maps your source files to their corresponding bundled filenames. A crucial step is ensuring that static file paths within your JavaScript code are correctly resolved in the production environment.
Problem: Original static file URLs might look like URL/assets/transactions/pending.svg.
Solution: These URLs need to be transformed to URL/static/dist/assets/transactions/pending.svg. This is primarily handled within the render_vite_bundle.py template tag (see below).
3. render_vite_bundle.py Template Tag
Create a custom template tag to render the Vite bundle. Place this file in your Django app’s templatetags directory (e.g., home/templatetags/render_vite_bundle.py).
# This template tag is needed for production
# Add it to one of your django apps (/appdir/templatetags/render_vite_bundle.py, for example)
import os
import json
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def render_vite_bundle():
"""
Template tag to render a vite bundle.
Supposed to only be used in production.
For development, see other files.
"""
try:
fd = open(f"{settings.VITE_APP_DIR}/dist/.vite/manifest.json", "r")
manifest = json.load(fd)
except:
raise Exception(
f"Vite manifest file not found or invalid. Maybe your {settings.VITE_APP_DIR}/dist/.vite/manifest.json file is empty?"
)
imports_files = [
'<script type="module" src="' + "{% static 'dist/" + manifest[file]["file"] + "' |safe %}" + '"></script>'
for file in manifest["index.html"]["dynamicImports"]
]
exclude = r'\\'
import_strings = ""
for f in imports_files:
f = f.replace(exclude, '')
import_strings += f + '\n'
main_js_file = manifest['index.html']['file']
main_css_file = manifest['index.html']['css'][0]
if 'static/dist/assets' not in main_js_file:
main_js_file = main_js_file.replace('assets', '/dist/assets')
if 'static/dist/assets' not in main_css_file:
main_css_file = main_css_file.replace('assets', '/dist/assets')
css_href = "{% static '" + main_css_file + "' %}"
js_href = "{% static '" + main_js_file + "' %}"
with open("static" + main_js_file, 'r+') as f:
content = f.read()
if 'static/dist/assets' not in content:
content = content.replace('assets', 'static/dist/assets')
f.seek(0) # Move the file pointer to the beginning
f.write(content)
f.truncate()
return mark_safe(
"{% load static %}" + f"""<script type="module" src="{js_href}"></script>
<link rel="stylesheet" type="text/css" href="{css_href}" />
{import_strings}"""
)
Explanation:
- The tag reads the
manifest.jsonfile to determine the correct filenames for your JavaScript and CSS bundles. settings.VITE_APP_DIRshould point to the root directory of your vite app, where thedistfolder resides. For example, if your vite app is in a folder namedfrontend, then you would setVITE_APP_DIR = os.path.join(BASE_DIR, 'frontend')in yoursettings.py.- It constructs the appropriate
<script>and<link>tags using Django’s{% static %}template tag to correctly reference the static files. - It modifies the content of the main js file by replacing
assetswithstatic/dist/assetsto ensure correct static file resolution. - The
mark_safefunction is crucial; it tells Django that the returned HTML is safe to render without further escaping.
4. Automating index.html Generation
To streamline the process, create a custom Django management command to generate the index.html file from your base template. This ensures that the latest Vite assets are always included.
Create a file named generate_vendor_cms.py inside your Django app’s management/commands directory (e.g., home/management/commands/generate_vendor_cms.py).
from django.core.management.base import BaseCommand
from django.template.loader import get_template
from django.template import TemplateDoesNotExist
class Command(BaseCommand):
help = "Generate Vendor CMS"
def handle(self, *args, **kwargs):
try:
base_template = get_template('vendor/base.html')
index_template = base_template.render()
with open('templates/vendor/index.html', 'w') as file:
file.write(index_template)
self.stdout.write(self.style.SUCCESS('index.html generated successfully'))
except TemplateDoesNotExist:
self.stdout.write(self.style.ERROR('base.html template does not exist'))
To run this command every time the server starts, add the following lines to your urls.py file:
from django.core.management import execute_from_command_line
execute_from_command_line(["manage.py", "generate_vendor_cms"])
Alternatively, you can manually run the command using:
python manage.py generate_vendor_cms
5. URL Configuration
Finally, set up the URL endpoint for your vendor CMS.
from django.shortcuts import render
# Example view (myapp/views.py)
from ratelimit.decorators import ratelimit
from ratelimit import keyfunc
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
@ratelimit(key=get_client_ip, rate="60/h", block=True)
def vendor(request):
return render(
request,
"vendor/index.html"
)
# Example URL configuration (myapp/urls.py or project urls.py)
from myapp import views
from django.urls import include, re_path
re_path(r"^vendor.*?$", views.vendor, name="vendor_cms"),
This setup serves the generated index.html file when a user navigates to a URL matching the pattern /vendor. The @ratelimit decorator is optional but recommended for security.
Summary
Integrating Vite with Django involves correctly serving static assets, using manifest.json for production builds, and automating index.html generation. This approach ensures your frontend and backend work seamlessly together.