Today, I’ll talk about my process for building and deploying static websites using Metalsmith, a static site generator for NodeJS.
A static website is appropriate for a variety of projects such as:
- Blogs
- Technical Documentation
- eBooks
- Small websites
By using a static site generator, you are able to get several features such as navigation, table-of-contents, templates, partials, permalinks, and more. If you didn’t use a generator, these are things that you would have to manually create and maintain.
I recently used Metalsmith to create the documentation site for Bedrock, and I was pleased with the development process. It’s worth sharing how I built it as I don’t think a lot of people have used Metalsmith. It’s a good alternative to Jekyll, which is a hugely popular static site generator written in Ruby.
Metalsmith is lightweight, yet very flexible. I was inspired by Segment’s technical documentation page, which looks and works very well and is just a static website built using Metalsmith and it’s many plugins.
Here’s what we will cover:
- How Metalsmith works
- Project Setup
- Writing using Markdown
- Using layouts and partials
- Automatic table-of-contents and navigation
- Syntax Highlighting
- Development and Live Reload
- One-line deploy to Github Pages
Clone a sample site on GitHub
For those who prefer looking at code, I put all the code required to build a static website with the above features up on GitHub. Check that out and clone it if you are interested in building a static site using Metalsmith.
Ok, now let’s talk about how I built it.
Remember to sign up to my newsletter if you want to read more articles like this.
How Metalsmith works
Metalsmith works in the following manner:
- It takes a collection of static files from a source directory
- It applies a set of transformations to them
- It moves the transformed files to a destination directory.
You define the transformations inside a build.js
file in your project’s root directory. It’s similar to the concept that Gulp employs.
For example, here is what my build.js
looks like. Read through the comments to understand the transformations.
var Metalsmith = require('metalsmith'),
metallic = require('metalsmith-metallic'),
drafts = require('metalsmith-drafts'),
layouts = require('metalsmith-layouts'),
markdown = require('metalsmith-markdown'),
assets = require('metalsmith-assets'),
collections = require('metalsmith-collections'),
autotoc = require('metalsmith-autotoc'),
browserSync = require('browser-sync'),
argv = require('minimist')(process.argv);
// If I run node run deploy --prod, it should not use browser-sync to watch for changes.
// Otherwise, it should.
if (!argv.deploy) {
browserSync({
server: 'build',
files: ['src/*.md', 'layouts/*.html', 'assets/*.css'],
middleware: function (req, res, next) {
build(next);
}
})
}
else {
build(function () {
console.log('Done building.');
})
}
function build (callback) {
Metalsmith(__dirname)
// This is the source directory
.source('./src')
// This is where I want to build my files to
.destination('./build')
// Clean the build directory before running any plugins
.clean(true)
// Use the drafts plugin
.use(drafts())
// Use metallic plugin to add syntax highlighting
.use(metallic())
// Use Github Flavored Markdown for content
.use(markdown({
smartypants: true,
gfm: true,
tables: true
}))
// Generate a table of contents JSON for every heading.
.use(autotoc({
selector:"h2, h3, h4, h5, h6",
headerIdPrefix: "subhead"
}))
// Group my content into 4 distinct collections. These collection names
// are defined as collection: <name>
inside the markdown YAML.
.use(collections({
"Get Started": {"sortBy": "date"},
"Tutorials": {"sortBy": "date"},
"User Authentication": {"sortBy": "date"},
"Building with React & Flux": {"sortBy": "date"}
}))
// Use handlebars as layout engine.
.use(layouts('handlebars'))
// Use the assets plugin to specify where assets are stored
.use(assets({
source: './assets',
destination: './assets'
}))
// Build everything!
.build(function (err) {
var message = err ? err : 'Build complete';
console.log(message);
callback();
});
}
We build the files by running node build.js
from the command line.
These transformations are defined by the developer and shared throughout the community as plugins. These plugins are all npm modules. For building Bedrock’s static site, I used the following plugins.
- metalsmith-assets: Include static assets in your Metalsmith build
- metalsmith-autotoc: Generate table of contents JSON based on document metadata
- metalsmith-collections: Group files together into an ordered collection
- metalsmith-drafts: Hide metalsmith files marked as drafts
- metalsmith-layouts: Apply layouts to source files by specifying a template engine of your choice
- metalsmith-markdown: Convert markdown files to HTML
- metalsmith-metallic: Highlight code blocks in markdown using highlight.js
I also use browser-sync, which reloads and rebuilds my static site when I save changes. I’ll talk more about that in the Development section.
Run the following command to install all these into your application:
npm i metalsmith-assets metalsmith-autotoc metalsmith-collections metalsmith-drafts metalsmith-layouts metalsmith-markdown metalsmith-metallic browser-sync --save-dev
Project Setup
Here’s an image of how my static website folder structure looks like.
assets
: This directory stores CSS and JavaScript files.
build
: The contents of this directory are created by the Metalsmith build.
layouts
: This stores layout templates. I used Handlebars as my layout engine. More about layouts and templating below.
src
: This is where all the site content is placed. Content is written in markdown with YAML tags. The metalsmith-collections
module groups files into collections and displays them automatically in a top-level navigation bar.
build.js
: This is the Metalsmith build file, explained above.
deploy.sh
: This is a shell script that deploys the contents of my build/
directory to Github Pages. More information in the Deployment section below.
Everything else is pretty self-explanatory.
Writing using Markdown
The pages are written using Markdown and stored inside the src/
directory. Each markdown file has YAML content above it. Here’s a snippet of one:
--- title: Building with React & Flux draft: false collection: Building with React & Flux layout: layout.html date: 2016-12-28 autotoc: true --- Bedrock ships with React, React Router and Flux. This allows you to start using React components inside your web application. Let's walk through an example of how to use React and Flux to build client-side pages.
The YAML at the top defines some variables that are used by the various plugins.
title
: Transforms into a{{title}}
variable that is available in the layout file.draft
: Used by the metalsmith-drafts plugin to specify if this is a draft or not.collection
: Used by the metalsmith-collections plugin to define the collection that this file belongs to.layout
: Specifies the layout to use for this file.date
: Used by the metalsmith-collections plugin to sort files within a collection by date.autotoc
: Specifies whether an automatic table of contents should be generated or not. Used by the metalsmith-autotoc plugin.
These markdown files will be transformed into HTML files through the transformations specified in build.js
above.
Using Layouts and Partials
Layouts are provided through the metalsmith-layouts plugin. It supports a variety of different layout engines.
To use, define the layout engine in build.js
:
... .use(layouts('handlebars')) ...
Metalsmith determines the layout template to use based on the YAML layout
key. Here’s what my layout.html file looks like. You’ll notice that several of the YAML variables are defined in there. The variable {{{contents}}}
will be filled with the content of the Markdown file.
I haven’t used partials for this project but you can use the metalsmith-partial plugin for it.
Navigation and Table of Contents
There are a few interesting snippets in the project’s layout file that are worth pointing out. The first is iterating over collections
.
<nav class="navigation-nav"> {{#each collections}} <a class="navigation-nav-item" href="{{this.0.path}}">{{@key}}</a> {{/each}} </nav>
The metalsmith-collections plugin generates a collection object that I use here to create a dynamic top-level navigation. Here’s the snippet in build.js
:
// Group my content into 4 distinct collections. These collection names
// are defined as collection: <name>
inside the markdown YAML.
.use(collections({
"Get Started": {"sortBy": "date"},
"Tutorials": {"sortBy": "date"},
"User Authentication": {"sortBy": "date"},
"Building with React & Flux": {"sortBy": "date"}
}))
Next, there’s the code for the table-of-contents.
{{#if toc}} <section class="toc sticky"> {{#each toc}} <a class="toc-item" href="#{{id}}">{{text}}</a> {{/each}} </section> {{/if}}
Not all pages will have a table-of-contents, but if one does, the metalsmith-autotoc plugin will provide an array to create a linked table-of-contents.
... // Generate a table of contents JSON for every heading. .use(autotoc({ selector:"h2, h3, h4, h5, h6", headerIdPrefix: "subhead" })) ...
Syntax Highlighting
Syntax highlighting is important for documentation sites. Fortunately, it’s really easy to add with the metalsmith-metallic plugin.
... // Use metallic plugin to add syntax highlighting .use(metallic()) ...
Make sure you apply this transformation before you transform your markdown to HTML. Since the syntax highlighting is done through Highlight.js, you will also need to add an appropriate syntax highlight theme file in. I added in the Atom Dark theme.
<link rel="stylesheet" href="https://highlightjs.org/static/demo/styles/atom-one-dark.css">
Once in place, you can write code snippets in Markdown.
Development and Live Reload
When developing your site, run node build.js
. This will start up a browser-sync server that will automatically look for changes in the src/
, assets/
, and layouts/
directories. When changes occur, it will rebuild Metalsmith and reload the page.
I prefer browser-sync to metalsmith-watch, which has issues rebuilding the codebase when it has to listen to multiple directories.
Deploying to Github Pages
When ready to release, you can deploy to Github Pages by running:
node run deploy --prod
This will run the shell script inside ./deploy.sh
. The script will perform push from the project’s build/
directory to origin/gh-pages.
Note: If you get errors when executing the shell script, you may need to edit its permissions to make it executable:
chmod +x deploy.sh
I originally got this script from the metalsmith-gh-pages-deploy repository.