Home

Eleventy brings me joy

2023-12-14

Background

In the olden days, I had a completely custom site written in classic ASP and kept it up for many years, until social media platforms became my primary outlet for sharing thoughs online. I still built the occasional website for various endeavors, but moved gradually to more ready-made systems like Wordpress and Tumblr. It had been a very long time since I last had a personal website when a few toots on Mastodon inspired me to start blogging again.

A false start

A few years ago I built a very basic site for documenting my home brewing using Hugo, and liked the concept. Hugo is a static site generator - a tool that transforms content such as Markdown files into fully-fledged web pages by merging them with templates dictating layout and design. In certain generators the content can be any form of structured data, even sourced from APIs or databases on the fly, which makes maintaining a large number of pages very manageable without a lot of tedious manual work.

In my opinion the biggest benefit of static site generators lies in the separation of content and presentation. This allows for updating and improving (or even fully replacing) the design and layout of a site without the need for a messy content migration. Using an basic, open format like markdown also means there's no content lock-in effect. If you're unhappy with one tool you can easily move to another.

I like the simplicity of Markdown and find the concept of static site generators appealing, so I decided to use Hugo for my new blog. I quickly installed it, created a new site using the included scaffolding tool, added what seemed to be the default theme, and started building my site.

I made some minor tweaks to the theme by adding a small CSS file of my own and customized a few of the theme files to better suit my needs. Then, I began blogging. I had a lot of fun; writing was easy, Markdown suits me well, and I received some positive feedback on Mastodon that encouraged me to keep going.

However, alongside this growing joy of writing, I found myself increasingly dissatisfied with almost every aspect of the site's design, and visiting it just made me disappointed.

Since the writing was so enjoyable, the logical next step was to create my own theme to make sure the output was equally pleasing. Hugo comes with a scaffolding tool for creating new themes as well, but all these structures and tools were starting to get to me. When I saw the large default file structure for a new theme I simply had enough and started looking for a better way of doing things.

Hello Eleventy

There are a whole bunch of different static site generators out there, but I kept seeing recommendations for Eleventy, which sparked my interest, and after reading a bit about it it certainly seemed like a good choice for me as well. It's popular and actively being developed, it's simple and flexible, and it's Node-based. A popular tool increases chances of finding both good information and technical assets like plugins. Its simplicity and flexibility particularly appealed to me after my falling-out with Hugo. Plus, I'm already very comfortable with the Node environment.

Eleventy is perhaps the least opinionated tool I've ever used; it doesn't require any particular file structure, it supports a long list of templating formats out of the box (HTML, Markdown, WebC, JavaScript, Liquid, Nunjucks, Handlebars, Mustache, EJS, Haml and Pug, to be precise), and you can add more if needed. You can even mix and match formats within the same site to suit your needs.

When you create a site with Eleventy, it doesn't come with any pre-installed themes. In fact, themes aren't really part of the Eleventy vocabulary, which feels like a big relief for me. Instead of themes Eleventy has layout templates. They're special templates used to display information from the other templates in the system, and can use any format you like, but a common convention seems to be using Markdown for content and Nunjucks for layout. Layouts can be chained, which means a layout can have a parent layout, for example like this:

posts/post.md:

---
title: My Post
layout: post
---

This is my content.

_includes/post.njk:

---
layout: main
---

<h1>{{ title }}</h1>
<article>
{{ content }}
</article>

_includes/main.njk:

<html>
...
<body>
{{ content }}
</body>
</html>

First the content template post.md points to post.njk and get wrapped in that. That layout template then points to main.njk and the output from the first step becomes the content for the second step, and so on. If you only have one type of content you can of course use a single layout template for that, but it's a very nice way of dividing a page structure into layers. Anyways, the end result of the above example would look like this:

<html>
...
<body>
<h1>My Post</h1>
<article>
<p>This is my content.</p>
</article>
</body>
</html>

The <p> element surrounding the innermost content comes from the built in conversion from Markdown to HTML, but the rest is just a combination of the two layout templates and the content. As you may have noticed there's also a header block surrounded by ---. This is called the front matter, and it's the place to put metadata about the content. You can put any information here, and it will automatically be available to any layouts used etc. This data is also merged "upwards" in the layout chain, so that partial layouts like the ones above can add data to the input from the underlaying template. Very convenient.

New beginnings

