Migrating to Zola

16 Dec 2023

Migrating from Jekyll to Zola

4 minutes reading time

#Jekyll

I started using Jekyll around 5 years ago and never needed another SSG. Its workflow is very straightforward and easy to tame on Linux/Mac (not to mention integrations with GitHub, etc). But a lot has changed, including restricted plugins. Now instead of debugging pages, I have to write ports for plugins or deal with rbenv. Let's fix this.

#CI

My legacy workflow was built around a single public repository pushing to GitHub Pages. This works great for a documentation/project site. But public drafts and cached source code make this look somewhat sloppy. So this time I decided to push it into a private repository. Currently, GitHub allows 2000 minutes of CI time per month on private repositories, which should be more than enough in my case.

build.yml:

name: Build

"on":
  push:
  pull_request:
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shalzz/zola-deploy-action@v0.17.2-1
        env:
          BUILD_DIR: .
          BUILD_ONLY: true

  deploy:
    needs: build
    if: github.ref == 'refs/heads/trunk'
    uses: ./.github/workflows/deploy.yml
    secrets:
      TOKEN: ${{ secrets.GHP_TOKEN }}

deploy.yml:

name: Deploy

"on":
  workflow_call:
    secrets:
      TOKEN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shalzz/zola-deploy-action@v0.17.2-1
        env:
          PAGES_BRANCH: trunk
          BUILD_DIR: .
          REPOSITORY: charlesrocket/charlesrocket.github.io
          GITHUB_TOKEN: ${{ secrets.TOKEN }}

This configuration utilizes GitHub's reusable workflows and allows one to manually trigger the build when required.

#Zola

Wow, Zola! Very fast, packed with the essentials, and its Tera template engine is amazing!

Although porting my old theme wasn't hard at all, I failed to add a taxonomy prefix to posts; this is unsupported at the moment. But now I think I'll probably stick with shorter URLs anyway and expand categories, just like tags.

I wasn't sure how to approach CSP via meta tags (since GitHub does not allow server-side header tunings), but the built-in get_hash function works on compiled SASS files just as well.

page vs section along with the taxonomies, make some use cases quite confusing (there is a proposal to deprecate sections), but the current version is 0.17.2, so I will just follow the changelogs for now.

#Tera

Looks very familiar:

{% block title %}
  {% if page.title %}
    {{ page.title }}{{ config.title }}
  {% else %}
    {{ config.title }}
  {%- endif %}
{% endblock title %}

#Templates

Almost the same:

+++
title = "Posts"
sort_by = "date"
template = "posts.html"
page_template = "page.html"
+++

I'm not sure yet how I will be using non-post pages, but the standard layout from docs worked perfectly:

posts.html:

{% extends "base.html" %}

{% block content %}
<body id="posts">
  <div class="block-left">
    <div class="content">
      <a href="{{ config.base_url }}" class="logo"><img src="{{ get_url(path=config.extra.logo) }}", alt="logo" width="64" height="64"></a>
      <h1 class="section-title">{{ section.title }}</h1>
    </div>
  </div>
  <div class="block-right">
    <a href="{{ config.base_url | safe }}/tags" title="tags" class="dashed-top-link">tags</a>
    <div class="content">
      <ul class="posts-list">
        {% for page in section.pages %}
          <li>
            <h2>
              <a href="{{ page.permalink | safe }}" class="post-title"><span>{{ page.title }}</span></a>
              <span class="date">{{ page.date | date(format="%d %b %Y") }}</span>
            </h2>
            <div class="post-info">
              <span class="word-count">{{ page.word_count ~ "w"}}</span>&nbsp;<span class="read-time">{{ page.reading_time ~ "m"}}</span>
            </div>
            <p>{{ page.summary | striptags | truncate(length=150) | safe }}</p>
            <ul class="tags title-tags">
              {% for tag in page.taxonomies["tags"] %}
              <li><a href="{{ config.base_url | safe }}/tags/{{ tag | slugify }}">{{ tag }}</a></li>
                {% if not loop.last %}
                {% endif %}
              {% endfor %}
            </ul>
          </li>
        {% endfor %}
      </ul>
    </div>
  </div>
</body>
{% endblock content %}

Pagination, tags, post summary - just like before, but better!

I probably should refactor all templates and utilize blocks better before moving my theme into a submodule (right after I finish touching up all the cool things that got implemented during this migration).