Zola Tutorial: How to use Zola the Rust based static site generator for your next small project, and deploy it on Netlify
Tags: beginners, tutorial, rust, zola
Published: 2020-06-29 15:18:55

(Photo by June Wong on Unsplash)

**Demo: [https://hello-zola.netlify.app/](https://hello-zola.netlify.app/)**

**Repo: [https://github.com/davidedelpapa/zola-helloworld](https://github.com/davidedelpapa/zola-helloworld)**

[Zola](https://www.getzola.org/) (by Vincent Prouillet) is a static site generator, just like Hugo or Gatsby.

It is written in Rust and it uses the [Tera](https://tera.netlify.com/) templating engine (by the same author).

## Features, pro's and con's

- It is a Static Site Generator: these have already their advantages, and disadvantages, _some_ of which are:

|Pro                       |Con                       |
|--------------------------|--------------------------|
|**Fast**: Once the site is generated it is just really a bunch of `html` and `css` files | **bare minimum**: Many advanced/reactive functionalities are stripped down: we have just the static pages|
|**Less infrastructure**: all the DB calls, the optimizations, etc, ere done at generation time: the infrastructure doesn't need to be in the server, but only in generating the site. The server stores just the static final files|**More disk space** Granted, disk space is getting cheaper, but transforming a large company site to a static site can use up lot of space in static pages that a `php` app connected to a DB could generate as needed, and customized|

Features specific to Zola:

- Minimal: Just focus on writing the content
- Augmented Markdown: Zola has an extended set of shortcodes and internal links to improve over the bare markdown.
- Easy to use: one CLI only, zero other dependencies.

We should at least give it a try.

## Installation from Github Releases

We could use various means to install Zola.

The fastest way to do it is to get the executable from [https://github.com/getzola/zola/releases](https://github.com/getzola/zola/releases)

We have to unzip it and move it in a folder reachable in our `$PATH`, for example, `~/.local/bin`

```bash
mv zola ~/.local/bin/zola
```

### Installation from Sources

We can install it from sources. It is not the recommended way, but if you want to do it, here's how. 

#### Dependencies

First we have to check the dependencies, though.

- [libsass](https://github.com/sass/libsass). I got mine from standard repos (for Debian/Ubuntu)

```bash
sudo apt get install libsass-dev
```

### Clone and Build

Clone the [git repo](https://github.com/getzola/zola) and `cargo build --release`. 

```bash
git clone https://github.com/getzola/zola.git
cd zola
cargo build --release
```

It is a very standard Rust crate, and as tradition wants it, the build _might_ take a while: I had a message at the end stating `Finished release [optimized] target(s) in 43m 50s`.

Now we have it under _./target/release/_. we have to move it in a folder reachable in our `$PATH`:

```bash
mv ./target/release/zola ~/.local/bin/zola
```

it can be for example: ~/.local/bin

Also remember: the build might take also _some space_ (mine took 1,5 GB), not the final file, of course, but remember to `cargo clean` after copying the file.

### Check the installation

To check if it's working:

```bash
zola --help
```

The help screen:

```bash
zola 0.11.0
Vincent Prouillet <hello@vincentprouillet.com>
A fast static site generator with everything built-in

USAGE:
    zola [OPTIONS] <SUBCOMMAND>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -c, --config <config>    Path to a config file other than config.toml in the root of project
    -r, --root <root>        Directory to use as root of project [default: .]

SUBCOMMANDS:
    build    Deletes the output directory if there is one and builds the site
    check    Try building the project without rendering it. Checks links
    help     Prints this message or the help of the given subcommand(s)
    init     Create a new Zola project
    serve    Serve the site. Rebuild and reload on change automatically
```

## Zola HelloWorld

We will first create and init a new project

```bash
zola init zola-helloworld
```

It will ask us some questions. Don't worry, you can always modify the `config.toml` file later.

```bash
> What is the URL of your site? (https://example.com):
> Do you want to enable Sass compilation? [Y/n]:
> Do you want to enable syntax highlighting? [y/N]:
> Do you want to build a search index of the content? [y/N]:
```

As a first run just pressed enter, and get the defaults: we can change them later, in the `config.toml`.

Let's move to our new folder

```bash
cd zola-helloworld
```

We are presented with a structure like this

```bash
helloworld
├── content/
├── sass/
├── static/
├── templates/
├── themes/
└── config.toml
```

We have 5 empty folders, and the `config.toml` file.

Let's take a look at its content:

```toml
# The URL the site will be built for
base_url = "https://example.com"

# Whether to automatically compile all Sass files in the sass directory
compile_sass = true

# Whether to do syntax highlighting
# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
highlight_code = false

# Whether to build a search index to be used later on by a JavaScript library
build_search_index = false

[extra]
# Put all your custom variables here
```

All options were saved there. As you can imagine, we can change them any time.

Let's cold start our testing server without any file:

```bash
zola serve
```

Zola by default serves on port `1111`: [http://localhost:1111/](http://127.0.0.1:1111/). Pointing our browser there we will be greeted by a default `Welcome to Zola!` message.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0cfwxn4fgmoyzoyudkjw.jpg)

We can leave the server on, because as we add file, zola will rebuild the content and hot load it to the browser. Neat, uh?

## Adding templates and pages

We need to templates, which tell the zola engine how to render content, and pages, where there is the actual content.

### 1st Template: templates/base.html

In the folder _templates/_ we will add a _base.html_ to be used by all other template and pages.

```html
<!DOCTYPE HTML>
<html>
    <head>
        <title>{{ page.title }}</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
    </head>
    <body>
    {{ page.content }}
    </body>
</html>
```

All the `html` tags are the usual, but there are two parts inside ```{{ }}``` which don't look like `html` tags at all: those are `tera` directives, very similar to [jinja2](https://jinja.palletsprojects.com/en/2.10.x/) or [liquid](https://shopify.github.io/liquid/).

1. The first directive, ```{{ page.title }}```, renders the page title.
2. the second directive, ```{{ page.content }}```, renders the page content. We add the `safe` filter to render properly the `html` generated, otherwise the content will be render as a `html` source!

### 1st Page: content/index.md

We need to add a page to use that template.

We will create a page in _content/_ directory.

Upon adding the file, Zola will complain and fail its build:

```bash
-> Content changed .../zola-helloworld/content/index.md
Failed to build the site
Error: Couldn't find front matter in `.../zola-helloworld/content/index.md`. Did you forget to add `+++`?
```

This is because we did not add the header part to it; let's remedy to that.

Open the `index.md` page and add the following

```yaml
+++
title = "First Zola page"
template = "base.html"
+++
# Hello world!

My first zola page.
```

As you can see, the part between `+++` is a `yaml` section, containing variables.

- `title` will be used to render the `page.title` we put in the `base.html` template.
- `template` tells which template to use.
- After the `+++` section there is the actual page content, which will be stored in `page.content`

Time to see how it is rendered!

Now if we go to the browser... nothing! We can even refresh the page and... nothing happens!

That's because our page is not there on the home, but under `/content`!

Pint your browser to: `http://127.0.0.1:1111/content/` and you should see the page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5eugl3ohmd18hxdb0ov2.jpg)

Fantastic!

Well, no! I hear you screaming from here: "that's not the expected way!" (or worst screams, I know).

In realty zola expects that the first page is a little more intriguing than blog pages, for examples, so that it might need more code. That is why, to have a proper `index.html`, in the usual place (the root of the server), we will need to place a real `index.html` in the `templates/` folder, and that will be the _root_ of our server.

But before adding a `index.html` inside the `templates/` folder, let's modify the `base.html`, so that we can use that as base for our `index.html` as well.

## Template inheritance

We first need to modify the _base.html_ to allow for nheritance.

### templates/base.html

In _tera_ inheritance is primarily achieved by substituting **blocks**.

```html
<!DOCTYPE HTML>
<html>
    <head>
        <title>{% block title %}{% endblock title %}</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
    </head>
    <body>
    {% block content %}{% endblock content %}
    </body>
</html>
```

Now, instead of the rendering of the two pages variables, achieved by ```{{ page.title }}```, for example, we have blocks, defined as ```{% block <name> %}{% endblock <name> %}```

The difference is thus: 

- ```{{ }}``` is for variables that must be rendered, similar to a `println!()` in Rust, or `echo ...` in bash, or `print()` in Python.
- `{% %}` is for commands. In this case we define a `block` which can be substituted by inheriting pages.

Let's see how it works in a `index.html` which we will finally add to the `template/` folder:

### templates/index.html

Upon adding the page `index.html` in the `template/` folder, zola will rebuild the site as usual, without complaining; but if we go to the homepage at `http://127.0.0.1:1111/` we will not see anymore the greeting message, but an empty page. Guess it's working...

Let's populate the page content

```html
{% extends "base.html" %}

{% block title %}Welcome to Zola{% endblock title %}

{% block content %}
<h1>Hello Zola!</h1>
{% endblock content %}
```

The first directive tells which templates this one extends from (the parent from which it inherits).

Afterwards, each `{% block %}` directive will substitute the content inside the parent's blocks.

In this case the parent's blocks were left empty, but it can have its own content, that the child will substitute anyway.

Thus:

- `{% block title %}Welcome to Zola{% endblock title %}` will substitute the `{% block title %}{% endblock title %}` inside the `base.html`.
- Similarly, `{% block content %}<h1>Hello Zola!</h1>{% endblock content %}` will substitute the `{% block content %}{% endblock content %}` inside `base.html`.

Time to save and check again the browser (zola's got hot reload, so if the server is left on you should already see the changes, otherwise run `zola serve`)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/90y06mjjnue4ww86ord2.jpg)

Now it works.

## Let's blog!

Now if we point our browser to `http://127.0.0.1:1111/content/` we see that the page is not working anymore. That's because we changed the `base.html` template, so that now there are no more "renderable" variables, but only blocks.

We will see how to restore it, and actually, how to create a basic blogging site out of this.

### templates/blog-page.html

Let's create a super-simple template for the pages of our blog.

```html
{% extends "base.html" %}

{% block title %}{{ page.title }}{% endblock title %}

{% block content %}
{{ page.content | safe }}
{% endblock content %}
```

As you can see, now we stuck the variables to be render right inside the blocks! Very easy.

### content/index.md

Let's change the _index.md_ to use this new template:

```yml
+++
title = "First Zola page"
template = "blog-page.html"
+++
# Hello world!

My first zola page.
```

Just by changing the `template` to match our new template, we restored the page at `http://127.0.0.1:1111/content/`!

However, that page is not in the "right" position. That is, zola can use it this way, as shown, but usually inside the `content/` folder are all the pages and sections that have to be rendered.

So let's tidy up our site.

### content/blog/

Inside `content/` we create a `blog/folder`

Inside it we move our formerly `content/index.md` and we rename it as `first-post.md`

the content of `content/` folder

```bash
content
   └── blog
       └── first-post.md
```

Now our page is under `http://127.0.0.1:1111/blog/first-post/`

As you can see, there is no mention of `content`: that is because under normal conditions (that is, without a `index.md`), the `content/` folder serves to organize the sections in our site, so that the folders inside it are injected in the site path directly.

Now if you try to use `http://127.0.0.1:1111/content/blog/first-post/` **it will not work**!

### content/blog/_index.md

If we want to have an index inside a section, for example at `http://127.0.0.1:1111/blog/`, we will use a special file `_index.md`:

```yml
+++
title = "List of blog posts"
sort_by = "date"
template = "blog.html"
page_template = "blog-page.html"
+++
```

As you can see, zola will complain because a template does not exist (yet)

```bash
-> Content changed .../zola-helloworld/content/blog/_index.md
Failed to build the site
Error: Failed to render section '.../zola-helloworld/content/blog/_index.md'
Reason: Tried to render `blog.html` but the template wasn't found
```

That is because in our `content/blog/-index.md` we set a `template = "blog.html"` that does not exist, instead `"blog-page.html"`, which does exist, we set under a new variable, `page_template`.

We have also another new variable, `sort_by` which tells zola engine how to sort the blog posts...

### templates/blog.html

Let's create the not yet existing template, `blog.html`

```html
{% extends "base.html" %}
{% block title %}My Blog{% endblock title %}

{% block content %}
<h1>{{ section.title }} </h1>

<ul>
  {% for page in section.pages %}
  <li><a href="{{ page.permalink | safe }}">{{ page.title }}</a></li>
  {% endfor %}
</ul>
{% endblock content %}
```

As you can see, we rendered both the title and the content blocks.

Inside the ```{% block content %}``` we add a `{{ section.title }}` that works as the `page.title`, except that it is set in the `_index.md` of each section. 

Notice that every variables inside that file are also available in each page of the section, if you need them.

Then we have a `{% for %}` command. It is needed to cycle through a collection of items, in this case the `section.pages` which contains all the pages inside the section, but the `_index.md`. It takes them one by one, and assign them to a `page` temporary variable, similar in the form to a Rust `for` command.

The `permalink` variable hold the rendered link of the page, so we use it for the `href` in the `<a>` tag, while we set the content of the link as the title of the page, with `page.title`

We need just to change one thing in the post page to make it work, that is, adding a `date` variable by which to sort the posts.

### content/blog/first-post.md

THis is the final content of of our `first-post.md`

```markdown
+++
title = "First Zola blog post"
date = 2020-06-29
+++
# Hello world!

My first zola Blog Page.
```

We deleted the `template = "blog-page.html"` which is not needed; in fact, we set the template for each page with the `page_template` variable inside the `_index.md`.

We inserted the `date` variable.

At this point the `http://127.0.0.1:1111/blog/` will look like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tjkekq69zxatuw79v34b.jpg)

And our post will be accessible through the link.

### content/blog/second-post.md

At this point we could add another page as easy as writing its content in markdown, and adding the front-matter with `title` and `date` set:

```markdown
+++
title = "Second Zola blog post"
date = 2020-06-30
+++
# Hello world!

My Second zola blog post.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dapibus rutrum facilisis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam tristique libero eu nibh porttitor fermentum. Nullam venenatis erat id vehicula viverra. Nunc ultrices eros ut ultricies condimentum. Mauris risus lacus, blandit sit amet venenatis non, bibendum vitae dolor. Nunc lorem mauris, fringilla in aliquam at, euismod in lectus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In non lorem sit amet elit placerat maximus. Pellentesque aliquam maximus risus, vel sed vehicula.
```

The page has been correctly added.

## Some nice improvements

We could modify the `index.html` to point to the blog section. But first a **style** improvement that I think will be much appreciated.

### templates/base.html

We add a small CSS library in the `<head>`:

```html
<head>
    <title>{% block title %}{% endblock title %}</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/picnic">
    <style>
        .center {
            margin: 0 auto;
        }
        nav {
            position: absolute;
        }
        main {
            padding-top: 3em;
        }
        </style>
</head>
```

We added a nice small CSS library, [Picnic CSS](https://picnicss.com/) from the [jsdeliver.net](jsdelivr.com) CDN.

We added also some custom style to center content, handle the navigation bar and the main 
Now we can use it in our pages.

We start right away inside the same `base.html` giving to all pages a navigation bar, and the same `flex` grid:

```html
<body>
    <nav class="demo">
    <a href="/" class="brand">
        <span>My Site</span>
    </a>

    <!-- responsive-->
    <input id="bmenub" type="checkbox" class="show">
    <label for="bmenub" class="burger pseudo button">&#9776;</label>

    <div class="menu">
        <a href="/blog" class="pseudo button icon-picture">Blog</a>
    </div>
    </nav>

    <main class="flex five">
    {% block content %}{% endblock content %}
    </main>
</body>
```

Our `<nav>` is responsive and links to the `blog/` section.

We give a grid of maximum five columns (we could use up to `twelve`)

We will change in turn all pages in our `templates/`:

### templates/index.html

```html
{% extends "base.html" %}

{% block title %}Welcome to Zola{% endblock title %}

{% block content %}
<article class="four-fifth center">
    <h1>Hello Zola!</h1>
    Visit my <a href="/blog">Blog section</a>!
</article>
{% endblock content %}
```

We added both an `<article>` section occupying 4/5 of the `flex` we set in `base.html`.

We added a link to the blog section too.

### templates/blog.html

Inside Blog we will add picnic's `cards` in stead of a `html` unordered list:

```html
{% extends "base.html" %}
{% block title %}My Blog{% endblock title %}

{% block content %}
<article class="four-fifth center">
  <h1>{{ section.title }} </h1>

  {% for page in section.pages %}
    <article class="card">
      <header>
        <h3>{{ page.title }}</h3>
      </header>
        <div align="right">Published on: {{ page.date | date(format="%d/%m/%Y") }}&nbsp;</div>
        <section class="center">{{ page.summary | safe }}</section>
      <footer>
      <a href="{{ page.permalink | safe }}#continue-reading" class= "button">Read More</a>
      </footer>
    </article>

  {% endfor %}
</article>
{% endblock content %}
```

We got another two variables here:

- `page.date`: we access it directly here, and we use the tera's `date()` filter. Read about it in [tera docs](https://tera.netlify.app/docs/#date).
- `page.summary`: to use it we need to add a `<!-- more -->` inside the content of a page. What is above it enters inside the `page.summary` variable. Mroeover, zola will create a anchor after it with id `continue-reading`, so that `href="{{ page.permalink | safe }}#continue-reading"` links straight to where to continue reading. Very neat.

### templates/blog-page.html

The last template to change, `blog-page.html` will look like:

```html
{% extends "base.html" %}

{% block title %}{{ page.title }}{% endblock title %}

{% block content %}
<article class="four-fifth center">
    {{ page.content | safe }}
</article>
{% endblock content %}
```

That's it!

With few lines more we improved much over the look and feel of our blog.

## Deploying

After we finish to style and tune or blog, it is time to show it to the world!

A static site can be deployed easily everywhere.

Zola can build the site with `zola build`. It creates the site inside the `public/` folder.
From there we can copy it everywhere.

However, I'll show here how to deploy straight on [Netlify](https://www.netlify.com/)

First you need a Netlify account, if you do not already have one. In the [Sign Up page](https://app.netlify.com/signup) you can authenticate with various git repositories (GitHub, GitLab, Bitbucket).

I opted for GitHub, and I'll save the site as a Github repo.

First thing:

```bash
git init
```

In the root of our repo. Then we create a new repository in our GitHub account. I called mine `zola-helloworld` as the zola project.

We will add a `.gitignore` instructing git to ignore the `public/` folder

```gitignore
public/
```

We need to add a `netlify.toml` file as well, to deploy to netlify:

```toml
[build]
publish = "public"
command = "zola build"

[build.environment]
# Set the version name that you want to use and Netlify will automatically use it.
ZOLA_VERSION = "0.9.0"
```

Now we can add the `remote` and commit the current project. In the shell:

```bash
git remote add origin https://github.com/davidedelpapa/zola-helloworld.git
git push -u origin master
```

Oce done, we just need to add a new site in Netlify, and select `GitHub` as the repository, and our newly created repo as the one to target.

Once done, our site is online.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2b14gxrgd5bvi6w1sq9k.jpg)

You can check mine at [https://hello-zola.netlify.app/](https://hello-zola.netlify.app/).

## Conclusions

We just saw the bare minimal of Zola.
Has we have seen, it is indeed a poweful static site generator, and ideal for small/personal sites.

I hope you enjoied it.