DEV Community

Cover image for Zola Tutorial: How to use Zola the Rust based static site generator for your next small project, and deploy it on Netlify
Davide Del Papa
Davide Del Papa

Posted on

Zola Tutorial: How to use Zola the Rust based static site generator for your next small project, and deploy it on Netlify

(Photo by June Wong on Unsplash)

Demo: https://hello-zola.netlify.app/

Repo: https://github.com/davidedelpapa/zola-helloworld

Zola (by Vincent Prouillet) is a static site generator, just like Hugo or Gatsby.

It is written in Rust and it uses the Tera 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

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

mv zola ~/.local/bin/zola
Enter fullscreen mode Exit fullscreen mode

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. I got mine from standard repos (for Debian/Ubuntu)
sudo apt get install libsass-dev
Enter fullscreen mode Exit fullscreen mode

Clone and Build

Clone the git repo and cargo build --release.

git clone https://github.com/getzola/zola.git
cd zola
cargo build --release
Enter fullscreen mode Exit fullscreen mode

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:

mv ./target/release/zola ~/.local/bin/zola
Enter fullscreen mode Exit fullscreen mode

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:

zola --help
Enter fullscreen mode Exit fullscreen mode

The help screen:

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
Enter fullscreen mode Exit fullscreen mode

Zola HelloWorld

We will first create and init a new project

zola init zola-helloworld
Enter fullscreen mode Exit fullscreen mode

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

> 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]:
Enter fullscreen mode Exit fullscreen mode

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

cd zola-helloworld
Enter fullscreen mode Exit fullscreen mode

We are presented with a structure like this

helloworld
├── content/
├── sass/
├── static/
├── templates/
├── themes/
└── config.toml
Enter fullscreen mode Exit fullscreen mode

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

Let's take a look at its content:

# 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
Enter fullscreen mode Exit fullscreen mode

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:

zola serve
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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.

<!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>
Enter fullscreen mode Exit fullscreen mode

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 or 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:

-> 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 `+++`?
Enter fullscreen mode Exit fullscreen mode

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

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

My first zola page.
Enter fullscreen mode Exit fullscreen mode

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

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.

<!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>
Enter fullscreen mode Exit fullscreen mode

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

{% extends "base.html" %}

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

{% block content %}
<h1>Hello Zola!</h1>
{% endblock content %}
Enter fullscreen mode Exit fullscreen mode

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

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.

{% extends "base.html" %}

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

{% block content %}
{{ page.content | safe }}
{% endblock content %}
Enter fullscreen mode Exit fullscreen mode

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:

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

My first zola page.
Enter fullscreen mode Exit fullscreen mode

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

content
   └── blog
       └── first-post.md
Enter fullscreen mode Exit fullscreen mode

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:

+++
title = "List of blog posts"
sort_by = "date"
template = "blog.html"
page_template = "blog-page.html"
+++
Enter fullscreen mode Exit fullscreen mode

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

-> 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
Enter fullscreen mode Exit fullscreen mode

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

{% 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 %}
Enter fullscreen mode Exit fullscreen mode

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

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

My first zola Blog Page.
Enter fullscreen mode Exit fullscreen mode

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

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:

+++
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.
Enter fullscreen mode Exit fullscreen mode

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>:

<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>
Enter fullscreen mode Exit fullscreen mode

We added a nice small CSS library, Picnic CSS from the jsdeliver.net 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:

<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>
Enter fullscreen mode Exit fullscreen mode

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

{% 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 %}
Enter fullscreen mode Exit fullscreen mode

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:

{% 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 %}
Enter fullscreen mode Exit fullscreen mode

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.
  • 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:

{% extends "base.html" %}

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

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

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

First you need a Netlify account, if you do not already have one. In the Sign Up page 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:

git init
Enter fullscreen mode Exit fullscreen mode

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

public/
Enter fullscreen mode Exit fullscreen mode

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

[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"
Enter fullscreen mode Exit fullscreen mode

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

git remote add origin https://github.com/davidedelpapa/zola-helloworld.git
git push -u origin master
Enter fullscreen mode Exit fullscreen mode

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

You can check mine at 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.

Top comments (5)

Collapse
 
bthooper profile image
Bryan Hooper

I realize this post is a bit old, but I have a question that is outside it's original scope anyway...hoping you can help or point me in the right direction.

I would like to have a "shortcode" that generates an image gallery with a caption under each image. I have figured out the html and css side of things, but I can't figure out how to pass data into the shortcode for this. I suspect this is becuase I am ignorant of Rust, but the Rust documentation didn't help me.

I was hoping I could have some kind of data in the [extra] block of the page that simple contains the image file name and the text of the caption. Then, I would call my gallery() shortcode somewhere in the page and it would plug the data in at the right places...iterating through each image file name/caption combo.

Typically I would use an object for this in something like JavaScript or Ruby but Rust doesn't seem to work that way since it is a lower level language. Any thoughts?

Collapse
 
chaostheorie profile image
Cobalt

My reply is unfortunately quite late. You can do this in multiple ways. Either have a file, that contains captions and paths (JSON, YAML …), somehwere in your project that can be read with load_data (see zola docs) or define an array with links to files and captions as an argument for the shortcode (see the Tera docs (used template engine) and the Zola docs for more details)

Collapse
 
bthooper profile image
Bryan Hooper

Better late than never! Thanks. I will give those suggestions a try. I moved on to other projects...but I should get back to this one about now!

Collapse
 
id027102 profile image
id027102

I try it to day and after (1st Page) creating index.md in content , I get

Error: Failed to serve the site
Error: We can't have a page called index.md in the same folder as an index section in "........../zola-helloworld/content"

Renaming index.md to _index.md solve the problem but then I get :

Error: Failed to serve the site
Error: Failed to render section '............/zola-helloworld/content/_index.md'
Error: Reason: Failed to render 'base.html'
Error: Reason: Variable page.title not found in context while rendering 'base.html'

All done with copy/paste from this site

If I remove page.site from base.html then I have page.content not found

Any idea ??

Collapse
 
davidedelpapa profile image
Davide Del Papa

Which version of Zola are you using? It seems there was a bug in version 0.17