CLOSE
megamenu-tech
CLOSE
service-image
CLOSE
Blogs
Improve Performance of Dynamic pages in Django using SSG

Boost Django Page Performance with SSG

Improve Performance of Dynamic pages in Django using SSG

#Django

#DjangoSSG

#DjangoDevelopment

Technology, Published On : 18 October 2024
Boost Django Page Performance with SSG

I had a project where pages were rendered dynamically based on URL slugs and data from a model. Initially, I opted for server-side rendering (SSR), which worked fine. However, with a massive dataset and some complex data processing, rendering times shot up to 14–20 seconds—even after optimizing. That was far too slow for a smooth user experience.

The fix? SSG. Instead of rendering these pages on every request, I generated them once and served them as static files. This approach dramatically reduced the response time, giving users near-instant page access. In this guide, I’ll walk you through how I implemented this solution to balance dynamic content and fast delivery, transforming a slow, server-heavy process into a snappy experience.

Speed is a feature. The faster the site, the more people will use it.

What is a Static Site Generator?

Static Site Generator software creates HTML files from templates and source data. Webpages generated using SSG can be referred to as statically generated pages. That means these pages will be generated at build time, and their content will only change if you add or update the data source and rebuild it. You have to rebuild it if you want to update the content.

Improve Performance of Dynamic pages in Django using SSG

What is the advantage of SSG over SSR?

Generating pages based on content updates instead of rendering the same page repeatedly for each request has multiple advantages.

Improvement in page loading speed

One of the biggest advantages of Static Site Generation (SSG) over Server-Side Rendering (SSR) is the speed it offers, which significantly improves the user experience. Imagine an online clothing store: in an SSR scenario, every time a customer wants to buy a shirt, the store has to stitch it from scratch, leaving the customer waiting while their order is prepared. This can be frustrating and might even lead them to abandon their cart. Now, picture the same store using SSG. Here, the owner has already pre-made a large inventory of identical shirts, so when a customer clicks to buy, the shirt is ready to go — no waiting required. This instant availability satisfies customers and encourages them to browse and purchase more items. While SSR may provide dynamic content, it often comes at the cost of speed, whereas SSG ensures a smooth, fast experience that keeps customers happy and engaged.

Reducing Server Load

Let’s take the same stitching store example. In one scenario, they stitch clothes in real-time, which requires significant human resources to work in parallel because customers are waiting, and each item has to go through the same steps repeatedly for every new customer. Now, imagine another scenario where they stitch items in bulk, taking their time without rushing. This parallels the difference between SSR and SSG. With SSR, the server generates pages from scratch each time, repeating the same steps for every request, which increases the server load. With SSG, the pages are pre-generated and ready when requested, reducing server load significantly.

Implementing SSG for Dynamically Created Content in Django

We are building a blog project in Django, where the admin will create, update, and delete pages in Django Admin. We will use SSG to generate blog pages that can be served with the Django static serve framework in development mode or through other static file deployment strategies in production.

Step 1: Setting up Basic Django Project

In this tutorial, we’ll use Windows to illustrate how to set up static site generation (SSG) for dynamic pages in Django

To begin your Django project, follow these actions:

  1. Set Up a Virtual Environment: Create a virtual environment to keep your project dependencies isolated. Run the following commands in your terminal:

python -m venv myenv

Activate the virtual environment:

myenv\Scripts\activate

2. Install Django: Ensure you have Django installed in your environment. You can do this via pip. If you haven’t done it yet, run the following command:

pip install django

3. Create a New Project: Use the django-admin command to create a new project named BlogProject:

django-admin startproject BlogProject


4. Navigate to the Project Directory: Move into the project folder you just created:

cd BlogProject

  1. Create a New App: Use the following command to create a new app for managing blogs:

python manage.py startapp blog

Step 2: Create a Blog Model

We will be creating a Blog model to store blog details, with some important fields, Admin will use Django admin for crud operations of the Blog Model

  1. Create model

Open the models.py file in your blog app directory and define the Blog model:

