Building a Blog with Metalsmith
Ever want to set up your own blog? Stuck on which Static Site Generator to use? Know Javascript? Or don't? After trying out multiple NodeJS based SSG's, and looking into multiple non-NodeJS based ones, I settled on Metalsmith for my own blog. Here's my beginner's guide to setting up a new blog from scratch.
Prerequisites
- You have NodeJS installed. And are hopefully at least a little familiar with it.
Setup
Start with a brand new empty directory, set it up for a Node project, and install Metalsmith:
> mkdir blog
> cd blog
> npm init
# Answer questions
> npm install --save metalsmith
Create your bare-bones Metalsmith build file, build.js
:
var Metalsmith = require('metalsmith');
Metalsmith(__dirname)
.source('./contents') // read files from here
.destination('./output') // write files to here
.build(err => { // build!
if (err) throw err; // handle errors
});
Yep, that's it. That's a fully-functioning Metalsmith static site. It's not going to do anything but copy your stuff from contents/
into output/
, but it works. Give it a shot - make your first content file and run the build:
> mkdir contents
> echo "<h1>Hello World</h1>" > contents/index.html
> node build.js
> cat output/index.html
<h1>Hello World</h1>
Look at that, there's our file, processed through the Metalsmith pipeline.
The pipeline is an important concept to grasp when it comes to working with Metalsmith. You tell it where to find your input files via .source()
and where to write the output files via .destination()
, and then you will begin inserting a bunch of plugins as ordered steps along the pipeline. When all your plugins are registered, you call .build()
to start the processing. It will then process all files from your source
directory through the plugin pipeline, transforming them at each step of the way, and finally write out the final files in your destination
directory.
Let's take a look at what the pipeline looks like by writing and adding our first plugin. Add the following between the destination
and build
steps:
.use((files, metalsmith, done) => {
console.log('files', files);
console.log('metalsmith', metalsmith);
console.log('done', done);
setImmediate(done);
})
This is the format of a plugin and it's going to show us exactly what the pipeline looks like. A plugin is called with .use(Function)
, and in most plugins, you'll see that you call the plugin which returns the function you'll pass to .use()
. Each step of the pipeline receives three arguments - the files
object, the metalsmith
object, and a done
callback.
The files
object:
{
'index.html': {
contents: <Buffer>,
mode: '0644',
stats: {
dev: 16777220,
mode: 33188,
...
}
}
}
The files object is just a key-value store with an entry for each file in your source
directory. It stores some useful information about the file in mode
and stats
, and it stores the entire content of the file as a Buffer
in contents
. You can easily convert theBuffer
to a string using files['index.html'].contents.toString()
.
The metalsmith
object
...is not super important for now. It's got lots of global metadata about the actual build you're running. Maybe we'll come back to it, but in building out this blog, I don't think I ever actually touched it.
The done()
callback
This is to tell Metalsmith when you're done. For whatever reason, all of their examples show it being called with setImmediate
during synchronous plugins, so I followed suit. Don't ask me why that matters.
Let's make it "Bloggy"
Ok, so using Metalsmith to copy/paste files around seems pretty stupid. Let's start adding some of the Community Plugins to make this thing useful.
Markdown
Let's stop writing HTML directly and use markdown. First, install the plugin:
> npm install --save metalsmith-markdown
Then, add the plugin as a step in the pipeline, right before our final debugging plugin we set up above. Please excuse the inline require
- that's only for readability in this post.
.use(require('metalsmith-markdown')())
And then rename and alter your existing index.html
file to an index.md
file, and adjust it's contents to be markdown:
> mv contents/index.html contents/index.md
> echo "# Hello World" > contents/index.md
> node build.js
> cat output/index.html
<h1>Hello World</h1>
Simple enough? The metalsmith-markdown
plugin essentially does the following with the files
object:
- For each
*.md
key, indicating a markdown file in yoursource
directory - Convert the contents to HTML by running it through a markdown parser
- Write out the new
file
as*.html
- Delete the old
*.md
key entry
It might seem odd that it's deleting the old one, but that's again an important aspect of the metalsmith pipeline. The .build()
step at the very end essentially just writes the files
object to disk. For every key in files
, a file matching that path is created in your destination
directory, and the contents written to it. If we didn't create the new index.html
key in files
, we wouldn't get an output/index.html
file, and if we didn't delete the index.md
key, we'd also get an output/index.md
file written out.
Templating
Now that we're converting markdown to HTML, let's start wrapping our contents in a common header and footer using nunjucks templates. First, install the metalsmith-layouts
plugin and the nunjucks
package, and create a directory to store our templates:
> npm install --save metalsmith-layouts nunjucks
> mkdir templates
Then add two templates we'll use for scaffolding the HTML page:
// templates/layout.nunjucks
<!doctype html>
<html>
<head>
{% block head %}
{% endblock %}
</head>
<body>
<header>
{% block header %}
Welcome to My Blog!
{% endblock %}
</header>
<div>
{% block content %}{% endblock %}
</div>
<footer>
{% block footer %}
Built with <a href="http://metalsmith.io/">Metalsmith</a>
{% endblock %}
</footer>
</body>
</html>
// temmplates/post.nunjucks
{% extends "templates/layout.nunjucks" %}
{% block content %}
<div class="post">
{{ contents | safe }}
</div>
{% endblock %}
Basically, layout.nunjucks
will be the base layout for every single page on out site, providing extendable blocks where content can be inserted. Then post.nunjucks
extends the layout tempate, and inserts the file contents inside the body
block.
Now, add a pipeline step that will process your files against these templates
.use(require('metalsmith-layouts')({
engine: 'nunjucks', // Which engine to use
directory: 'templates', // Where are the templates stored
default: 'post.nunjucks' // Default template to use
}))
Now, when you run the pipeline, you should see a full-structured index.html` file written out, using the proper above layout.
Page titles and specific layouts
Let's look at how we might both title a post as well as choose a specific layout to use. Metalsmith supports YAML Frontmatter by default (which can be turned off, but I don't see a good reason why), so you can specify metadata at the beginning of your source files. So, insert the following at the beginning of index.md
:
---
title: My first Blog Post
layout: post.nunjucks
---
Those entries will become key/value pairs on the file object in the pipeline, and also exposed to your templates. So now we can enhance our post.nunjucks
file to include the title:
{% extends "templates/layout.nunjucks" %}
{% block header %}
<h1>{{ title }} </h1>
{% endblock %}
{% block content %}
...
{% endblock %}
And the metalsmith-layouts
plugin already looks for a layout
property on every source file, and uses that prior to the default.
Next Steps
So what's next? We've got markdown files with frontmatter metadata building into a nunjucks-driven extensible layout structure. What do we need to move this into a full-on blog? Here's the list of plugins I'm using to host brophy.org, and feel free to check out the source for this site over in GitHub if you want to see any examples:
metalsmith-collections
to organize all of your posts into sorted collectionsmetalsmith-pagination
to group your collections into paginated listsmetalsmith-metallic
for syntax highlighting in code blocksmetalsmith-page-titles
to generate HTML<title>
contentmetalsmith-assets
to copy over static assetsmetalsmith-favicons
to copy over and setup your site iconsmetalsmith-drafts
to support draft posts that don't get publishedmetalsmith-permalinks
to generate permalinks without the.html
metalsmith-excerpts
to grab the first paragraph of a post for an overview displaymetalsmith-tags
to allow tagging of posts and listing posts by tag`metalsmith-sass
for SCSS compilationmetalsmith-icons
for easy inclusion of custom icon fonts from font-awesomemetalsmith-feed
to generate an RSS feed
Good luck!