Moving from Gatsby to Eleventy

Moving from Gatsby to Eleventy

Building something comes with choices, often lots of them. Hopefully most of these are successful and meet your expectations but there will inevitably be some choices where, given the opportunity, you'd like to change your decision. For me, Gatsby was one of those choices.

Last year, I decided to rebuild my site with Ghost, using Gatsby as a static site generator. Part way through this rebuild I started to feel like Gatsby might not actually be the right choice for me, until I eventually decided to find an alternative. I've since moved over from Gatsby to Eleventy and so far, feel far happier with what I've ended up with.

So, what were my frustrations? In this post, I'm going to go over my experiences with both Gatsby and Eleventy. This post is not a comprehensive walkthrough of either tool, and crucially only reflects my opinions – your mileage may vary.

My aims

Back at the start of 2020 I was looking for a new tech stack for this blog. My aim being to end up with a stack that allowed me to write more, iterate, learn and experiment.

This tech stack shouldn't uncontrollably hijack the code that gets delivered to browsers. CMS's are notorious for inserting a lot of unnecessary additional markup onto the page ("divitis"), whilst a lot of frontend libraries and frameworks will often result in serving a lot of unused code – prioritising developer experience over user experience. I wanted to retain enough control that I could find the right balance between developer and user experience.

Decisions rarely come without compromise. With that in mind, it was important that this stack be fluid, evolving over time without requiring a full rebuild each time.

Initial tech stack choices

I settled on using Ghost as a headless CMS, with Gatsby and React to generate a static site hosted with Netlify.

Brand logos for Ghost + Gatsby + Netlify
Initial tech stack including Gatsby

Server-side rendering and static site generation

A major problem with React, and all other JavaScript frameworks, is that the default way of using them will result in sending essentially empty markup to the browser, before then loading a whole load of JavaScript which has to be parsed and then render the page. Our shiny frameworks are undoing all of the semantics, performance, accessibility that we'd usually get for free with our HTML and CSS.

Increasingly, JavaScript frameworks are now offering server-side solutions to work around these problems. For example, React provides ReactDOMServer for rendering React code on a Node server and a hydration option that can take this pre-rendered markup and attach all the event handlers that allow React to takeover once in the browser. Instead of delivering empty markup you can serve complete HTML and CSS, regaining some of the semantics, performance and accessibility that you'd otherwise be losing with a JavaScript framework.

Over the last few years, a number of tools have come along which expand upon this functionality, with Gastby and Next.js being particularly popular options. These tools generally have two different ways of working, server-side rendering (SSR) or static site generation (SSG). Server-side rendering requires a Node server which runs your JavaScript on a server on-demand, whereas static site generation prebuilds a HTML file for each and every page on a site. With both approaches, your framework is then loaded in the browser and extra utilities like client-side navigation bundled in.

The idea of using a server to create markup is hardly a new solution – dozens of languages such as PHP have been doing this for decades. The only real difference in doing so with React is that it enables you to reuse the same code on the server as you do in the browser. This is known as isomorphic React.

Why React?

This is a simple blog, so why even use React in the first place? I'm a big fan of React, using it extensively outside of this site, but I'm also fully aware that a blog is hardly the most complex of JavaScript applications and shouldn't require it.

At work we're part way through moving to a full React stack including server-side rendering and hydration using Next.js, so using React on this site gave me some familiarity making it easy to follow a component-led approach. But equally, it gave another way of exploring using React a hyration-based setup.

Why Gatsby?

Given I'm already using Next.js elsewhere, I thought this could be a good opportunity to try out Gatsby, one of the most popular alternatives.

With Next.js, I've mainly been using its server-side rendering capabilities whereas Gatsby is used for static site generation. Part of the reason for chosing Next.js over Gatsby elsewhere came down to concerns over how well static site generators would work when they reach a massive scale. This site is pretty small, so should be ideal for generating into a static site.

