In my guide to static websites with Jekyll I talked about the many advantages of using a static CMS for your blog: it's faster, more secure and lets you set up your process exactly the way you want. But I never really found a good solution to make it easy to add content elements like images, code blocks and embeds to blog posts without having to write a bunch of HTML every single time — and change every instance of it if I ever wanted to change the markup. While looking for the perfect static CMS for the new spaCy website, I finally came across Harp, a static webserver with built-in pre-processing. Together with Jade, a simplified markup language that supports JavaScript code and compiles to HTML, it's been my go-to setup ever since.

In this blog post, I'll explain how to install and set up your blog with Harp in about a minute (yes, really!) and how to write powerful templates using Jade. I'll also share a couple of useful tips and tricks, as well as a nice-looking skeleton blog template I wrote to get you started.

There are a bunch of benefits that come with this setup, but here are the most important ones:

  • Incredibly fast setup and intuitive templating using simple JavaScript expressions and JSON metadata
  • Automatic pre-processing for Sass, LESS, Jade, EJS, Stylus, CoffeeScript and Markdown
  • Simplified, clean and readable markup and support for reusable components for layouts and blog entries
  • Can be easily extended with custom JavaScript functions

Getting started

If you're familiar with the command line and/or already have Node installed, simply skip this first step and go ahead and install Harp. Otherwise, follow these two simple steps:

  1. Install Node via the recommended installer for your platform and operating system. The installer installs the JavaScript runtime as well as the package manager npm, which we'll use to install Harp.
  2. Install Harp and its dependencies with this simple command:
    sudo npm install -g harp

This installs Harp globally and adds the harp command. For a step-by-step installation tutorial, check out this video. After installation, navigate to the folder you want to create your project in.

The skeleton template

To make things easier, I've put together a nice-looking boilerplate designed for a modern, minimal blog. It includes a bunch of example components and functions, easily extendable CSS and a bunch of documentation and explanations throughout the code.

Preview of skeleton blog template

View Skeleton Theme on GitHub

You can either download the theme and add it to your directory manually, or import it directly using Harp. The harp init command below creates a new directory called "myproject" in the current location and then imports the latest version of my boilerplate from GitHub:

harp init myproject --boilerplate inesmontani/pretty-harp-jade-skeleton

For alternative templates, check out Harp Boilerplates or use harp init to install the default. Once you've set up your project, all you need to do is run this and open localhost:9000 in your browser:

harp server

harp server previews your site locally, watches for any changes in the directory and processes new and updated files.

The folder structure

Now that the site's up and running locally, let's take a look at the folder structure. Harp allows you to structure your project pretty flexibly. My skeleton theme keeps the source files in the root directory and compiles the static site into a directory named www.

├── _includes                   # Layout partials (not compiled)
|   ├── _footer.jade
|   ├── _functions.jade         # JavaScript functions
|   ├── _header.jade
|   ├── _mixins.jade            # Reusable mixins
|   ├── _navigation.jade
|   ├── _page.jade              # Page template
|   └── _post.jade              # Post template
├── assets
|   ├── css
|   └── js
├── blog                        # Blog posts
|   ├── img
|   ├── _data.json              # Blog post meta
|   └── this-is-a-post.jade
├── www                         # Destination of compiled site
├── _data.json                  # Root directory meta
├── _harp.json                  # Configuration
├── _layout.jade                # Layout
├── 404.jade
├── about.jade
├── feed.xml.jade               # RSS feed, compiled to feed.xml
└── index.jade

Similar to other generators like Jekyll, files with a leading underscore are not being compiled and can be used for layout partials and configuration. All other files are either compiled (like pages, blog entries or stylesheets) or simply copied over (like images).

Files that can be pre-processed by Harp, like .sass, .scss, .less or .coffee are also converted automatically. css/style.sass simply becomes css/style.css in the destination directory.

Organizing metadata

