Content: Blog

Tutorials, Technical articles

Seamlessly Integrating React with Django CMS: A Modern Approach

Vinit Kumar

June 17, 2025

Django CMS has long been the go-to choice for content management in Django applications, providing powerful editorial tools and flexible page structures. However, as web applications become increasingly interactive, developers often find themselves needing the dynamic capabilities of React within their CMS-managed pages.

In this comprehensive guide, we'll explore how to integrate React seamlessly in your Django CMS project using Vite as our build tool. We'll walk through a real-world implementation that demonstrates the "SPA-in-CMS" approach, where React applications with their local router live within Django CMS pages while maintaining all the benefits of both technologies.

Example Project: You can find a complete working example of this integration in the djangocms-react-proj repository, which demonstrates how React can be seamlessly integrated with Django CMS.

Why Integrate React with Django CMS?

Before diving into the implementation, let's understand the compelling reasons for this integration:

  • Editorial Power: Django CMS provides robust content management, versioning, and editorial workflows

  • Modern UI: React enables interactive, dynamic user interfaces with excellent developer experience

  • Best of Both Worlds: Content editors can manage pages while developers build rich interactive features

  • PerformanceModern tooling, such as Vite provides lightning-fast development builds and optimized production bundles

Architecture Overview

Our current approach follows the SPA-in-CMS pattern:

  1. Django CMS manages the overall page structure and routing

  2. React applications are embedded within CMS pages via dedicated mounting points

  3. Vite builds the React applications and generates asset manifests. It could very well be any other bundler like webpack as well.

  4. Django dynamically includes the correct CSS and JavaScript files

  5. React takes control of specific page sections while respecting the CMS layout using React Router. React router let's you control the component that would be rendered based on specific routes.

Implementation Deep Dive

Let's examine the key components of our implementation:

Using a CMS App hook, you can integrate any Django App to a CMS page. Here we register a Django app called Jobs, which will essentially serve as a wrapper of an React app. The template of the app will load the required JavaScript and CSS assets and will also contain a container to mount the react app.

Project Structure of the Django App

$ ~/projects/djangocmsorg/djangocms-react-proj/apps/jobs/ tree
.
├── __init__.py
├── __pycache__
├── admin.py
├── apps.py
├── cms_apps.py
├── migrations
│   ├── __init__.py
├── models.py
├── templates
│   └── jobs
│       └── job_list.html
├── tests.py
├── urls.py
└── views.py

6 directories, 10 files

React Application Structure

The react app could be one with a plugin like React router that can render the components based on the routes they are in.

.
├── build
│   ├── index.html
│   ├── manifest.json
│   └── static
├── index.html
├── package.json
├── postcss.config.js
├── src
│   ├── api
│   ├── common
│   ├── components
│   ├── context.tsx
│   ├── helpers
│   ├── hooks
│   ├── index.tsx
│   ├── pages
│   ├── root
│   ├── styles
│   ├── tests
│   └── utils.tsx
├── tailwind.config.js
├── vite.config.js
├── vitest.config.js
└── yarn.lock

13 directories, 12 files

Once the app is built, we need to use the files in the build folder for production deployment, whose file structure looks like this:

$ ~/projects/djangocmsorg/djangocms-react-proj/backend/static/js/jobs/ tree build
build
├── index.html
├── manifest.json
└── static
    ├── css
    │   ├── main.css
    │   └── main.css.map
    └── js
        ├── main.fe85f7b6.js
        └── main.fe85f7b6.js.map

4 directories, 6 files

Template Implementation

The template creates the mounting point for React and includes the necessary assets:

<!-- apps/jobs/templates/jobs/job_list.html -->
{% extends CMS_TEMPLATE %}
{% load cms_tags sekizai_tags static %}

{% block content %}
    <!-- React mounting point with data attributes for configuration -->
    <div id="root"
         class="jobs"
         data-url="{{api_url}}"
         data-baseUrl="{% url "jobs:jobs-list" %}">
    </div>
{% endblock content %}

<!-- CSS assets loaded in the head -->
{% addtoblock "css" %}
    {% for css_link in css_links %}
        <link href="{% static css_link %}" rel="stylesheet">
    {% endfor %}
{% endaddtoblock %}

<!-- JavaScript assets loaded before closing body tag -->
{% addtoblock "js" %}
    {% for js_link in js_links %}
        <script type="text/javascript" src="{% static js_link %}"></script>
    {% endfor %}
{% endaddtoblock %}

Asset Management

The foundation of our integration lies in the asset management utility that bridges Vite's build system with Django's static file serving. We will use this helper method to get the path for the built JS and CSS and return a static path that will be passed on to the django template via context.

# generic/utils.py

def get_css_and_js_link_from_vite_assets(project_type: str, uses_client: bool = False) -> tuple[list[str], list[str], list[str]]:
    """
    Retrieve CSS and JS links from Vite asset manifest.
    This function reads Vite's generated manifest.json file and extracts
    the paths to CSS, JS, and entry point files for a specific project.
    """
    # Construct the path to the manifest file
    if uses_client:
        manifest_path = Path(f"backend/static/js/{project_type}/build/manifest.json")
        base_path = f"js/{project_type}/build/client/"
    else:
        manifest_path = Path(f"backend/static/js/{project_type}/build/manifest.json")
        base_path = f"js/{project_type}/build/"

    # Parse the manifest and extract asset paths
    with manifest_path.open('r', encoding="utf-8") as manifest_file:
        asset_manifest_dict = json.load(manifest_file)

    css_links = []
    js_links = []
    main_js_links = []

    # Process each entry in the manifest
    for key, entry in asset_manifest_dict.items():
        if 'file' not in entry:
            continue

        # Extract CSS assets
        if 'assets' in entry:
            for asset in entry['assets']:
                if asset.endswith('.css'):
                    css_links.append(base_path + asset)

        # Extract JS files and identify entry points
        file_path = entry['file']
        if file_path.endswith('.js'):
            js_links.append(base_path + file_path)
            if entry.get('isEntry', False):
                main_js_links.append(base_path + file_path)

    return css_links, js_links, main_js_links

