Our journey to becoming more static // 8 min reading time

We've ditched the back-end for a lot of our sites recently, including this one! You're reading the Cedita Blog from a completely static site.

A little background to this post is that when it comes to development for the web we tend to favour heavy-handed options, either .NET / node.js on the back-end, or Svelte / Angular on the front end. All developers have their own flavour and believe strongly about their chosen options generally, but that's a tale for another post.

With that said, when it comes to creating our own sites we obviously lent on what we knew best and so most of our websites were .NET applications. We even wrote our own engine for the hosting and optimisation of them - the Cedita Group Site Engine (GSE). This engine handled all of our "static" sites by routing domains to the correct place, handling output caching and optimisation of static assets such as imagery, CSS, and even the HTML output.

The Group Site Engine

The GSE has served us well for nearly 2 years now hosting 9 different sites across a number of our brands, but it comes with all the necessary components that come with both development and hosting of .NET applications in general.

On the development side of things, that's a plethora of NuGet packages, lots of backing C# code to perform routing, more NuGet packages to handle image resizing, output middleware for compression of assets, and our bespoke cache layer that pushed resources to edge nodes.

For hosting we're always running at minimum 2 processes which includes a reverse proxy and the dotnet application host itself. Depending on load and cache-hits, this can result in unnecessarily high CPU and memory usage in order to keep the performance where we would like it. This alone was enough reason to bring forth a change in approach as our core values revolve around high performance at the lowest possible cost (in terms of environmental considerations for energy usage).

As you're reading through this you may have realised that what we had essentially built with the GSE was a big old complex output caching proxy, just to be hidden behind a reverse proxy (which in turn is hidden behind our edge router or load balancer, but we won't get in to that).

The Thought Process

To determine the correct way forward we spoke to our developers and content creators - as a relatively small team this process was quick. To nobody's surprise, the results were vastly different: Developers wanted a way to express their creativity; and content creators wanted the easiest possible way to write their content without needing to understand the technical aspects of it.

They both understood the overall goal of what we were trying to do which was cut down our physical energy load, increase performance of our sites, and improve the iteration process (or developer experience).

Without boring you we eventually shortlisted 3 tools:

  1. SvelteKit - Every developer we've come across falls in love with Svelte. Rather than being a runtime-driven framework, it's instead a compiler meaning the in-browser performance is far outperformed by any others. SvelteKit has the option to generate static output which solves our ultimate aim, however there is very little scope for easy non-technical development as everything would require its own component without some bespoke trickery.
  2. Scully - This pleased a more vocal subset of our development team, they focused on Angular development for clients and so having an Angular Static Site Generator (SSG) was a great way forward. They would be able to use what they know and still reap all the benefits that we were looking for. For content creators, the ability to use Markdown in their posts was a big bonus, though the speed of iteration and local previewing was a problem.
  3. 11ty - We ultimately considered this the best of both worlds. It was simple enough for easy iteration, it was powerful enough for developers to do what they wanted to do, and it was fast enough to keep everyone happy. Its multitude of available template engines allowed our developers to write whatever they wanted to, whilst our content creation team were able to write simple Markdown to create posts or pages on websites.

Using 11ty

Eleventy Homepage Screenshot
11ty Homepage and its Possum

Eleventy is super approachable, defining itself as a simpler static site generator and sporting the possum suspended by a red balloon, a homage to the original logo designer.

It provides everything that you would need to write the majority of website types from a blog (like this!) through to a documentation site through to even content-rich static sites: Nested Layout Support, Navigation, Data Cascading, so many template engines, and perhaps the most important being extensibility. For what 11ty doesn't provide is generally covered by a plugin, either one of which is already in existence or one that you've written yourself.

We actually found a lot of the plugins to be relatively inflexible, so ended up either forking them and customising them or simply writing our own versions in the form of shortcodes, Nunjucks templates to be included or implementing our own filters. We're going to look at releasing most of the useful additions we made publicly in the future for anyone going down the same path.

Getting Started with 11ty

As we said above a lot of what you will initially need with 11ty is provided for you. Because of this we have started to use its simplistic power to rapidly prototype solutions to problems in addition to just replacing our sites, something we used to do with a .NET project or client-side application.

We would like to share a simple example of where we've done that - there may be further articles on 11ty in the future for more complex scenarios.

Installing and using 11ty is initially simple, with you being able to get up and running as simple as this by installing 11ty as a global tool:

npm i -g @11ty/eleventy

Now let's create a barebones "site" using only 2 files in a new directory.

Create _includes/starter.njk with the following:

<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
{{ content | safe }}
</body>
</html>

We are using two simple built-in Nunjucks features here, {{ .. }} to output variable's content, and the safe filter which allows HTML to be output unescaped. This is because your content will be compiled to HTML, especially in the case of Markdown. As it is controlled by you, we can safely assume that it's safe, no pun intended.

Also create index.njk in the root:

---
title: Hello world!
layout: starter
---

Hello I am from Eleventy

The data contained between the --- markers at the top is called Front Matter Data, it doesn't form part of your template's output yet does set data to be used elsewhere. In this case, the title variable is set for use within our layout, and we set the layout which is used by 11ty to point to our starter.njk layout.

