← journal

Zola speedrun

Zola is a static site generator (SSG) written in Rust. It relies on Terra template engine. That’s all for the technical nerd speak, let’s get to building.

Start by initialing the project. Run:

zola init myblog

There will be several prompts. Provide a website address (can be a dummy). Also, feel free to reply N to all of them, we will configure everything afterwards.

Structure

By default, the generator expects this project structure:

├── config.toml
├── content/
│   └── blog/
│       ├── _index.md
│       ├── first.md
│       └── second.md
├── sass/
├── static/
├── templates/
│   ├── base.html
│   ├── page.html
│   ├── list.html
│   └── index.html
└── themes/

Here’s how it works:

Templates

Create the templates/ folder.

Let’s begin with the templates/base.html:

<!DOCTYPE html>
<html lang="en">

<head>
  {% include "head.html" %}
</head>

<body>
  {% include "navigation.html" %}
  <main>
    {% block content %} {% endblock %}
  </main>
  {% include "footer.html" %}
</body>

</html>

Notice there are placeholders for head.html, navigation.html, and footer.html. Let’s create those as well in the templates/.

# head.html
<meta charset="UTF-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<meta name="robots" content="index, follow">
  <div class="">
    {% for item in config.extra.navigation %}
    <a class=""
      href="{{ item.url }}">{{
      item.name }}</a>
    {% endfor %}
  </div>
# footer
<footer>
  My super cool blog 2024
</footer>

Notice: in navigation.html there are some items coming from config.extra. We are going to create them next.

# navigation
<nav>
    {% for item in config.extra.navigation %}
      <a
      href="{{ item.url }}">{{ item.name }}</a>
    {% endfor %}
</nav>

Lastly, let’s create templates/general.html for pages like index, contact, etc:

{% extends "base.html" %}
  {% block content %}
    <h1>
      {% if section.title -%}
        {{ section.title }}
      {%- endif %}
      {% if page.title -%}
        {{ page.title }}
      {%- endif %}
    </h1>

    <p>
      {% if section.description -%}
        {{ section.description }}
      {%- endif %}
      {% if page.description -%}
        {{ page.description }}
      {%- endif %}
    </p>

    <article>
      {% if section.content -%}
        {{ section.content | safe }}
      {%- endif %}
      {% if page.content -%}
        {{ page.content | safe }}
      {%- endif %}
    </article>
  {% endblock content %}

Configuration

In Zola, config.toml is the source of truth. There, we put the project’s title, description, RSS, navigation (if you prefer), and everything else. Don’t forget to check out Zola Docs for more. Here’s a sensible starter:

base_url = "https://my-cool-blog.com"
title = "Your blog's title"
description = "The description goes here"
default_language = "en"
minify_html = true
generate_feed = true
feed_filename = "atom.xml"
author = 'Arya Stark'
compile_sass = false
build_search_index = false

[markdown]
render_emoji = true
external_links_target_blank = true
external_links_no_follow = true
external_links_no_referrer = true
smart_punctuation = true
lazy_async_image = true

[link_checker]
internal_level = "error"
external_level = "error"

[slugify]
paths = "on"
anchors = "on"

[extra]
navigation = [
  { url = "/now", name = "now"},
  { url = "/journal", name = "journal"},
  { url = "/workshop", name = "workshop"},
  { url = "/bookmarks", name = "bookmarks"},
]

Content

The conten will live in a dedicated folder. Let’s create it: /content. Here we are going to have subfolders. Think of them as types of content you post: blog posts, videos, or podcast episodes. We can hav as many subfolders as we want. For example, I settled for journal, workshop and bookmarks for my personal website.

Such sections can include _index.md, which defines the content and metadata for the section. Let’s create content/blog/_index.md.

+++
title = "I am a section title"
description = "I am a section description"
template = "list.html"
paginate_by = 8
sort_by = "date"
+++

Mind the dividers +++.

It all starts with frontmatter. By default, _index.md is for listing content. But you can write some markdown as well. It will be rendered on the section page consequently.

Les’t create a couple of articles. Now, in content/blog/ create article1.md:

+++
title = "Article title"
description = "Some fancy description can go here as well"
date = "2024-01-05"
draft = false
template = "page.html"
+++

# Heading 1
Ut culpa quis et ut nulla occaecat elit ad commodo consequat. Ea nostrud ut adipisicing et ipsum nostrud cillum non magna in ad. Est reprehenderit exercitation excepteur ut laborum minim non occaecat amet. 

## Heading 2
Laborum aliqua duis commodo irure anim occaecat Lorem deserunt. Dolore dolor adipisicing ad sint non dolor ut Lorem consectetur fugiat quis. In aute non in amet velit ut dolor adipisicing magna reprehenderit incididunt labore incididunt.

### Heading 3
Esse proident ullamco id adipisicing velit ad do sint nisi commodo consectetur. Commodo enim consequat eu dolore aliqua pariatur consectetur eiusmod quis minim ut irure. Non aliquip laboris occaecat sunt adipisicing nisi occaecat occaecat esse Lorem ullamco. 

- Bullet 1
- Bullet 2
- Bullet 3

1. Argument 1
2. Argument 2
3. Argument 3

>Some super smart quote

Render content

How to render it? Hint: notice the template field in both frontmatters above. Let’s create them, starting with templates/list.html:

{% extends "base.html" %}
{% block content %}
  <h1>{{ section.title }}</h1>

<!-- content list -->
  <ul>
    {% for page in paginator.pages %}
        <li>
          <a href="{{ page.permalink | safe }}">
             {{ page.title }}
          </a>
        </li>
    {% endfor %}  
  </ul>

<!-- paginator -->
{% if paginator %}
  {% if paginator.previous %}
    <a href="{{ paginator.previous }}">&#8592; Previous</a>
  {% endif %}
    {% if paginator.next %}
      <a href="{{ paginator.next }}">Next &#8594;</a>
    {% endif %}
  {% endif %}

{% endblock content %}

Next, let’s create templates/page.html:

{% extends "base.html" %}

  {% block content %}
    <h1>{{ page.title }}</h1>
    <article>{{ page.content | safe }}</article>

    <!-- paginator -->
  {% if paginator %}
    {% if paginator.lower %}
      <a href="{{ page.lower.permalink | safe }}">&#8592; Previous</a>
    {% endif %}
    {% if paginator.higher %}
      <a href="{{ page.higher.permalink | safe }}">Next &#8594;</a>
    {% endif %}
  {% endif %}
  {% endblock content %}

Style

There are many tools that allow creating great-looking interfaces. If you prefer working with plain css, create static/style.css and work here. Or, if you like working with Tailwind, the simplest way is to update your head.html:

  <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>

Conclusion

Zola is a great site generator: easy to use, it has great markdown support, zero dependencies, and clean developer experience. It is also scalable, extensible and comes with a dedicated catalog of themes.