purple cello
Zola is a static site generator written in Rust. Zola has a pretty good documentation to get you started quickly.
As a reminder, this will be how to use Zola to create a blog that fits my own needs. It might not be relevant, or even applicable to someone else's needs. The documentation seems to suggest to keep blog posts in a content/blog
directory, which would output a similar path, and the way to achieve something different is mostly by creating theme files. My approach has been quite different than what seems to be the recommended one.
git clone https://github.com/getzola/zola.git
cd zola
cargo install --path . --locked
zola --version # should return "zola 0.19.2" or similar
# copy binary somewhere in $PATH
cp target/release/zola ~/.cargo/bin/zola
Alternatively, we can download the latest binary somewhere in $PATH
and make it executable.
Zola comes with a few build-in commands (see all with zola help
):
zola init
: scaffolds a Zola project (creates directory structure and toml file).zola build
: recreates the public
directory which contains the site.zola serve
: serves the site at 127.0.0.1:1111
, reloads on changeSo, to create our first Zola project:
mkdir myproject
cd myproject
zola init
For demonstration purposes, we will just press enter to every question, accepting the default values. Now, inside the directory myproject
we have the following structure:
tree myproject
.
├── config.toml
├── content
├── sass
├── static
├── templates
└── themes
All the values we accepted by default in the previous step are in config.toml
:
base_url = "https://example.com"
compile_sass = true
build_search_index = false
[markdown]
highlight_code = false
[extra]
# Put all your custom variables here
In this example, we will not use sass or themes, so we can rmdir sass themes
to delete the directories. We also need to change the config.toml
to reflect this:
base_url = "/"
compile_sass = false
build_search_index = false
[markdown]
highlight_code = true
[extra]
# Put all your custom variables here
We will use base_url = "/"
so to get relative links when we build the site.
Zola uses the Tera templating engine, which works very similarly to jinja2. We can create a base template and extend it with the {% extends <template name>.html %}
syntax and mark where the content will be injected with {% block content %}{% endblock %}
:
templates/base.html
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Zola blog</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
templates/index.html
{% extends "base.html" %}
{% block content %}
<h1>Hi</h1>
{% endblock %}
We can serve the site with zola serve
and watch the changes at 127.0.0.1:1111
Everything that is considered content goes to the content
directory. Any subdirectory of content
will have the respective path. For example, if we decide to create all our blog posts in content/articles
, and we create the file content/articles/post-1.md
, the URL of the post will be 127.0.0.1:1111/articles/post-1. Similarly, if we create an "About" page in content/about.md
, it will be displayed at 127.0.0.1:1111/about.
However, for this example, we want flat URLs for everything. Every post should have a path of <base url>/my-post
and every page a path of <base url>/mypage
.
To achieve this, we will add our blog posts in content
without a directory structure.
The first thing we need is to create our first blog post with some frontmatter. Frontmatter in Tera can have the form of yaml frontmatter:
---
title: my title
date: 2024-01-01
description: my first blog post
---
although it is recommended to use toml-like frontmatter:
+++
title="my title"
date=2024-01-01
description="my first blog post"
+++
I will be using the yaml frontmatter format, for more portability of the blog posts between static site generators.
So, let's create that blog post:
content/post-1.md
---
title: First post
date: 2024-01-01
---
## My first Zola post
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae est turpis.
If we try to see our post at 127.0.0.1:1111/post-1/, we will see instead the "Welcome to Zola" page, with the following message:
You're seeing this page because we couldn't find a template to render.
To modify this page, create a page.html file in the templates directory or install a theme.
So, we need to create one more template, templates/page.html
:
{% extends "base.html" %}
{% block content %}
<h1>{{ page.title }}</h1>
<p>Published on: {{ page.date }}</p>
{{ page.content | safe }}
{% endblock %}
And now our post is visible at 127.0.0.1:1111/post-1.
We will rewrite content/index.html
:
{% extends "base.html" %}
{% block content %}
<h2>My blog</h2>
<ul>
{% for p in section.pages %}
<li><a href="{{ p.permalink }}">{{ p.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Let's say we need some "static" pages, such as an "About" page, maybe a "Services", or "Projects", or really whatever. Following the previous flat directory approach, let's add the "About" page in the content
directory:
content/about.md
---
title: "About"
date: 2024-01-01
---
Hi, this is the "About" page.
To add a link to homepage in every page, we modify templates/base.html
:
<body>
<nav><a href="/">Home</a> | <a href="/about/">About</a></nav>
<hr>
{% block content %}{% endblock %}
</body>
Now everything works correctly, and all pages are linked and resolve correctly. However, there are two problems:
templates/page.html
template, displays the day published, which might not be desirable for static pages.The first problem is very easy to solve: We edit the templates/page.html
page as follows:
{% extends "base.html" %}
{% block content %}
<h1>{{ page.title }}</h1>
{% if page.date %}
<p>Published on: {{ page.date }}</p>
{% endif %}
{{ page.content | safe }}
{% endblock %}
Then, we completely remove the date: 2024-01-01
frontmatter variable from the about.md
file, and this works perfectly! The page doesn't display a date anymore.
Alternatively, we can create a new template, templates/static.html
:
{% extends "base.html" %}
{% block content %}
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
{% endblock %}
and make the "About" page use it by adding it in the frontmatter:
---
title: About
date: 2024-01-01
template: "static.html"
---
Both of these ways correctly resolve the problem (1). To resolve (2), however, we need to find a way to separate blog posts from static pages. Let's create a subdirectory in the content
directory: content/pages
and move content/about.md
there. Now the page is served from <base url>/pages/about/
, which is still not what we want. So, let's edit the frontmatter of content/pages/about.md
:
---
title: About
path: about
---
And now, everything works as expected!
content
directorySo far so good, but there is a slight problem. As we go on adding more posts in our content directory, the index page that lists the posts displays them in chronological order. That is, the oldest is first and the newest is last in the list. This is not the usual way people set their blogs and it might not be the desirable behaviour.
As far as I could tell, without any intervention, Zola orders texts chronologically according to their timestamp. We want to use the date
metadata in our frontmatter and have the post list display them in a reverse chronological order (newest first).
For this, we will add an content/_index.md
file in our content directory. The frontmatter of this file will set some preferences for the directory; the underscore in the file name is there so that it always shows up first in our directory.
content/_index.md
+++
sort_by = "date"
+++
And that takes care of it! We can add all sort of configuration options in the _index.md
file, such as what template the pages should use (if different than the default, page.html
, what template the index file itself should use, and more. Basically, the _index.md
file creates a section, and, in a complex site structure, each section can have its own configuration parameters.
If we want to add a CSS style, we will add it in the static
directory (ex., static/main.css
and link it in the base template simply as <link rel="stylesheet" href="/main.css">
. Similarly, we can drop images in the static
directory and link them as <img src="/myimage.jpg" alt="my image">
(or ![my image](/myimage.jpg)
in markdown).
Zola offers template variables for a table of content out of the box. We want to use the page.toc
variable and we want to start from the h2
header (as h1
is the title):
<strong>Table of contents</strong>
<ul>
{% for h2 in page.toc %}
<li><a href="{{ h2.permalink | safe }}">{{ h2.title }}</a>
{% if h2.children %}
<ul>
{% for h3 in h2.children %}
<li><a href="{{ h3.permalink | safe }}">{{ h3.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
This could go in the page.html
template, to work only with posts.
Zola also has built-in the option for feeds. In config.toml
:
base_url = "/"
compile_sass = false
build_search_index = false
generate_feeds = true
author = "John Doe"
[markdown]
highlight_code = true
[extra]
# Put all your custom variables here
This will automatically generate a feed at 127.0.0.1:1111/atom.xml.
Zola will use the templates/404.html
if it finds one:
{% extends "base.html" %}
{% block content %}
<h1>404 - Page not found</h1>
<p>Return to <a href="/">Home</a></p>
{% endblock %}