To generate your site, let's run:

eleventy

Almost instantly you should see the output file being created in the _site folder with these contents:

<!DOCTYPE html>
<html>
<head>
<title>Hello world!</title>
</head>
<body>

Hello I am from Eleventy
</body>
</html>

Already, you've used 11ty, Nunjucks, and Front Matter Data to generate your static site.

Another step towards reality

Most of the basics guides to be found online will stop here or link off in to other places, so let's take it just another little step further.

Create an about.njk in the root:

---
title: About Me
layout: starter
---

Hello this is about me

Running eleventy at this stage would generate an about/index.html no problem, but let's first update our starter template with some primitive navigation.

</head>
<body>
+ <nav>
+ <ul>
+ {%- for page in collections.all -%}
+ <li>
+ <a href="{{ page.url }}">{{ page.data.title }}</a>
+ </li>
+ {%- endfor -%}
+ </ul>
+ </nav>
+ <article>
{{ content | safe }}
+ </article>
</body>
</html>

We used {%- and -%} here to strip leading and trailing whitespace from the Nunjucks output.

Let's also serve it using the BrowserSync defaults built in to 11ty by running:

eleventy --serve

You'll see a URL output (likely https://localhost:8080) so browse there, and you should have navigation sat at the top of your pages.

Even now, with zero configuration and 11ty you should be able to see the simplicity of development and content creation. Our about.njk for example could very well be about.md, and you are be able to use Markdown instantly.

To take this a little further we would recommend you start by taking a look at controlling navigation with more granularity - see the Eleventy Navigation Plugin. This involves adding more front matter to your data, giving you further options for navigation customisability than simply iterating through all of your pages.

More than Performance

Speaking about Eleventy just from a performance context is unfair, as static sites generally can't be "performant" from a typical standpoint of execution time as there is nothing to execute. Eleventy is however higher performing on the build side of things than most other generators we tried, with incremental rebuilds of individual pages as you work on your content. This is something that you have to opt-in to, which is as simple as adding a new command line paramater.

- eleventy --watch
+ eleventy --watch --incremental

A lot of the benefits come from everything simply being a template file in a repository. This is great for developers who are used to working with such a system day-to-day and works for content creators by adding on a headless CRM (such as Forestry) that commits directly to repositories.

The ugly

We've praised 11ty and learned to love it internally across all of our teams but that isn't to say that there aren't any strange components to it. Overall, these do not come close to outweighing the benefits.

Extensibility leads to potentially large configuration

The way that 11ty is designed means that it is extremely easy to configure and extend, however it all (generally) takes place in one file which is the root of your project .eleventy.js. In fairness this can be considered a + and a - for the platform as it means any configuration related issues are in one place, however in our experience depending on the amount of plugins and / or filters you are working with at any time, it can become quite a huge file.

Let's look at a sample configuration from a plugin so you can imagine it being repeated several times in the same configuration file, with varying levels of complexity.

eleventyConfig.addPlugin(
require("eleventy-plugin-ignore"),
{
// template ignored if function returns true
ignore: (data) =>
data.ignore ||
(data.draft && NODE_ENV === "production"),
// check all templates ending with these extensions
templateFormats: ["html", "liquid", "md", "njk"]
}
);

This blog's configuration file, for example, is over 350 lines long.

Data Cascade can be strange

In our Nunjucks templates (specifically extended layouts) we expected behaviour that seemed to be missing. Perhaps we don't understand it correctly, or perhaps it's simply not designed for this in the first instance however consider the following basic template layout:

  • layouts/base.njk (Our base layout for all pages)
  • layouts/article.njk (Our sub-layout for articles)

In Nunjucks we use {% set variable = 'value' %} to set things, which gives us a bit of power as a whole block can be used for example, with the output of an {% include .. %}. This concept generally works, however when we used this in our article.njk the variable did not reach the base template.

To work around this, we used 11ty's Computed Data to inject the variable as we wanted based on the content we needed:

const slugify = require('slugify');

module.exports = {
eleventyComputed: {
jsonDataFile: data => {
if (data.jsonDataFile) {
return data.jsonDataFile;
}
return 'root-data/' + slugify(data.title).toLowerCase() + '.json';
},
}
}

This was notably strange to us as a set within an actual article did propagate down (or up, depending on how you look at it) to the layouts. We do believe that it is simply order of execution of the extended layout files specifically.

Getting started off a template can be difficult

When we were first playing around we looked at templates that have achieved similar to what we were looking to do. There are a number of templates available for 11ty projects yet the version used can wildly vary, this means that some things such as renderData are still used in templates which can cause confusion over what they were replaced by.

We do feel that a more up-to-date list of available templates would help newcomers in to the 11ty ecosystem with a lot less friction.

Summary

In summary, we fell for 11ty quite hard after trying it out in a number of different internal scenarios and we are even recommending it to our clients depending on their requirements.

The ease of getting started, as well as the ongoing development/content creation experience and of course the benefits of static sites in general mean that it's almost a no-brainer for a lot of simplistic projects.

So for a lot of our sites we have said goodbye to the Group Site Engine and said hello to 11ty.