Building a blog with Metalsmith

When you want to be able to deploy interesting web projects alongside a blog with a unified and personal theme, using a CMS or blog engine stops becoming an option. Maybe you just don't want to deal with all of the concerns of hosting, securing, and updating WordPress on your own. Or maybe you want to be able to serve lots of visitors quickly on slim hardware. If any of these sounds like you, it is time to look at static site generators, such as Metalsmith. This post covers how to get started building a basic blog with Metalsmith.

Video

Why spend a few minutes reading when you could spend longer listening to me talk?

Intro

First, let's cover some background. Metalsmith is a static site generator written for Node.js. It is very much in the spirit of Gulp, allowing you to define a build pipeline using plugins. This pipeline takes your directory tree of documents, assets, and templates and passes them through a series of plugins that process them into a full website. All you need to do is pick and configure the plugins that you need. Let's get started!

There are many different plugins in the Metalsmith ecosystem to assist with processing various file types. For the purpose of this article, we will be working with the following: Less for CSS pre-processing, markdown for document formatting, and jade for templating. We will combine plugins to process these with plugins that can help us gather posts into collections, generate excerpts, create permalinks, and more.

Before I show any other code, I want to give you the require calls and corresponding variables that will be used throughout the article:

var metalsmith = require('metalsmith'),
  branch = require('metalsmith-branch'),
  collections = require('metalsmith-collections'),
  excerpts = require('metalsmith-excerpts'),
  markdown = require('metalsmith-markdown'),
  permalinks = require('metalsmith-permalinks'),
  serve = require('metalsmith-serve'),
  templates = require('metalsmith-templates'),
  watch = require('metalsmith-watch'),
  moment = require('moment');

Directory structure

Before jumping into things, let's lay out the basic directory structure of our demo Metalsmith project:

/project_dir
|-/node_modules
|-/build
|-/src
  |-/posts
      |-test-post.md
|-/templates
  |-layout.jade
  |-post.jade
  |-posts.jade
  |-posts.md
|-build.js
|-package.json

As with other Node.js projects, we will have a package.json and /node_modules. In addition, we will define your Metalsmith pipeline in build.js file. You are welcome to call it something else, but I find node build to be a fairly descriptive command for what we are trying to do here. Beyond this, there are three other directories: /src, /build, and /templates. Our jade templates go in /templates. The general site structure is composed in /src, and the output of processing that through the build pipeline is output into /build. You will see more of the details of how this works below.

Basic concept

Let's start without any sort of plugin processing:

var siteBuild = metalsmith(__dirname)
  .metadata({
    site: {
      title: 'azurelogic.com',
      url: 'https://azurelogic.com'
    }
  })
  .source('./src')
  .destination('./build')
  // build plugins go here
  .build(function (err) {
    if (err) {
      console.log(err);
    }
    else {
      console.log('Site build complete!');
    }
  });

This chunk of code forms the foundation of our static site pipeline. Let's talk about what everything does. metalsmith(__dirname) starts up the Metalsmith generator with __dirname as the base directory. .source('./src') then sets the source directory for where all of our markdown files, templates, javascript, Less, CSS, etc should come from. Conversely, .destination('./build') sets the destination directory for all of these files. .build(callback) triggers the build pipeline to be processed. Since we have not invoked any plugins, all this does is copy the files and directory structure from /src to /build. If you add files anywhere in the tree below /src and that file is not processed by a plugin, it will get copied over to /build with its path intact. There is one other important function to mention here: .metadata(obj) allow you to inject any additional metadata into the process for use in templates or other plugins. I have found it useful for injecting my site title and url, but there are other uses with other plugins.

Document structure and metadata

We just discussed metadata in the build pipeline, but there is much more to the metadata than just that. Every file should begin with a metadata (or front matter) section formatted like this:

---
key1: value1
key2: value2
---

You don't have to have any data in the metadata section. In fact, my Less and jade files omit it entirely. You will likely find this section most useful for populating data used by templates directly or by various plugins to process markdown documents. We will see more of this in the next section.

Adding markdown and template processing

If you take note of the comment in the generic pipeline block above, you see that the build pipeline is built up between the Metalsmith setup and the call to build(callback). This build pipeline is composed of use(plugin) calls. Let's see what this looks like with markdown and jade template processing added to the pipeline.

var siteBuild = metalsmith(__dirname)
  .metadata({
    site: {
      title: 'azurelogic.com',
      url: 'https://azurelogic.com'
    }
  })
  .source('./src')
  .destination('./build')
  .use(markdown())
  .use(templates({
        engine: 'jade',
        moment: moment
      }))
  .build(function (err) {
    if (err) {
      console.log(err);
    }
    else {
      console.log('Site build complete!');
    }
  });

It is important to note that order matters here. We have to process the markdown docs into HTML before we can insert that markup into the templates. It is also worth noting that we can even pass other libraries into the template engine, as I have done with moment. This allows for formatting date strings inside of the templates. This is a very powerful feature. So let's take a look at a sample markdown doc and jade templates.

test-post.md

---
title: test post
publishDate: 2015-01-01
modifyDate: 2015-01-01
author: Rob Ribeiro
template: post.jade
---

This is just a test

post.jade

extends layout

block main
  article#article
    h1= title
    div By #{author} | Published #{moment(publishDate).format("M.D.YYYY")}
      if modifyDate && moment(publishDate).isBefore(modifyDate)
        span Updated #{moment(modifyDate).format("M.D.YYYY")}
    div!= contents