from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User
from django.utils.text import slugify
class Blog(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    published_date = models.DateTimeField(auto_now_add=True)
    static_file_path = models.FilePathField(null=True)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.title

2. Register the Blog Model in Admin.

Next, we need to register the model in the Django admin interface. Open or create the admin.py file in your blog app directory and register Blog model:

from django.contrib import admin
from blog.models import Blog

@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'author', 'published_date')
    prepopulated_fields = {'slug': ('title',)}
    exclude = ('static_file_path',)

3. Migrate to models into the database

Now we need to migrate the model into the database table for your Blog model. Before you run migration commands, you need to make sure that blog is there in INSTALLED_APPS of settings.py :

INSTALLED_APPS = [
    ....
    'blog',
    ....
]

Run the following migration commands in your terminal:
python manage.py makemigrations
python manage.py migrate

Step 3: Create an HTML Template for the Blog Page

Now we need an HTML Template to render blog pages, we will be creating this inside blog app. Create a blog_detail.html file inside blog/templates/ directory:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>{{ blog.title }}</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                margin: 0;
                padding: 20px;
                background-color: #f4f4f4;
            }
            .blog-post {
                background-color: #fff;
                padding: 20px;
                border-radius: 5px;
                box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
                margin: 20px 0;
            }
            h1 {
                color: #333;
            }
            p {
                line-height: 1.6;
                color: #555;
            }
            .meta {
                font-size: 0.9em;
                color: #888;
            }
        </style>
    </head>
    <body>
        <div class="blog-post">
            <h1>{{ blog.title }}</h1>
            <p>{{ blog.content|linebreaksbr }}</p>
            <p class="meta"><strong>Author:</strong> {{ blog.author.get_full_name }}</p>
            <p class="meta"><strong>Published on:</strong> {{ blog.published_date|date:"F j, Y, g:i a" }}</p>
        </div>
    </body>
</html>

Ensure that the value of APP_DIRS is set to True in the TEMPLATES settings within settings.py ; otherwise, the template finder will not locate the file.

Step 4: Setup Static Site Generator

This is the major step in this tutorial, We will be creating a StaticBlogGenerator class to generate statically generated pages, and update and remove them according to the requirement.

  1. Create StaticBlogGenerator class

We will create the StaticBlogGenerator class inside blog/tasks.py file, This class will generate HTML pages from Blog object with the help of blog/templates/blog_detail.html template and store it in a specific folder

from django.conf import settings
from django.core.files.base import ContentFile
from django.core.files.storage import storages
from django.template import loader
from blog.models import Blog

class StaticBlogGenerator:
    template = settings.BLOG_PAGE_TEMPLATE
    storage_class = storages[settings.BLOG_PAGE_STORAGE]

    def __init__(self, blog: Blog):
        self.blog = blog

    def generate(self):
        html_content = self._render_to_string()
        return self._save(html_content)

    def delete(self):
        if self.blog.static_file_path is None:
            return
        fs = self.storage_class
        if fs.exists(self.blog.static_file_path):
            fs.delete(self.blog.static_file_path)

    @property
    def filename(self):
        return "%s.html" % self.blog.slug

    def _render_to_string(self):
        context = {'blog': self.blog}
        return loader.render_to_string(self.template, context)

    def _save(self, html_content):
        filename = self.filename
        fs = self.storage_class
        full_filename = "%s/%s" % (settings.BLOG_PAGE_ROOT, filename)
        file_path = fs.generate_filename(full_filename)
        if fs.exists(file_path):
            fs.delete(file_path)
        self.blog.static_file_path = file_path
        self.blog.save(update_fields=('static_file_path',))
        return fs.save(file_path, ContentFile(html_content))

2. Add settings variables in settings.py

You may have noticed some variables that need to be set in the settings.py file. Here’s an explanation of each:

BLOG_PAGE_TEMPLATE: Specifies the template to be used for generating a blog page.

BLOG_PAGE_STORAGE: Defines the storage system from the STORAGES settings that will be used to store the generated files.

