Using Lume: A Static Site Generator for Deno

by Hexagon, 5 minutes read javascript yaml deno lume guide

If you're like me, you probably appreciate the simplicity of using a static site generator for your blog or website. Today, I want to share the beauty of Lume, a static site generator built for Deno.



What is Lume?

Lume is a minimalist static site generator specifically designed for Deno. It's not WordPress; you won't find a ton of built-in features or a graphical interface. However, it gives you full control over your code and configuration.

Why Lume?

  1. Less Code: With Lume, you won't be affected by unnecessary code or plugins. Everything is simple and to the point.

  2. Full Control: You get to control every aspect of your site.

  3. Deno-Powered: Being built on Deno, you get all tools you need in a single executable.

  4. Multiple File and Template Support: Lume supports various file types like Markdown, YAML, and even TypeScript. It's also compatible with multiple template engines, including Nunjucks.

Bootstrapping Your Lume Project

To create a new Lume project, run the following command:

deno run -Ar https://deno.land/x/lume/init.ts

You don't have to enable any plugins just yet. You can enable them as you discover you need to.

Actually, you could start by manually creating the configuration, but this command initializes a new Lume project with some basic files.

Configuration

In the root directory, you'll find a _config.ts file. This file helps you customize your site settings.

Lume is flexible and supports both TypeScript and JavaScript for configuration; just make sure to use the correct file extension.

While running a basic Lume site doesn't require you to know much (if any) JavaScript, it could benefit you, especially if you're diving into more advanced plugins or customizations.

If you're new to JavaScript and would like to learn more, consider checking out my article series The guide to JavaScript.

Running Your Site

To test your website locally, use deno task serve to start a development server, or deno task lume to generate your site to the _site folder in the root of your project.

These tasks are generated by the init script, and essentially work through these rows in the generated deno.json.

{
  "tasks": {
    "lume": "echo \"import 'lume/cli.ts'\" | deno run --unstable -A -",
    "build": "deno task lume",
    "serve": "deno task lume -s"
  }
}

Adding Content

For content, I prefer to use Markdown for its simplicity. For templates, I opt for Nunjucks, which is simple yet powerful. You can sprinkle the project root with .md or .njk files, but i like to gather everything inside the src directory.

If you want to have a separate source directory, move all source files, including _includes to the src directory, and modify _config.ts to initialize using the following configuration.

Point out the public URL of your blog while you're at it; this will help with the sitemap plugin and possibly others later.

const site = lume({
  src: "./src",
  location: new URL("https://my.blog.url"),
}

Templating with Includes

Lume allows you to use 'includes' for templating. This is a feature I heavily utilize to maintain a consistent look across my site.

Base Template for HTML/Head

For example, I have a head.njk file that includes all the essential HTML head tags.

<!doctype html>
<html lang="{{ lang }}">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
 
    <!-- Halfmoon CSS -->
    <link href="https://cdn.jsdelivr.net/npm/halfmoon@2.0.0/css/halfmoon.min.css" rel="stylesheet" integrity="sha256-pfT/Otf/lK1xFNInb5QQR1uRF9cOP/8zDICH+QQ6o2c=" crossorigin="anonymous">

    <!-- Specific -->
    <title>{{ title }}</title>
 
    <link href="/css/style.css" rel="stylesheet">

  </head>
  {{ content | safe }}
</html>

I use Halfmoon CSS as the graphical framework, make sure to replace with your preference, or maybe even custom styles?

Template for Normal Page

base.njk extends head.njk with a body for a normal page

---
layout: head.njk
---

<body>

  {% include "nav.njk" %}

  <!-- content -->
  <div class="container">
    {{ content | safe }}
  </div>

</body>

Template for the header

nav.njk defines the top bar.

<nav class="navbar navbar-expand-lg mb-5">
  <div class="container">
    <a class="navbar-brand" href="/">
      Hexagon's Blog
    </a>
    <div class="collapse navbar-collapse" id="navbar-collapse-1">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
      
        <!-- Icons Links -->
        <li class="nav-item">
          <a class="nav-link" href="https://hexagon.56k.guru"><i class="fas fa-globe me-2"></i></a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="https://github.com/hexagon"><i class="fab fa-github me-2"></i></a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="https://github.com/hexagon/sponsors"><i class="fas fa-hand-holding-usd me-2"></i></a>
        </li>
      </ul>
    </div>
  </div>
</nav>

Template for an Article

And for articles, I have a post.njk that also includes head.njk and has additional elements specific to articles and article series.

---
layout: base.njk
---

<div class="row">
  <div class="col-md">
    <article lang="{{ language }}">
      <header>
        <h1>{{ title }}</h1>
      </header>
      <p>
        <time datetime="{{ date }}">
          {{ date | date }}
        </time>
      </p>
      {{ content | safe }}
    </article>
  </div>
</div>

Setting Up a System for an Article Series

For those who are keen to set up an article series, you can use the _data.yml to specify series-related metadata.

In the _data.yml for a particular series folder, you can define attributes like series.name, series.tag, and so forth. The information in this file gets overridden by what you define in the _data.yml for each post in that series, providing a multi-layered configuration system.

Example for _data.yml in a series folder, like /posts/my-series/_data.yml

layout: post.njk
title: Page title for my series articles (overridable)
description: Description of my series
series: 
  title: Title for the series in the series listing
  tag: my-series-tag
tags:
 - my-series-tag
 - javascript
 - guide
 - post

Showing an in-series listing in the sidebar

Add this to nav.yml

<!-- Sidebar -->
<div class="col-md-3">
    <!-- Current Series TOC -->
    {% if page.data.series %}
    <section>
        <p>This article is part {{ page.data.part }} of the series <strong>{{ page.data.series.title }}</strong></p>
        <ul>
            {% for subpost in search.pages(page.data.series.tag) %}
            <li class="nav-item">
                <a class="nav-link {% if subpost.data.url == url %}active{% endif %}" href="{{ subpost.data.url }}">{{ subpost.data.title }}</a>
            </li>
            {% endfor %}
        </ul>
    </section>
</div>
{% endif %}

Final Configuration

Finally, the _config.ts ties everything together. Here's an example of how I've set up this blog:

import lume from "lume/mod.ts";
import feed from "lume/plugins/feed.ts";
import metas from "lume/plugins/metas.ts";
import sitemap from "lume/plugins/sitemap.ts";
import slugify_urls from "lume/plugins/slugify_urls.ts";
import nav from "lume/plugins/nav.ts";
import date from "lume/plugins/date.ts";

// Markdown plugin configuration
const markdown = {};

// Initialize site
const site = lume({
  src: "./src",
  location: new URL("https://hexagon.56k.guru"),
}, {
  markdown,
});

// Add plugins
site.use(date());
site.use(feed());
site.use(metas());
site.use(sitemap());
site.use(slugify_urls());
site.use(nav());

// Copy the css follder from /src to /_site on build
site.copy("css");

export default site;

For more details on configuring Lume, refer to the official documentation at lume.land/docs/configuration.

I also recommend checking out the plugin directory at lume.land/plugins.

And that's it! Now you know how to build your blog using Lume and Deno. Happy coding!


Introducing Minitz - Time zone conversion in JavaScript Guest Blogging using static site generators