All metadata is organized in JSON files. There are 2 files that are interpreted by Harp:

  • _harp.json — Used for "globals", i.e. general configuration and other information you want to make easily available to your templates. This could include things like the site name or your Twitter handle.
      "globals": {
        "sitename": "My blog",
        "description": "This is my blog where I post stuff",
        "author": "Me",
        "email": "",
        "twitter": "mytwitteraccount",
    All of these variables can be used in your theme like #{sitename} (inline syntax) or title=sitename to set the content of the <title> element to "My blog". More on the Jade syntax later!
  • _data.json — Used for all relevant metadate for files in its respective folder. Folders don't have to contain a _data.json, but they can to keep things neat and organized. Here's an example for a blog post:
      "this-is-a-post": {
        "title": "This is a post",
        "categories": [ "test", "personal" ],
        "date": "2016-06-01",
        "image": "post1.jpg"
    Each item is an object and its key (this-is-a-post) refers to a file with that name in the same folder. So the snippet above belongs to this-is-a-post.jade.

Jade in a nutshell

While Harp is a pretty powerful static server and a useful tool for any kind of site, the combination with Jade takes it to a whole new level. Jade (soon to be renamed to Pug) is a markup language with simplified syntax that compiles to HTML. To give you an example, let's have a look at a simple loop to render a list of posts. Here's how this would look like using plain HTML and template tags:

{% for post in site.posts %}

  <article class="{% if %post.featured %}featured{% else % }regular{% endif %}">
    <h2 class="headline">{{ post.title }}</+h(2)>

      {% if post.excerpt %}
        <div class="excerpt">{{ post.excerpt }}</div>
      {% endif %}


{% endfor %}

And here's the same thing in Jade:

each post in site.posts
    article(class=post.featured ? "featured" : "regular")

        if post.excerpt

So much cleaner, right? One of the big advantages of Jade is that it simplifies the syntax without actually simplifying the subset that it supports. It lets you express anything you can also express in HTML. On top of that, you can define reusable components, include files and use JavaScript-like statements and even full JavaScript expressions.

The most important features

  • Jade is whitespace sensitive, meaning that you have to use indentation for parent/children relationships. This also lets you omit the closing tag. Before working with Jade, I never realised how much space is taken up by closing tags alone.
      li I am a list item.
      li Me too.
  • Each line needs to start with a tag. Plain text needs to be prefixed with a | character.
    p This is good.
    This is really bad and will be rendered as <This>
    | This is good again.
  • Classes and IDs are added after to tags similar to CSS, attributes are added in brackets after. div tags can be left out.
    #container"") Google
  • Variables containing strings can be displayed inline or as a tag's content.
    - var title = "Hello world"
    p The title is #{title}
  • For conditionals and iteration, there are simplified versions of if/else if/else, each, while and case (equals switch in JS):
    each post in posts
      if post.description
        p.error No description.
  • JavaScript expressions can be added inline. A good example of this is using the ternary operator for dynamic classes:
    .post(class=featured ? "wide" : "narrow")
      p Post categories: #{categories.join(", ")}
  • Any other JavaScript code, like variable declarations, needs to be prefixed with a hyphen:
    - var counter = 0
    each post in posts
        - counter++
  • Mixins are reusable components or "content functions" and can take arguments.
    mixin image(file, alt)
        img(src="/blog/img/" + file alt=alt)
    +image("example.jpg", "Image description")

For more examples and an interactive editor, check out the Jade docs or play around with some code on CodePen. Simply go to the editor settings and choose Jade as your HTML preprocessor.

Working with mixins and JS

Using custom complex markup in blog posts can become pretty annoying. If you want components like data tables, responsive embeds or code blocks that require classes and nested elements, you usually had to add them to each post manually. Whenever that markup changed, you had to go back and change every single instance of that component. With mixins, you only need to define the markup in one place:

mixin quote(source,
  blockquote.quote(class=style.join(" "))

    if source

+quote("Rick Astley") Never gonna give you up.

block allows you to insert the children of a mixin in a specific place., also called "rest argument syntax", lets the mixin take an unknown amount of arguments. Every argument after source is then combined into an array that's available to your mixin. .join(" ") combines the arguments into a string separated by spaces — perfect for a list of CSS classes for example.

I usually organize my mixins in a _mixins.jade file, which I can include whenever needed. My mixins file also includes a collection of regular JavaScript functions that can be used to add even more functionality. Whenever you want to add something a litlte more complex, there's no need for plugins – just write your own function!

Here is a function that turns dates in the YYYY-MM-DD format into a nicer natural-language version like "May 1, 2016". It's also included in my skeleton template:

-  function convertDate(input) {
-    var dates = [];
-    var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
-    var date = new Date(input);
-    return months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
-  }

Jade rendered by Harp requires JavaScript code to be prefixed with a hyphen on each line, which I agree looks a little awkward and will hopefully be fixed soon.

Layout partials with Harp

Think of Harp's layout partials as somewhere in between regular includes and flexible mixins. The !=partial() function lets you include other files using their relative path. This one includes the file _header.jade from a folder called _layout:


Partials can also be included dynamically. For example, you could have different pages include different widgets that are specified in the metadata, using the same template:

- var widget = "_newsletter-signup"

Partials can also take a second argument, an object with additional data you want to pass on. This is not necessary for global or post metadata, which will always be available for the page that's currently rendered. But you could have a general template for a sidebar, that's populated with different content, or you could create a partial to display a flexible number of posts, like so:

- var post_max = posts || 0
- var post_counter = 1

each post, slug in
  if(post_max === 0 || post_counter <= post_max)

      //- post markup goes here

    - post_counter++

If no post count is specified, it defaults to 0 - which will render all posts that are available. The partial can then be called anywhere in your templates like this:

!=partial("_latest-posts", { posts: 6 })

Building the site

Compiling the static version of your side including all necessary pre-processing only takes one simple command:

harp compile

That's it and you're ready to go!

Tips and Tricks

Detecting the current page and section

To create flexible templates without unnecessary repetition, it's good to know the page you're currently on and which folder it is in. Harp comes with two options:

  • current.source returns the name of the current page. For example, some-post if you're visiting /blog/some-post.
  • current.path returns the full path to the current page as an array. For example, ["blog", "some-post"] if you're visiting /blog/some-post.

This way, you can easily check if a post is the home page index (current.path[0] == "index"), a blog post (current.path[0] == "blog") or just a very specific page. You could also use this to write WordPress-style isHome() or isBlog() functions.

Separate previewing from publishing

Sometimes you want to include things in your code that are only rendered when previewing the site locally, like new experimental styles. Or you want to include your Google Analytics only when building the final site that will be uploaded to your server. That's where environments come in handy. By default, Harp sets the environment to "development" when serving the site via harp server and "production" when building it via harp compile. The variable environment outputs the current environment in your theme:

if environment == 'development'
  p For testing purposes only!

Creating an RSS feed

RSS feeds might seem like a mystery sometimes, but they're really just simple XML files. Harp will compile feed.xml.jade to feed.xml and render it with Jade, wihch means you can use simplified syntax. A full RSS feed is part of my Skeleton Template, including a little function to convert your post date into a valid RSS date. Here's a simplified version to show the principle:

doctype xml
  rss(version="2.0", xmlns:atom="")
      title= title
      link= url
      description= description
      atom:link(href="#{ url }/feed.xml" rel="self" type="application/rss+xml")

      each post, slug in
          description <![CDATA[
            !=partial("blog/" + slug)
            | ]]>

This example loops through the blog posts and then uses dynamic partials to display the blog post's content. Wrapping the content in <![CDATA[ and ]]> is necessary to make sure that none of it is interpreted as XML.

To prevent Harp from compiling the feed as a normal page using your layout, make sure to opt out of the layout in the _data.json of your home directory:

"feed": {
  "layout": false

Pretty URLs

By default, Harp compiles all files as separate HTML files with the file extension .html. This works fine, but it's definitely not as nice as being able to navigate to blog/some-post. If your server is running Apache (which is the case for most shared hosting), add this to your .htaccess file on your server (or create one – go here for more info and tips):

Options +MultiViews

This is the easiest option to make your server look for matching filenames. So if you request blog/some-post, the server will search for matching files, find some-post.html and show it to you. Keep in mind that you won't be able to have a file and a folder with the same name in the same directory if you're using this solution.

Art-directed Posts

Art-directed posts are blog posts that have their own unique style and layout different from the general template. I love the idea of this and it's very easy to set up. Harp lets you "opt-out" of a layout by setting layout: false in the post meta data:

"this-is-a-post": {
  "title": "This is a post",
  "layout": false

Now the content of your post is shown directly and not plugged into your _layout.jade. This means that you need to set up the whole HTML structure of the page within the post — so you can basically do anything you want! If you only want to opt out of the general blog post template for certain posts, you could consider doing something like this in the _layout.jade:

if current.path[0] == "blog"
  if artdirected


This way, you keep the basic structure of your layout intact. yield simply display the content of the current page.

Any questions or suggestions?

Let's talk on Twitter!

Links and Resources

About the author
Ines Montani

I'm a digital native, programmer and front-end developer working on Artificial Intelligence and Natural Language Processing technologies. I'm the co-founder of Explosion AI and a core developer of spaCy and Prodigy.