Key Features:

  • Manifest-Based: Reads Vite's generated manifest for accurate asset paths

  • Flexible Structure: Supports different project types and build configurations

  • Asset Classification: Separates CSS, JS, and entry point files for proper loading order

  • Future-Proof: Handles Vite's file hashing and optimization automatically

Django View Integration

The Django view acts as the bridge between the CMS and React application. It pulls in the JS and CSS static path and passes them to the template via template context.

# apps/jobs/views.py

class JobsView(TemplateView):
    template_name = "jobs/job_list.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        # Retrieve CSS and JS links from Vite asset manifest
        css_links, js_links, main_js_links = get_css_and_js_link_from_vite_assets("jobs")

        context["css_links"] = css_links
        context["js_links"] = js_links

        # Note: main_js_links are available but not used in this example
        return context

Why This Approach Works:

  • Dynamic Asset Loading: Assets are resolved at runtime based on the current build

  • Project Isolation: Each React app can have its own asset bundle

  • Context Flexibility: Additional data can be easily passed to the React application

Template Features:

  • CMS Integration: Extends the CMS template to maintain page structure

  • Data Passing: Uses data attributes to pass Django context to React

  • Asset Optimization: Uses Sekizai for optimal CSS and JS placement

  • Clean Separation: React content is contained within a dedicated div

CMS App Registration

The CMS app hook integrates the React application into Django CMS's routing system. In this case, we create a page to host our React app and then attach the application to the page. The CMS Apphook takes control of the page and routes for that page are not handled by the URL defined in the get_urls of the app.

# apps/jobs/cms_apps.py

@apphook_pool.register
class JobsApp(CMSApp):
    app_name = "jobs"
    name = "Jobs App"

    def get_urls(self, *args, **kwargs):
        return ["apps.jobs.urls"]

Registration Benefits:

  • CMS Integration: The app appears in the CMS admin for page attachment

  • URL Management: Automatic URL resolution within the CMS structure

  • Namespace Isolation: Clean separation from other CMS apps

Vite Configuration

For this integration to work, your Vite configuration should generate a manifest file:

// vite.config.js

export default {
    build: {
        manifest: true,
        outDir: 'backend/static/js/jobs/build',
        rollupOptions: {
            input: 'src/main.jsx'
        }
    }
}

 

Advantages of This Approach

  1. Development Experience

    • Hot module replacement during development
    • Modern JavaScript tooling with Vite
    • Component-based development with React
  2. Production Performance

    • Automatic asset optimization and minification
    • Tree shaking and code splitting
    • Efficient caching with file hashing
  3. CMS Integration

    • Seamless page management through Django CMS
    • Content editor-friendly workflow
    • Flexible deployment of React components
  4. Scalability

    • Multiple React applications per project
    • Independent deployment and versioning
    • Clear separation of concerns

Best Practices and Considerations

Asset Caching

Consider implementing caching for the manifest reading function:

from django.core.cache import cache

def get_css_and_js_link_from_vite_assets(project_type: str, uses_client: bool = False):
    cache_key = f"vite_assets_{project_type}_{uses_client}"
    assets = cache.get(cache_key)

    if assets is None:
        # Read and parse manifest as before
        assets = (css_links, js_links, main_js_links)
        cache.set(cache_key, assets, 300) # Cache for 5 minutes
    return assets

Error Handling

Add robust error handling for production environments:

def get_css_and_js_link_from_vite_assets(project_type: str, uses_client: bool = False):

    try:

        # Existing implementation

        pass

    except (FileNotFoundError, json.JSONDecodeError) as e:

        logger.error(f"Error reading Vite manifest for {project_type}: {e}")

    return [], [], [] # Return empty lists as fallback

Security Considerations

  • Validate project_type parameter to prevent path traversal

  • Use Django's static file security features

  • Implement proper Content Security Policy headers

Deployment Strategy

  1. Build Process: Run npm run build to generate production assets

  2. Static Files: Use collectstatic to gather all assets

  3. Cache Invalidation: Clear asset caches when deploying new builds

  4. CDN Integration: Consider serving assets from a CDN for better performance

Alternative Approaches

While this guide demonstrates the SPA-in-CMS approach, you might also consider using Headless CMS as an API backend with a separate React frontend

Conclusion

Integrating React with Django CMS provides a powerful foundation for modern web applications. This approach allows you to:

  • Maintain the editorial power of Django CMS
  • Leverage React's component ecosystem and developer experience
  • Use modern build tools for optimal performance
  • Scale your application architecture as requirements grow

The implementation we've explored provides a solid foundation that can be extended and customized based on your specific needs. Whether you're building interactive dashboards, dynamic forms, or complex user interfaces, this pattern offers the flexibility to create engaging user experiences within a CMS-managed environment.

By following this approach, your team can enjoy the best of both worlds: content editors get familiar CMS tools, while developers get modern JavaScript development workflows.

The integration of React with Django CMS opens up endless possibilities for creating rich, interactive web applications while maintaining the content management capabilities that make Django CMS so powerful.

blog comments powered by Disqus

Do you want to test django CMS?

Try django CMS