hmans.co

Lume is Great and I'm finally at peace.

It was time to relaunch my little blog. It had been time to do this for literally months. This week, I finally set down and got started. Lots of cool new frameworks to try! I was looking for something that satisfied the following requirements:

After the couple of disappointments that I hinted at above, I ended up with Lume, a static site generator build for Deno.

And it's perfect. It literally checks every single one of the boxes listed above.

Batteries Included

Lume is very similar to Eleventy in nature (and, in fact, inspired by it), so it's more focused on emitting website documents than being an "application framework". Unlike Eleventy, Lume made me productive much faster. In Eleventy, I always felt like adding website-typical tooling like Sass was weirdly complicated (but I admit it's been 1.5 years since I last tried it, and it might have evolved since then.)

In Lume, many things are just a small plugin away, many of which are shipped with Lume itself. Adding Sass support was a matter of doing the following in my configuration file:

import sass from "lume/plugins/sass.ts";
site.use(sass());

Lume ships with a PageFind plugin that is just as easy to set up and "just works":

import pagefind from "lume/plugins/pagefind.ts";
site.use(pagefind());

Now just add a <div id="search"></div> to your site's HTML, and you have a fully working full-text search for your site. Amazing.

Hooray Nested Data!

Lume offers a simple, opinionated abstraction of your site: every document you create is a page, and every page has data. Pages can have content, but they can also be just data.

They can be written with Markdown (where their data is expressed as typical Markdown frontmatter):

---
title: Hello World
author: hmans
---

# Hello World!

But you can also write pages as JSX modules, where the default function renders the page body, and you use named exports for data:

export const title = "Hello World";
export const author = "hmans";

export default () => <h1>Hello World!</h1>;

Many other template languages are supported besides these two.

Default data for all documents in the same directory and its child directories can be set via a _data file, which can be authored in Yaml, JSON, or even be made dynamic with JavaScript and TypeScript. I'm using this in my blog to set a default layout and page type for all pages within the posts directory:

layout: post.njk
type: post

When it's time to render a list of all blog posts, I can use Lume's search helper to get a subset of all pages in the site:

{% for post in search.pages("type=post", "date=desc") %}
<p>
  <strong>
    <a href="{{ post.data.url }}">{{ post.data.title }}</a>
  </strong>
  {{ post.data.subtitle }}
</p>
{% endfor %}

ESBuild for the bigger client-side stuff

If you eventually do need to bundle some JavaScript that you need to run in the client, you can add the esbuild plugin, and it will automatically create a bundle from any .js(x) or .ts(x) file in your site. Adding something like Sandpack was extremely straight forward with this.

Here's an example app where React is rendered server-side, but then hydrated in the client. It's cool that this is possible!

I'm also enjoying that in Deno, you can just write your import statement, and the next time you start the app, it will fetch the specified dependency for you (from NPM, too!) and cache it locally. No need to run npm install or yarn install anymore.

Customization

Lume allows you to use the DOM API to process files before they get written to disk:

site.process([".html"], (page) => {
  page.document?.querySelectorAll("img").forEach((img) => {
    if (!img.hasAttribute("alt")) {
      img.setAttribute("alt", "This is a random alt");
    }
  });
});

Creating your own plugins is exceptionally easy, as they mostly just encapsulate things you would otherwise be doing inside your site's configuration file, like the snippet above. It's simple and powerful, and I'm enjoying it a lot.

Conclusion

Lume is making me extremely happy. On Twitter, I called it "a cornucopia of sane design decisions". It's the first time in a long time that I feel like I can build a website without having to fight the tool I'm using to do it. Building this blog with it was huge amounts of fun, and I'm looking forward to using it in more web projects in the future.