BLOG_PAGE_ROOT: Specifies the root folder where the generated files will be stored.

settings.py :

BLOG_PAGE_STORAGE = 'default'
BLOG_PAGE_ROOT = 'blogs/'
BLOG_PAGE_TEMPLATE = 'blog_detail.html'

Step 5: Connect signals to trigger the generator

Now that we have all the components—template, model, and generator—it’s time to make it live. We will create signals to create, delete, and update the pages based on changes in the models.

We have three scenarios: creating and updating blog content, updating the blog slug, and removing the blog. Let’s write signals for each scenario in blog/signals.py

from blog.tasks import StaticBlogGenerator

def generate_blog_page_on_post_save(instance, update_fields, **kwargs):
        if isinstance(update_fields, frozenset) and 'static_file_path' in update_fields:
                return
        StaticBlogGenerator(instance).generate()

def remove_blog_page_on_slug_change(sender, instance, **kwargs):
        if not instance.id:
                return
        old_instance = sender.objects.filter(id=instance.id).first()
        if old_instance and old_instance.slug != instance.slug:
                StaticBlogGenerator(instance).delete()

def remove_blog_page_on_delete(instance, **kwargs):
        StaticBlogGenerator(instance).delete()

Let’s look closely into each signal function:

generate_blog_page_on_post_save is for generating blog pages whenever Blog an object updates, but here we have an exception if the update is for static_file_path field.

remove_blog_page_on_slug_change is a pre-save signal, It will remove the file for the slug changes.

remove_blog_page_on_delete is for deleting the page file after deleting the Blog object.

Now we need to connect these signals to Blog model in blog/apps.py

from django.apps import AppConfig
from django.db.models.signals import post_save, pre_save, post_delete

class BlogConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'blog'

    def ready(self) -> None:
        from blog.signals import (
            generate_blog_page_on_post_save,
            remove_blog_page_on_slug_change,
            remove_blog_page_on_delete
        )
        post_save.connect(generate_blog_page_on_post_save, self.get_model('Blog'))
        pre_save.connect(remove_blog_page_on_slug_change, self.get_model('Blog'))
        post_delete.connect(remove_blog_page_on_delete, self.get_model('Blog'))
        super().ready()

Step 6: Setting up URL configuration for Static Files

Now that we have statically generated blog pages, we need to configure Django to serve these files when a user requests a specific blog page. We can achieve this by serving the static files directly through Django in development mode, or by using a different static file deployment strategy in production.

  1. Serve Static Files in Development Mode:

Open your urls.py file and configure Django to serve the statically generated HTML files from the BLOG_PAGE_ROOT directory.

from django.conf import settings
from django.conf.urls.static import static
from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    # other paths...
]
if settings.DEBUG:
    urlpatterns += static(
        '/blogs/',
        document_root=settings.BLOG_PAGE_ROOT
    )

In this case, we’re using the built-in static helper function to serve the static blog pages under the /blogs/ path.

  1. Serve Static Files in Production Mode:

In a production environment, it’s best to serve static files through a more efficient server like Nginx. This allows your application to focus on dynamic content while Nginx or a CDN handles the static file delivery for optimal performance.

You can find a detailed guide on how to deploy static files in Django here: How to deploy static files

Conclusion

By using Static Site Generation (SSG) for your dynamic pages in Django, you can significantly improve performance by reducing server load and speeding up page delivery. This method allows for the benefits of static files while maintaining dynamic content through background generation and updates. It’s a win-win solution for projects where content changes occasionally but must be served quickly.

Mohammed Shahsad KP

Mohammed Shahsad KP

Junior Software Engineer

Enthusiastic full-stack developer specializing in creating efficient applications, dedicated to ongoing skill development, and passionate about exploring new technologies for innovative solutions

Modal_img.max-3000x1500

Discover Next-Generation AI Solutions for Your Business!

Let's collaborate to turn your business challenges into AI-powered success stories.

Get Started