Gatsby prides itself on a fast developer experience and fast user experience, and has a big community behind it so felt like the ideal tool to experiment with to compare against Next.js.

Finding limitations

At first, all seemed to go well but over time I started to notice a few limitations.

Plugin driven development

One of the strengths of Gatsby is the massive development community behind it. Gatsby has a huge plugin library with thousands of plugins available. That tends to mean that when you come across a problem to solve you'll find that not only has someone else already solved this, but someone has created a plugin to help you. In fact, Gatsby see this as such a selling point that they promote plugins on their homepage second only to performance.

Gatsby homepage including the text "Create blazing fast websites and apps AND harness the power of 2000+ plugins
Gatsby homepage in January 2021 with an emphasis on the large library of plugins

Whilst that's great, it does tend to then mean that most solutions end up requiring adding more dependencies to your project and stitching together multiple plugins to get something working. After a while it starts to feel like you're spending more time configuring plugins than doing actual development – plugin driven development.

Each plugin comes with it's own opinions on how to solve the problem you're working on, and may come with many more features than you require. The end result being you've got a site which doesn't quite solve things they way you'd like them solved, all whilst bloating up the size of JavaScript you're including on the page.

This reminded me a lot of working with jQuery plugins. jQuery plugins were a great way of rapidly building up a page but also ended up adding a lot of bloat to the page.

Images

Images became my main frustration. I wanted all images on the site, including ones within the content of a post, to be optimised and displayed using responsive image markup.

To get started, I used the gatsby-image plugin. This is a fairly high-level plugin which gives you an <Img /> component which can often be used as a replacement for a standard <img />. Whilst configurable, it's also fairly opinionated coming with its own interpretation how to display an image. At the time, it wasn't possible to use loading="lazy" to enable native image lazy loading and instead had to use its own JavaScript-based solution.

As I didn't feel comfortable losing control over my image markup, I ended up writing my own markup and stitching together a number of other plugins to handle the resizing of images and replacing images within post content.

This proved brittle, with extra dependencies to maintain and problems with different versions breaking the functionality. Whilst I could've built the functionality myself and bypassed the need for the high-level plugins it ultimately felt like the approach being recommended by Gatsby pushes for plugins first, custom development second.

Slow and fragile builds

Once I'd got a working solution for my images, the next challenge became slow and fragile builds. I seemed to be spending more and more time frustratedly waiting for builds to happen. Partly, this was inevitable due to wanting to include a fair bit of image optimisation but still if a site with a dozen pages is slow to build that doesn't give me much faith for how well the system will scale out.

Most frustratingly of all, these builds were also fragile. Most of the attempts to improve build performance required using caching, which frequently resulted in builds that either didn't include my changes or would feature broken images.

A bit of research highlighted that I wasn't the only one facing build problems, including a now infamous Twitter thread by Nat Alison which shows that build times were just the tip of the iceberg when it came to problems.

Keep things simple

Seeing as I was almost finished, I thought I'd keep going and then review my options. Taking a look at my completed site, it felt a bit more complex than I'd hoped for a first iteration and a developer experience that was already compromised.

It wasn't slow, especially with client-side navigation, but there was definitely a noticeable initial cost to downloading the JavaScript that this required. I started to question both Gatsby and using React. If the page itself was light enough, a new page request should feel as fast as a client-side page navigation.

Moving to Eleventy

Whilst my experience with Gatsby didn't find anything earth-shatteringly wrong, it did feel like the wrong tool for me. So, based on my experiences I decided to look for an alternative.

There were two paths I explored, the first being to keep going down the React path but using Next.js instead of Gatsby. The second option was to double-down on the static site generation aspect of my tooling and use a tool that focuses pruely on that, and not on then hyrating a client-side React application as well.

The Next.js option was tempting as it has proven to be a mature solution at work that was very flexible and not too opinionated, whilst the built-in image solution is far closer aligned to what I was looking for. Next.js would still hydrate in the browser and support client-side routing but it was also possible to disable this entirely to remove the client-side runtime to no longer use React in the browser.

