Contact Blog Consulting

Serving a Jekyll blog with Django

I’ve been using Jekyll for the blog over at gpxz.io but eventually the site is going to run on Django.

I want to keep using Jekyll to manage and render blog posts as it’s the best tool for the job and I already have it set up. But I also want the blog to inherit from the website’s Django templates for visual consistency, and I want requests to go through Django middleware.

I was able to get this working with the following approach:

  1. Get Django to render a liquid template.
  2. Use that template in Jekyll to render blog posts.
  3. Serve those blog posts with Django.

1. Rendering a Jekyll template with Django

We’ll start off my making kind of a meta template: a Django template that, when rendered, will produce a Jekyll template. The Django template can extend the base template you’re using for Django (with your navbar, footer, etc). {% verbose %} tags are needed to prevent Django from trying to execute the liquid tags.

For example this template

<!-- django/myapp/templates/blog_post.html -->

{% extends "base.html" %}

{% block main %}
<div class="blog-post">
    <h1>{% verbatim %}{{ page.title }}{% endverbatim %}</h1>
    {% verbatim %}{{ content }}{% endverbatim %}
</div>
{% endblock %}

when rendered by django will result in a Jekyll template like

<!-- jekyll/_layouts/post.html -->

<html>
  <head></head>
  <body>
    <nav></nav>
    <div class="blog-post">
        <h1>{{ page.title }}</h1>
        {{ content }}
    </div>
    <footer></footer>
  </body>
</html

Next we need a trigger to render the template into Jekyll’s _layouts directory. I use autoreload in development for Django so I used AppConfig to render the template whenever Django is started (which will happen whenever the template is modified), but you could also add this as a manage.py command or similar:

# django/myapp/apps.py

from django.apps import AppConfig
from django.conf import settings
from django.template.loader import render_to_string


class MyAppConfig(AppConfig):
    def ready(self):
        if settings.WRITE_BLOG_TEMPLATES_ON_STARTUP:
            html = render_to_string("www/blog-post.html")
            dest_path = "/app/jekyll/_layouts/post.html"
            with open(dest_path, "w") as f:
                f.write(html)

2. Render site with Jekyll

Not much needs to change on the Jekyll side: just put layout: post in your fontmatter.

3. Serve blog posts with Django

Add the rendered blog folder as a template directory in Django

# django/myapp/settings.py

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [os.path.join(BASE_DIR, "myapp", "templates"), "/app/blog/_site/"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
                "django.template.context_processors.request",
            ],
        },
    },
]

Add a catchall url to route blog requests

# django/myapp/urls.py

urlpatterns += [
  path("blog/<slug:slug>", BlogPostView.as_view())
]

and the corresponding view

# django/myapp/views.py

from django.views.generic import TemplateView


class BlogPostView(TemplateView):
    def dispatch(self, request, *args, **kwargs):
        post_name = self.kwargs['slug']
        post_filename = post_name + '.html'
        post_path = os.path.join('/app/blog/_site', post_filename)
        return render(request, post_path)

then you should be good to go!

You can add views for feed.xml and sitemap.xml too.

Other approaches