Adventures in generating static sites

Static site generators seem fairly popular these days, and as a result there is a lot of them to pick from. The go-to place for a list is probably static-gen.com, and that fairly long list does not even include generators not hosted on Github (of which there may or may not be some).

This site is statically generated, and so in order to get here, I had to dive into the world of static site generators and emerge with one which suited my needs.

Blogs and not-blogs

A lot of static site generators out there are essentially static blog generators. This generally involves having a bunch of posts which can be sorted chronologically and displayed in sequence on the output pages. Sometimes, there are features like tags or categories, which allow identifying some subset of posts to display on a page dedicated to that particular tag or category. Pages are also often a feature—these are usually a special kind of a post which lives outside of the main list, and can be referenced in a navigational menu or something similar.

Another group of static site generators is designed around providing a more generic framework for building static websites. These still separate content from templates, but instead of enforcing a set taxonomy allow the user to define their own. Blogs are still possible with this way of doing things, but require explaining to the software what a blog is. The upside is more possibilities in generating non-blog things.

Lektor

Lektor is a static site generator from the latter category. Written in Python, it uses Jinja2 for templating, and has a plugin system for extending functionality.

Lektor has a set of features common to static site generators of its ilk: it expects content in plain text files, tries to be smart about not re-generating already generated stuff, and offers a local server with dynamic reload for quickly testing your site. An unusual feature is its admin panel: when running the local server, Lektor also runs a nice UI which allows you to create, edit, and delete content files from within the browser—a feature common to dynamic content management systems, but usually not found in static generators.

How building sites with Lektor works

Lektor manages to strike a nice balance between flexibility and complexity. In order to build a website, one has to define some models, some templates, and then use them with some content.

Models

Defining models involves creating some ini files. These tell Lektor what fields a particular type of content should have—fields like title, body, or publish date. One can also do things like define default subtypes of children, or set pagination and sorting for said children. A trivial example:

[model]
name = Blogpost

[fields.title]
type = String

[fields.body]
type = String

Templates

Templates use Jinja2. A template gets an object representing a content element; the fields of the object can be used in the template. A template generally goes with a model (or multiple models), so those are the fields that we know we can use. A template for the model above could be this:

<article>
<h1>{{ this.title }}</h2>
{{ this.body }}
</article>

Content

Finally, Lektor will need some content—entries which use a particular model and fill out its fields. This is where it gets a bit odd: while other, similar systems tend to use something like YAML or JSON prepended to the main content, Lektor uses fields separated by ---:

title: Hello, world!
----
body:
Hello, world! This is my first blog post!

Lektor does not assume there is a default, main content field, and so the body field is just like any other the model has.

In practice

Lektor aims to be simple, without sacrificing flexibility. It is capable of doing fancier things, beyond the basics outlined above, but the basics themselves are, indeed, simple. This is one of the nicer things about Lektor: it is more powerful than the simpler blog generators, yet does not require too much setup and fiddling.

There is some nice flexibility to Lektor. It has a plugin system to extend functionality, and the templating system itself is fairly powerful as well. The templates can also use the API that Lektor provides to, for example, query for arbitrary content. For a more structured approach, there is a flow mode, which allows gluing together blocks which use different models—essentially creating a page which incorporates arbitrary sub-pages with their own content and templates.

There are some downsides to Lektor, though. For one, the admin UI does not seem to be geared towards managing a large amount of subpages, such that would be present in a long-running blog. The content folder also requires every subpage to be its own subdirectory, with a file named contents.lr inside for the actual content, which may be seen as a bit verbose. Additionally, the recommended way of automating running Sass seems to be to do so via a Webpack plugin, which is a bit of a heavyweight solution, especially considering the fact that I have no need to process any Javascript.

Another complaint, which is likely to bother some people more than others, is the installation process for Lektor. The recommended way is the dreaded curl | sh, and installing Lektor to a Virtualenv via Pip is discouraged due to the way Lektor manages its own plugins.

Alternatives

Of note is also some other, similar software I looked at before settling on Lektor.

Hugo is a fairly popular, non-blog oriented static site generator written in Go. It is more complex than Lektor, and claims to be fairly fast. Another interesting project is Statik, which is even more build-your-own-model than Lektor, and puts ORM-like queries front and center (Lektor has something similar, but it essentially an extra feature and not the main way of doing things).

Conclusions

All in all, though, I was able to get going with Lektor with relative ease, and did not need to sift through too much documentation to figure out how to do some of the less standard things. In an area with a lot of choices available, it is a piece of software that managers to meet my particular requirements, and does so quite well.