In the end, I decided it would be more interesting to try something different and stick to the ethos of keeping things simple. I'd been hearing a lot of people having success with Eleventy, which is a static site generator similar to Hugo and Jekyll used for taking templates and transforming them into HTML.

Whilst Eleventy, Hugo, Jekyll, Next.js and Gatsby may all do static site generation they fall into two distinct groups. In the first group, Next.js describes itself as "The React Framework for Production" whilst Gatsby uses "One Front-end to Rule Them All". Both are JavaScript frameworks where static site generation is just one of many features.

Screenshot of hero text on Next.js homepage describing itself as the React framework for production
Screenshot of the Next.js homepage in January 2021
Screenshot of hero text on Gatsby homepage stating one front-end to rule them all
Screenshot of the Gatsby homepage in January 2021

Meanwhile, Eleventy describes itself as "a simpler static site generator". Eleventy, Huge and Jekyll all fall into the second group of tools which focus purely on static site generation without adding in any other extras or opinions.

This aligned nicely with what I was looking to do, so I ditched React and Gatsby, and started working on migrating over to Eleventy.

Brand logos for Ghost + Eleventy + Netlify
New tech stack with Eleventy

Picking a templating language

One of the first choices to make with Eleventy is which templating language to use. It supports a range out of the box, including JavaScript, Markdown, Nunjucks and Liquid and you can use a mix of them if you so desire. I started out using Nunjucks, but came across a few limitations with async logic within macros which led me to settle on using Liquid instead.

This migration meant moving my React components to Nunjucks, and handling the data flow from Ghost into Eleventy but otherwise I was able to keep most of the setup the same with only very minimal changes to markup and styling during this move.

Images

After my experiences using Gatsby I paid particular attention to how Eleventy would handle images. Eleventy has a first-party plugin for handling images which is far lower-level than gatsby-image. Instead of giving up control over of my image markup, eleventy-image will handle resizing and transforming images and return JSON data which I can then use to construct image markup as I choose. If you'd rather, you can let it also build the HTML for you. If you do, it's far less opinionated with the only requirement being to provide an alt attribute – which is the kind of opinion I can get onboard with!

For images within content, which is markup generated by my CMS, I'm using rehype to find images and pass them through eleventy-image, transforming them into full responsive markup.

My experience with the image plugin so far seems representative of other first and third party Eleventy plugins too. There are a lot less of them than with Gatsby, likely down to it being a simpler tool, having a smaller community and less emphasis on plugin development. The plugins I've used so far have all been low-level utilities, exposing filters or helpers that make life easier rather than creating a mess of plugins to join together or adding weight to the page.

Performance

Eleventy builds have been fast and reliable, with no strange caching behaviour to contend with. Image processing has added some time to the builds, although there is more I could do to optimise this if it starts to become frustrating.

In terms of front-end performance, navigating between static HTML files feels really fast with Eleventy itself adding no CSS or JavaScript of it's own. I've used Parcel to build my CSS and JavaScript, which is nice and lightweight too.

Looking forwards

So far I'm really happy with Eleventy so plan on keeping it as part of my tech stack, it's both enjoyable to work with and produces clean output with a good user experience.

I do now struggle to see myself graviating towards Gatsby for any future projects. It feels like Next.js is the more mature framework for working with React on the server for both small and large projects, and still lets me work how I'd like to work within my codebase. Whereas Eleventy is ideal for a simplier, content-heavy site like a blog where React doesn't add that much value.

That isn't to say that Gatsby doesn't have it's place. It may not be for me, but I can definitely appreciate why it appeals to a lot of people. It provides a lot of functionality and a lot of developer convenience for working with React in a similar way to how jQuery provided a lot of developer convenience for cross browser JavaScript which equally made it very appealing.


Cover photo by Agê Barros on Unsplash.