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:
- Can render the site out to 100% fully static HTML, with no server or cloud functions required. This one is important to me because I need to know that I can throw my blog on any host that can serve static files. Some static sites I deployed almost a decade ago are still online today, for free, with no work required on my side, and I think that's awesome, and find it very relaxing. I wouldn't feel this way if the site required to live on, say, Vercel (and I'm saying this as someone who makes heavy use of that platform.)
- Can render a static RSS and/or ATOM feed with the full HTML bodies of my posts. I was surprised how often this was simply not possible to do with the new generation of website frameworks. I was even more surprised how often people eventually just asked me who, in 2022, would even care about RSS feeds. You may not care about RSS feeds, but I do — and in my opinion a website framework that simply can't render out non-HTML content is, at best, a toy, but certainly not an adequate tool for building a website. (Okay, I'm getting into ranting mode again. Stay tuned for angry future posts. :b)
- Support for nested layouts/data. The web, just like a file system, is a tree of objects/documents. Let me make use of this when building my website, please. I want all posts to live in a
posts
folder. I want all of them to automatically inherit a post-specific layout. I was surprised to find how few frameworks embrace this. - Lets me author posts in Markdown, ideally MDX. This is pretty much a given in 2022, literally every framework supports this (and that's awesome.)
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.
- lume.land, the Lume website
- The Lume Showcase, a list of websites built with Lume
- The documentation (it's very good)
- You can sponsor the author on GitHub \o/