Add infinite scroll with Django and HTMX in 60 seconds ♾️

Published: November 17, 2023

I’ll show you how to add infinite scrolling to your Django app in 60 seconds using HTMX.

Infinite scroll speeds up your initial page load. Instead of immediately loading all content, you only load content as your users scroll.

There are 5 steps to add infinite scroll with HTMX and Django below.

Our finished Django app with infinite scrolling will look like this:

Optional video tutorial featuring me below 📽️:

Photo of Tom Dekan giving a talk called 'Add infinite scroll with Django and HTMX in 60 seconds' at the London Django Meetup 🇬🇧in November

Update: Here's me presenting this guide at the Django Meetup in London 🇬🇧 in November

Let’s go 🚀

Setup Django app

  • Install our requirements into a virtual environment and create our Django project and app:
pip install django faker
django-admin startproject core .
python manage.py startapp sim

Update settings.py

INSTALLED_APPS = [
    # ...
    'sim',
]

1. Create the Model and Sample Data

# sim/models.py
from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    photo_url = models.URLField()

    def __str__(self):
        return self.title
  • Run migrations:
python manage.py makemigrations
python manage.py migrate
  • Generate some sample data:
python manage.py shell
# In the python shell:
from sim.models import Article
from faker import Faker
fake = Faker()

for i in range(100):
    sentences = ' '.join(fake.sentences(5))
    Article.objects.create(
        title=fake.sentence(),
        content=sentences,
        photo_url=f"https://picsum.photos/seed/{i+1}/600"
    )

2. Create the views

# sim/views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Article

def articles(request):
    """
    Fetch paginated articles and render them.
    """
    page_number = request.GET.get('page', 1)
    paginator = Paginator(Article.objects.all(), 10)
    page_obj = paginator.get_page(page_number)

    return render(request, 'articles.html', {'page_obj': page_obj})

3. Update URLs

  • Update core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('sim.urls')),
]

  • Create sim/urls.py containing:
from django.urls import path
from . import views

urlpatterns = [
    path('', views.articles, name='articles'),
]

4. Create HTML template

  • Create templates folder at sim/templates/
  • Create articles.html in sim/templates/ containing:
<!doctype html>
<html>
  <head>
    <script
      src="https://unpkg.com/htmx.org@1.9.6"
      integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni"
      crossorigin="anonymous"
    ></script>
    <style>
      /* Body and Container Styles */
      body {
        font-family: Arial, sans-serif;
        line-height: 1.6;
        margin: 0;
        padding: 0;
      }

      #articles {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 20px;
        max-width: 1200px;
        margin: auto;
        padding: 20px;
      }

      h1 {
        grid-column: 1 / -1;
        /* Span all columns */
        margin-bottom: 20px;
        text-align: center;
        text-decoration: underline;
      }

      /* Article Styles */
      .article {
        border: 1px solid #ccc;
        padding: 20px;
        border-radius: 8px;
      }

      .article h2 {
        font-size: 24px;
        margin-bottom: 10px;
        text-align: center;
      }

      .image-container {
        display: flex;
        justify-content: center;
      }

      .article img {
        max-width: 100%;
        height: auto;
        border-radius: 8px;
      }

      .article p {
        text-align: justify;
      }
    </style>
  </head>

  <body>
    <h1>Articles</h1>
    <div id="articles">
      {% for article in page_obj %}
      <div class="article">
        <h2>{{ article.title }}</h2>
        <a class="image-container" href="{{ article.photo_url }}">
          <img
            width="300"
            height="300"
            src="{{ article.photo_url }}"
            alt="A sample image"
          />
        </a>
        <p>{{ article.content }}</p>

        {% if page_obj.has_next and forloop.last %}
        <span
          hx-get="{% url 'articles' %}?page={{ page_obj.next_page_number }}"
          hx-swap="beforeend"
          hx-target="#articles"
          hx-select=".article"
          hx-trigger="revealed"
        >
        </span>
        {% endif %}
      </div>
      {% endfor %}
    </div>
  </body>
</html>

5. Run our server

python manage.py runserver

Finished - You can now scroll infinitely through your articles 🎉

You've just built a sleek Django app complete with infinite scrolling. You get the benefits of loading the content that the user wants on demand, meaning a much faster initial page load.

P. S - Photon Designer

I'm building Photon Designer - an entirely visual editor for building Django frontend at the speed that light hits your eyes 💡.

Subscribe to my free newsletter

Get updates on AI, software, and business.