(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
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
Clone and Build
Clone the git repo and cargo build --release
.
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
:
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:
zola --help
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
Zola HelloWorld
We will first create and init a new project
zola init zola-helloworld
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]:
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
We are presented with a structure like this
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:
# 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:
zola serve
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.
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>
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.
- The first directive,
{{ page.title }}
, renders the page title. - the second directive,
{{ page.content }}
, renders the page content. We add thesafe
filter to render properly thehtml
generated, otherwise the content will be render as ahtml
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 `+++`?
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.
As you can see, the part between +++
is a yaml
section, containing variables.
-
title
will be used to render thepage.title
we put in thebase.html
template. -
template
tells which template to use. - After the
+++
section there is the actual page content, which will be stored inpage.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.
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>
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 ablock
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 %}
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 thebase.html
. - Similarly,
{% block content %}<h1>Hello Zola!</h1>{% endblock content %}
will substitute the{% block content %}{% endblock content %}
insidebase.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
)
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 %}
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.
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
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"
+++
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
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 %}
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.
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:
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.
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>
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">☰</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
{% 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:
{% 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") }} </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'sdate()
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 thepage.summary
variable. Mroeover, zola will create a anchor after it with idcontinue-reading
, so thathref="{{ 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 %}
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
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/
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"
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
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.
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)
Which version of Zola are you using? It seems there was a bug in version 0.17
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?
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)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!