layout.jade

doctype html
html
  head
    block head
      block title
        title= title ? title + " | " + site.title : site.title
      block styles
  body
    block body
      #layout-main
        block main

You should immediately notice the front matter on test-post.md. The template field is what tells the template plugin which template file to use. The rest of this metadata is useful throughout the build pipeline, and it is even available inside of the templates. post.jade consumes some of this metadata even. It interpolates author as well as the publishDate and modifyDate after being formatted through moment, which as mentioned before was being passed into the template engine earlier. You can also see that we are able to do conditional logic, even involving the site.title, which was set way back in the call to metadata(obj) in the build pipeline. This allows for very rich configuration and templating. In my full build, I even have conditions based on whether I am in dev or production.

Testing

Now that we have established a basic build pipeline, we can start to build pages. However, it is difficult to write blindly and know that it will all work in production. We can use other plugins to serve the plugins and even refresh the files as we work on them. This is where serve and watch come into play:

.use(serve({
  port: 8080,
  verbose: true
}))
.use(watch({
  pattern: '**/*',
  livereload: true
}))

we need to keep them towards the bottom of your pipeline, as the build pipeline should be essentially complete by the time you reach them. The serve plugin acts as a webserver, and the watch plugin acts as to reprocess individual files through the pipeline as we edit and save. Note that this only applies to files in our source directory, which does not include /templates. In order to have live reload functionality, you will need to add this to script tag to your layout template: script(src="http://localhost:35729/livereload.js")

So, let's expand the functionality of our pipeline now. We now want a blog roll page that can show all of our posts with the first paragraph of each of them. In addition, we want our urls to look like https://azurelogic.com/projects/ instead of https://azurelogic.com/projects.html. The first goal is accomplished by a combination of the excerpts and collections plugins. The second goal is done with the permalinks plugin, but we're going to include the branch plugin to show how it works and to do some route restructuring. Let's look at the code first:

.use(excerpts())
.use(collections({
  posts: {
    pattern: 'posts/**.html',
    sortBy: 'publishDate',
    reverse: true
  }
}))
.use(branch('posts/**.html')
    .use(permalinks({
      pattern: 'posts/:title',
      relative: false
    }))
)
.use(branch('!posts/**.html')
    .use(branch('!index.md').use(permalinks({
      relative: false
    })))
)

The first thing to think about here is how this fits into the pipeline. In order to process the template for the posts page correctly, we will need to have the excerpts and collections, which means that this chunk goes in between the markdown and template plugins in the pipeline. Excerpts extracts the content of the first

tag into metadata, while collections creates a collection of files in the metadata for selective iteration. This is very useful in designing a blog roll:

posts.md

---
title: blog of posts
template: posts.jade
paginate: posts
---

posts.jade

extends layout

block main
  #posts
    h1 posts
    if collections.posts && collections.posts.length > 0
      each post in collections.posts
        h2
          a(href='/'+post.path)!= post.title
        div!= post.excerpt
    else
      h2= collections.posts.length
      h2 there ain't no posts here, man

Notice that the posts.md file only contains front matter. That is because it only serves to trigger the building of the designated template and to provide the required metadata. In addition, we now have metadata for the posts collection and for excerpt on each post. This makes for a very concise "repeater" concept that accomplishes our first goal.

Moving on to permalinks and branch, we have two branches: posts and "not posts" (also, now that markdown has been processed, these are .html files, not .md). This allows us to rewrite items in the post directory based on their title metadata instead of the filename. You could also use this process to include the year or full date. This is also why we need the branch. We don't want to rewrite the url of any of our other pages like this.

Wrap-up and Complete Pipeline

With all of this put together, we have the basis for building a static site with Metalsmith that includes a blog and is highly extensible. In future articles on Metalsmith, I plan to cover other plugins to enable things like sitemaps, rss, tags, and pagination, as well as techniques for multiple pipelines and optional sections. Until then, here is the completed pipeline discussed in this article:

var metalsmith = require('metalsmith'),
  branch = require('metalsmith-branch'),
  collections = require('metalsmith-collections'),
  excerpts = require('metalsmith-excerpts'),
  markdown = require('metalsmith-markdown'),
  permalinks = require('metalsmith-permalinks'),
  serve = require('metalsmith-serve'),
  templates = require('metalsmith-templates'),
  watch = require('metalsmith-watch'),
  moment = require('moment');


var siteBuild = metalsmith(__dirname)
  .metadata({
    site: {
      title: 'azurelogic.com',
      url: 'https://azurelogic.com'
    }
  })
  .source('./src')
  .destination('./build')
  .use(markdown())
  .use(excerpts())
  .use(collections({
    posts: {
      pattern: 'posts/**.html',
      sortBy: 'publishDate',
      reverse: true
    }
  }))
  .use(branch('posts/**.html')
      .use(permalinks({
        pattern: 'posts/:title',
        relative: false
      }))
  )
  .use(branch('!posts/**.html')
      .use(branch('!index.md').use(permalinks({
        relative: false
      })))
  )
  .use(templates({
        engine: 'jade',
        moment: moment
      }))
  .use(serve({
      port: 8080,
      verbose: true
    }))
    .use(watch({
      pattern: '**/*',
      livereload: true
    }))
  .build(function (err) {
    if (err) {
      console.log(err);
    }
    else {
      console.log('Site build complete!');
    }
  });
MetalsmithNode.jsJavaScripttutorialjadevideo