So, armed with a new, lightweight technical setup, I moved all my Markdown files over from Hugo and started working on a brand-new version of this site. The default front matter formats differ slightly between Hugo and Eleventy, but that was easily adjusted. Now that I've learned a little bit about Eleventy, I know I could have adapted the front matter parsing (which uses gray-matter under the hood and allows for a lot of customization) to fit the Hugo format instead, but it really wasn't a big deal. The rest of the content was usable, but Hugo's link syntax had to be replaced with regular paths.

I also decided to write my own layouts rather than using a starter project or copy-pasting some "best practice" template. Before I started, I made up some half-conscious principles for the site:

I wrote a minimal structure for the framework around the content, the "page chrome" if you will, using as descriptive markup as possible:

<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <header>...</header>
    <main>
        <article>
            <h1>Title</h1>
            <p>Content goes here.</p>
        </article>
    </main>
    <footer>...</footer>
  </body>
</html>

It's clean, it's clear, and to my delight there's not a single <div> on the entire site. I don't like divs, and I'm very happy to just use semantic markup and a touch of CSS to style it. Some other things that this site lacks are JavaScript, analytics/tracking, CSS frameworks, responsive design, and custom fonts (actually, I specify sans-serif as font in my CSS and just let go). It wasn't until I realized I wanted a favicon that I added a funny picture of myself to the site, and other than the HTML document itself that's the only file that is loaded (unless you're reading a post that has images of its own, that is). This typically leads to loading times of around 100 ms, which I'm very happy with.

It turns out that when you write clean and descriptive markup like this, you score really high on the Lighthouse tests as a bonus. A few minor tweaks here and there, and I found myself getting a perfect 100/100 score for all four main categories (Performance, Accessibility, Best Practices, and SEO) for both Desktop and Mobile. I'm well aware of the fact that not all websites can be this basic and/or minimalistic, so I'm not too smug about it, but it feels great to get a perfect score like that. The lack of "deliberate" responsive design is also satisfying. Again, with a basic layout like this, everything just works. I added a small padding to the body tag (which also benefits larger screens) and that was it for my mobile adaptation.

I've got the above markup divided into two layout templates: one for posts and one for pages, and one additional main layout that wraps around either type of content. There really isn't anything else to it; just write a few text files and you're good to go.

Tooling

The main Eleventy command npx @11ty/eleventy performs the actual static site generation. It also accepts a --serve argument, which starts a small development server that rebuilds the site and reloads the browser on relevant file changes. I added a few shortcuts to my package.json like this:

...
"scripts": {
    "dev": "npx @11ty/eleventy --serve",
    "build": "npx @11ty/eleventy",
    "clean": "rm -r _site"
},
...

When I sit down to write or work on the site I just run npm run dev and the development server is automatically started. The live reload is very convenient both for tinkering with the site and for writing text. Even though many editors have live preview for Markdown, it's nice to be able to switch over to the actual site and see what your work in progress will look like. I store the entire site in a Git repository both for backup and for deployment.

Publishing

Due to unforseen circumstances with my regular go-to hosting service DigitalOcean App Platform I decided to publish my site on Netlify. They're similar in that they both provide hosting solutions with continuous deployment support. This means you can connect your service of choice to your Git repository, and it will build and deploy a new version of your site for every commit you make to a specific branch. Netlify is a bit more advanced than DigitalOcean in that they can also deploy a private version of your site for essentially any commit or pull request, so that you can preview the result before going live. I've configured it to deploy to a private URL for my main branch, while a separate "production" branch updates the public site.

Configuring the site on Netlify was a breeze. I granted Netlify (limited) access to my blog repository, and it automatically picked up both the build command from my package.json and the default _site output folder used by Eleventy. Sweet. A few seconds later I could access my site online, albeit through a temporary hostname. Once I was happy with the site, I pointed my domain name to Netlify as per their instructions, waited for DNS records to propagate and then for a SSL certificate to be generated, and then it all just worked.

Having a Continuous Deployment service like this ties in really well with my Git-based workflow, and even allows me to push a small change to the live site right from my phone (using Working copy on iOS as my Git client) if I want to.

Joy

Now that this new site is live, I can happily say that it genuinely puts a smile on my face. I sometimes open it on my phone just because it feels good. I'm glad that I took the time to write the entire layout properly from scratch, and I'm pleasantly surprised by how little time it actually took. Eleventy has been fantastic to work with. Despite handling some pretty advanced tasks, it has never felt complicated to me. The Eleventy community seems to be a very friendly and helpful one, and it's been easy to find answers to my questions. I'm eager to give back, and I guess this post is my first small step in doing so. I have another Eleventy-themed post coming, describing some of the customizations I made to have things just the way I like them. It's these small, yet significant, details that ensure the site truly brings me joy.