Initially, I intended to run the site as dynamic content, using Node.js & Express. But I instead used Node.js as a static site generator, spitting out html pages that I can then push to Github Pages or surge.sh.
The Options
After a bit of research, I came across a bunch of blog-ready static site generators for Node.js (see StaticGen).
I decided on Metalsmith, due to the bare-bones structure and the flexibility of it's optional plugins.
How It Works
Installation is simple. I already had Node.js and npm installed, so I chose a directory and navigated to it in terminal.
npm install metalsmith --save
This installs Metalsmith and it's dependencies in the node_modules
directory. It also creates a package.json
file containing the
required npm packages for this project.
Now I created an app file in the root directory that contains Metalsmith app.
I called it build.js
. To get started, this file contains the following:
var Metalsmith = require('metalsmith');
// require plugins here
Metalsmith(__dirname)
.source('./src')
.destination('./build')
// build plugins here
.build(function(err){
if (err){
console.log(err);
} else {
console.log('Site build complete!');
}
});
Now, when I run the terminal command node build
from the project
root directory, Metalsmith will grab the source directory, run it through any
plugins and spit it out at the build directory.
This is what my source directory looks like:
|-/src
|-/fonts
|-/images
|-/js
|-/posts
|-creating-this-site.md
|-/scss
|-_normalize.scss
|-style.scss
|-404.md
|-blog.md
|-index.md
Without plugins, Metalsmith will simply copy the contents of this source directory to the build directory.
The Plugins
What plugins did I want to use? I found this out as I went along, but for starters I wanted to:
- use Jade for templating the html
- use Sass for compiling css
- concatenate JS files
- use Markdown for writing posts & pages
- create a Blog page containing a list (or 'collection') of posts
So after hitting the Metalsmith plugins directory, I found the plugins I needed to get started.
I installed the plugins by going to the project root in terminal and typing:
npm install metalsmith-sass --save
## Replace metalsmith-sass with plugin name.
## Repeat for each plugin
To use the plugins with Metalsmith, I required each at the top of build.js
:
var Metalsmith = require('metalsmith'),
// require plugins here
collections = require('metalsmith-collections'),
excerpts = require('metalsmith-excerpts'),
sass = require('metalsmith-sass'),
markdown = require('metalsmith-markdown'),
permalinks = require('metalsmith-permalinks'),
layouts = require('metalsmith-layouts'),
concat = require('metalsmith-concat');
Now I integrated the plugin into the Metalsmith pipeline, passing options as an object:
Metalsmith(__dirname)
.source('./src')
.destination('./build')
// build plugins here
.use(collections({
posts: {
pattern: 'posts/*.md',
sortBy: 'date',
reverse: true
}
}))
.use(markdown())
.use(excerpts())
.use(layouts({
engine: "jade",
moment: moment
}))
.use(concat({
files: 'js/**/*.js',
output: 'js/min/main-min.js'
}))
.use(sass({
outputDir: 'css/'
}))
.build(function(err){
if (err){
console.log(err);
} else {
console.log('Site build complete!');
}
});
Each plugin manipulates files in the source directory. The order can be critical: converting from markdown to html prior to applying layouts with Jade.
Layouts
So how do layouts work? My layouts live in a directory /layouts
containing:
|-/layouts
|-/partials
|-_head.jade
|-_header.jade
|-blog.jade
|-layout.jade
|-page.jade
|-post.jade
I started with a master layout called layout.jade
.
//- layout.jade
doctype
html
head
include partials/_head.jade
body
.container.row
include partials/_header.jade
.eight.columns.content
block content
layout.jade
includes the partials: _head.jade
and
_header.jade
:
//- partials/_head.jade
title= title ? title + ' - ' + 'Eric Jinks' : 'Eric Jinks'
meta(charset="utf-8")
link(href='/css/style.css' rel='stylesheet' type='text/css')
script(src="/js/min/main-min.js")
//- partials/_header.jade
header
.taright.four.columns
h1.logo
a(href="/")Eric Jinks
nav.nav
ul
li
a(href="/blog") blog
li
a(href="/") about
ul.social
- var twitter = "twitter.com/jinksi"
- var github = "github.com/jinksi"
- var instagram = "instagram.com/jinksi"
- var email = "ericjinks@gmail.com"
li
a(href="http://#{twitter}" title="#{twitter}")
i(class="icon ion-social-twitter")
a(href="http://#{github}" title="#{github}")
i(class="icon ion-social-github")
a(href="http://#{instagram}" title="#{instagram}")
i(class="icon ion-social-instagram")
a(href="mailto#{email}" title="#{email}")
i(class="icon ion-email")
Note the use of variables and logic in Jade === so much awesome.
Now I create a page template page.jade
.
//- page.jade
extends layout
block content
div!= contents
!=
represents unescaped html.
This is what a markdown page from the source directory looks like (index.md
):
---
title: About
layout: page.jade
---
Hello. Text goes here......
To add another page, I simply add another markdown file in the source directory:
---
title: 404 - Page Not Found
layout: page.jade
---
This page doesn't exist
And the Blog page blog.md
:
---
title: Blog
layout: blog.jade
---
Which uses the blog layout - blog.jade
:
//- blog.jade
extends layout
block content
ul.post-list
each post in collections.posts
li.post-item
h5.title
a(href="/#{post.path}")
=post.title
div.details #{moment(post.publishDate).format("Do MMM YYYY")}
div !{post.excerpt}
a(href="/#{post.path}" class="readmore").u-pull-right read more
.clear
hr
The blog layout works in combination with the metalsmith-collections
plugin to create the blog list.
Posts work in a similar way. Here is a basic test-post.md
, inside
the /src/posts
directory:
---
title: Test Post
publishDate: 2015-10-05
author: Eric
layout: post.jade
---
Post content goes here.
post.jade
:
//- post.jade
extends layout
block content
.post-header
h4.post-title= title
div.details #{author}, #{publishDate}
.post-content
div!= contents
What Went Wrong?
I was getting errors with the metalsmith-sass
plugin. This had
something to do with Xcode Command Line Tools
being out of date
on my machine. I solved this using the instructions found here.
The Result
Finally, after adding a few more Metalsmith plugins, this is what my project directory looks like:
/project_dir
|-/node_modules
|-/build
|-/layouts
|-/partials
|-_head.jade
|-_header.jade
|-blog.jade
|-layout.jade
|-page.jade
|-post.jade
|-/src
|-/fonts
|-/images
|-/js
|-/posts
|-creating-this-site.md
|-/scss
|-_normalize.scss
|-style.scss
|-404.md
|-blog.md
|-index.md
|-build.js
|-package.json
And this is what my build.js
contains:
var Metalsmith = require('metalsmith'),
branch = require('metalsmith-branch'),
collections = require('metalsmith-collections'),
excerpts = require('metalsmith-excerpts'),
sass = require('metalsmith-sass'),
markdown = require('metalsmith-markdown'),
permalinks = require('metalsmith-permalinks'),
serve = require('metalsmith-serve'),
layouts = require('metalsmith-layouts'),
watch = require('metalsmith-watch'),
concat = require('metalsmith-concat'),
moment = require('moment');
Metalsmith(__dirname)
.metadata({
site: {
title: 'Eric Jinks',
url: 'http://ericjinks.com'
}
})
.source('./src')
.destination('./build')
.use(collections({
posts: {
pattern: 'posts/*.md',
sortBy: 'date',
reverse: true
}
}))
.use(markdown())
.use(excerpts())
.use(branch('posts/**.html')
.use(permalinks({
pattern:':collection/:title'
}))
)
.use(branch('**.html')
.use(branch('!index.html')
.use(branch('!404.html')
.use(permalinks({
pattern:':title'
}))
)
)
)
.use(layouts({
engine: "jade",
moment: moment
}))
.use(concat({
files: 'js/**/*.js',
output: 'js/min/main-min.js'
}))
.use(sass({
outputDir: 'css/'
}))
.use(serve({
port: 8080,
http_error_files: {
404: "/404.html"
}
}))
.use(watch({
paths: {
"${source}/**/*": "**/*",
"layouts/**/*": "**/*"
},
livereload: true
}))
.build(function(err){
if (err){
console.log(err);
} else {
console.log('Site build complete!');
}
});
What Next?
(Read Part 2 here)
This blog could use a tag or category system à la Wordpress, an RSS feed, pagination and a draft post system. There are Metalsmith plugins for all of these and more. If not, plugins can easily be crafted with a bit of basic JS (source):
var plugin = function(files, metalsmith, done) {
console.log(files);
done();
};
Metalsmith(__dirname)
//...
.use(plugin)
//...
More info
Node.js
npm
Jade
Metalsmith.io
Awesome Metalsmith
Getting to Know Metalsmith
Building a blog with Metalsmith