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?
-
Less Code: With Lume, you won't be affected by unnecessary code or plugins. Everything is simple and to the point.
-
Full Control: You get to control every aspect of your site.
-
Deno-Powered: Being built on Deno, you get all tools you need in a single executable.
-
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!