Guides

Harold is a static site and blog generator based on Handlebars and Markdown. Let's see how it is built. Be sure to check Getting started section first.

Workflow

With Harold, the workflow looks like this:

  1. You work in the src directory, and Harold transforms everything into standard .css and .html files in the build directory.
  2. You can prepare partials, pages, posts/docs and assets files.
  3. You work with Handlebars and Markdown. So a little bit of knowledge of these two is required.
  4. You work with a development server that runs at localhost:3000.
  5. You will get all changes reflected in the browser after refreshing it.

Project Types

Since v1.3.0, all directories are optional, making Harold flexible for different project types:

Minimal Landing Page

Use case: Simple website with just static pages, no blog.

Minimal structure:

src/
├── pages/
│   └── index.hbs
└── styles/
    └── main.scss

No posts, partials, or assets required!


Documentation Site

Use case: Technical documentation or knowledge base.

Recommended structure:

src/
├── docs/           # Markdown docs (configure as mdFilesDirName)
├── docs-layouts/   # Doc templates
├── pages/
│   ├── index.hbs
│   └── docs.hbs
├── partials/
│   └── sidebar.hbs
└── styles/
    └── main.scss

Configuration:

{
  "mdFilesDirName": "docs",
  "mdFilesLayoutsDirName": "docs-layouts"
}

Blog

Use case: Personal or company blog.

Full structure:

src/
├── posts/          # Blog posts in markdown
├── blog-layouts/   # Post templates
├── pages/
│   ├── index.hbs
│   ├── about.hbs
│   └── all-posts.hbs
├── partials/
│   ├── head.hbs
│   └── footer.hbs
├── assets/
│   └── images/
└── styles/
    └── main.scss

Full Website

Use case: Complex site with blog, pages, and custom features.

Complete structure: See "Directories structure" section below.


Directories structure

Below is the src directory structure from the Default template:

.
├── assets (optional)
│   ├── images
│   │   └── favicon.png
│   └── js
│       ├── harold.js
│       ├── harold-main-menu.js
│       ├── harold-scroll-top.js
│       └── harold-search.js
├── blog-layouts (optional, required if using posts)
│   └── blog-post.hbs
├── jsonData (auto-generated)
│   └── posts.json
├── pages (optional)
│   ├── about.hbs
│   ├── all-posts-list.hbs
│   ├── author.hbs
│   ├── index.hbs
│   └── projects.hbs
├── partials (optional)
│   ├── footer.hbs
│   ├── go-top-btn.hbs
│   ├── head.hbs
│   ├── main-menu-overlay.hbs
│   └── search-overlay.hbs
├── posts (optional)
│   ├── example1.md
│   ├── example2.md
│   ├── example3.md
│   ├── example4.md
│   ├── example5.md
│   ├── example6.md
│   └── harold-intro.md
├── statics (optional)
└── styles (optional)
    ├── _basic.scss
    ├── _homepage.scss
    ├── _main-menu.scss
    ├── main.scss
    ├── _page.scss
    ├── _post.scss
    ├── _search.scss
    ├── _utils.scss
    └── _variables.scss

Note: Since version 1.3.0, most directories are optional! Harold will gracefully skip missing directories:

  • Missing posts directory? Harold will build pages only
  • Missing assets? No problem, just pages will be built
  • Missing styles? Harold will skip CSS generation
  • Missing pages or partials? Harold will warn but continue

This makes Harold more flexible for different project types (documentation-only sites, minimal landing pages, etc.).

styles and assets directories are self-explanatory. Here you can build your Scss structures and custom javascript logic. You can also save images here. All will be moved and compiled later.

posts and pages are places for actual content. You write pages with Handlebars markup and posts with Markdown.

blog-layouts is a place for all custom blog layouts. You can then point particular layout to use in the Markdown file.

statics is a place for all custom static files that you would want to copy to the 'build' directory, such as robots.txt, manifest.json, etc. They will land in the root. If you wish, you can also nest directories there.

All compiles and lands in the build directory:

.
├── about.html
├── all-posts-list.html
├── assets
│   ├── images
│   │   └── favicon.png
│   └── js
│       ├── harold.js
│       ├── harold-main-menu.js
│       ├── harold-scroll-top.js
│       └── harold-search.js
├── author.html
├── index.html
├── jsonData
│   └── posts.json
├── posts
│   ├── example1.html
│   ├── example2.html
│   ├── example3.html
│   ├── example4.html
│   ├── example5.html
│   ├── example6.html
│   └── harold-intro.html
├── projects.html
└── styles
    └── main.css

As you can see here, static website, ready to deploy.

Pages and partials

You will build pages using Handlebars templating engine. So everything possible with Handlebars should be possible also here. You can define your contents and split them into partials. Partial is a fragment of your HTML-like document.

<div class="container-full-width docs-layout" data-js-doc-content>
  {{> docs-sidebar-left}}
  <section class="docs-content docs-home">
    <h1>A quick intro to Harold JS</h1>

{{> docs-sidebar-left}} here is nothing more than a separate .hbs file located in the partials directory. It will be injected precisely in this place in the code.

You can also use partials with parameters. For example we can use head partial (also located in partials directory) which looks like:

{{> head
  title="Harold - Static site generator"
  description="Blogs, landing pages, portfolios, documentation sites - start with ready-to-use templates"
  ogTitle="Harold - Static site generator"
  ogDescription="Blogs, landing pages, portfolios, documentation sites - start with ready-to-use templates"
  ogUrl="https://www.haroldjs.com"
  twitterTitle="Harold - Static site generator"
  twitterDescription="Blogs, landing pages, portfolios, documentation sites - start with ready-to-use templates"
  twitterUrl="https://www.haroldjs.com"
}}

Handlebars engine will then inject all of these parameters into partial's placeholders. Head partial looks like:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="icon" href="/assets/images/favicon.png" type="image/png" />
  <title>{{title}}</title>
  <meta name="description" content="{{description}}">
    
  <meta property="og:title" content="{{ogTitle}}" />
  <meta property="og:description" content="{{ogDescription}}" />
  <meta property="og:image" content="{{ogImage}}" />
  <meta property="og:url" content="{{ogUrl}}" />

  <meta name="twitter:title" content={{twitterTitle}} />
  <meta name="twitter:description" content="{{twitterDescription}}" />
  <meta name="twitter:url" content="{{twitterUrl}}" />
  <meta name="twitter:image" content="{{twitterImage}}" />

In the end, everything is compiled to standard .html starting from the page file as a root file for html output.

Read more in the Handlebars docs: https://handlebarsjs.com/guide/

Markdown files

Every single Markdown file is compiled into a separate .html file located in the posts directory (or different one if there is custom config file). Every file defines a couple of parameters at the top of the file. It uses the Front Matter approach (Embedded YAML structured data at the top of Markdown documents).

---
layout: 'docs'
title: 'Recipes'
publicationDate: '2021-05-01'
tags:
  - learn
ogTitle: "Harold Recipes - Static site generator"
ogDescription: "Ready-to-use recipes. You can take them as inspiration or copy it as it is and use in your custom template"
ogImage: "https://www.haroldjs.com/assets/images/harold-start.png"
ogUrl: "https://www.haroldjs.com/docs/recipes"
twitterTitle: "Harold Recipes - Static site generator"
twitterDescription: "Ready-to-use recipes. You can take them as inspiration or copy it as it is and use in your custom template"
twitterImage: "https://www.haroldjs.com/assets/images/harold-start.png"
twitterUrl: "https://www.haroldjs.com/docs/recipes"

---

Rest of the markdown content here...

You can add as many parameters there as you can, but Harold requires mandatory ones in every .md file. They are:

  • layout - defines which blog layout to use
  • title - defines post title
  • publicationDate - defines publication date in format YYYY-MM-DD

Optional front matter fields supported by Harold:

  • excerpt - Short description of the post (used in post lists)
  • tags - Array of tags for categorization and filtering
  • coverImage - URL or path to cover/featured image

Custom fields - You can add any custom fields you want! All front matter data is passed to your layout template and accessible as variables.

Example with all supported fields:

---
layout: 'blog-post'
title: 'My Awesome Post'
publicationDate: '2025-12-20'
excerpt: 'A brief description of this post'
tags:
  - javascript
  - tutorial
coverImage: '/assets/images/cover.jpg'
customField: 'Any custom data you need'
---

Rest of the markdown content here...

Accessing custom fields in layouts:

In your layout .hbs file, all front matter fields are available as variables:

<h1>{{title}}</h1>
<p>Published: {{formatDate date=publicationDate}}</p>
{{#if customField}}
  <div class="custom">{{customField}}</div>
{{/if}}
<div class="content">
  {{{content}}}
</div>

In markdown files, you can also use standard HTML code. Of course, most of it. Some special scripting tags are removed when compiling.

Since version 1.3.0, images in markdown automatically get optimized:

  • Automatic dimensions - Width and height attributes are added automatically to prevent layout shift (only for local files)
  • Lazy loading - Native loading="lazy" for better performance
  • Async decoding - decoding="async" for non-blocking rendering
![Alt text](assets/images/photo.jpg)

Becomes:

<img src="assets/images/photo.jpg" 
     alt="Alt text" 
     width="800" 
     height="600" 
     loading="lazy" 
     decoding="async" />

In the Default template, there are predefined styles and structures to be used. For example, centered images:

![Alt text](image.jpg?style=centered)

Or wide media elements using query parameters (since v1.3.0):

![Wide image](image.jpg?style=wide)

Alternatively, you can still use HTML:

<div class="wide-content"><img src="/assets/img.png" alt="alt text" /></div>

The query parameter approach is recommended as it keeps your markdown cleaner.

Another predefined structure will be helpful when you need to embed some iframe-based content.

Since v1.3.0, you can use query parameters for cleaner markdown:

<iframe src="https://codepen.io/embed/xxxxx?style=embed" height="500"></iframe>

Or the traditional HTML wrapper:

<div class="embeded-media-container">
  <iframe src="https://codesandbox.io/embed/proper-usage-of-react-tracked-o8v3s?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="proper usage of react-tracked"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>
</div>

Both approaches will make your embedded content responsive. The ?style=embed parameter automatically wraps the iframe in the necessary container div.

Helpers

Helpers are unique fragments of Handlebars code that maps javascript functions under the hood. So you can use loops, conditions, blocks, etc. The list of all built-in helpers is here: https://handlebarsjs.com/guide/.

Besides that Harold offers it's own custom helpers which are:

responsiveImg (since v1.3.0)

A powerful helper for creating responsive, optimized images with automatic dimensions:

{{responsiveImg 
  src="assets/images/hero.jpg" 
  alt="Hero image"
  width="1200"
  height="600"
  loading="eager"
  className="hero-image"
  srcset="assets/images/hero-small.jpg 480w, assets/images/hero-large.jpg 1200w"
  sizes="(max-width: 600px) 480px, 1200px"
}}

Parameters:

  • src (required) - Image source path
  • alt - Alt text for accessibility
  • width - Image width (auto-detected if not provided)
  • height - Image height (auto-detected if not provided)
  • loading - "lazy" (default) or "eager" for above-the-fold images
  • decoding - "async" (default) or "sync"
  • className - CSS class name
  • srcset - Responsive image sources
  • sizes - Media query sizes

formatDate

It is a handy helper when you need to change date formatting across the whole app.

{{formatDate date=publicationDate format='dd mmmm yyyy'}}

In the example above, we take publicationDate, and we format it using dd mmmm yyyy. Check format options here: https://blog.stevenlevithan.com/archives/date-time-format.

It can be used in every .hbs file.

postsList

This helper is very powerful when it comes to building post lists. It has a very good parametrization.

{{postsList
  perPageLimit=6
  currentPage=1
  className="homepage-featured-post"
  noTags=true
  noExcerpt=true
  noDate=true
  byTagName="featured"
  noReadMoreButton = false,
  readMoreButtonLabel="Lets dive in!"
  noImage = false,
  dateFormat = 'yyyy-mm-dd',
}}
  • perPageLimit - you can define how many posts (.md) files the helper should list in this place (Default: undefined -> all items)
  • currentPage - you can list first, second or other pages here (Default: 1)
  • className - useful when you want to define a custom CSS class for the container. All children will get an additional prefix, so here, for example, the title element will get the 'homepage-featured-post--title' class (Default: hrld-post-list)
  • noTags - hide tags list (Default: false)
  • noExcerpt - hide excerpt text (Default: false)
  • noDate - hide publication date (Default: false)
  • byTagName - filter and create list by specified tag name (Default: undefined -> all tags)
  • noReadMoreButton - hide read more button (Default: false)
  • readMoreButtonLabel - define read more button label (Default: 'Read more')
  • noImage - hide preview image (Default: false)
  • dateFormat - define publication date format (Default: 'yyyy-mm-dd')

relativePath

{{relativePath 'about.html'}}
{{relativePath 'assets/images/image.png'}}
{{relativePath 'styles/style.css'}}

I would recommend always using this helper in .hbs files. Of course, if you are well aware of your paths, you can omit that. In Scss and Markdown files, you still need to use standard paths, if needed, relative ones. It will probably change in the future.

hostDirName

{{hostDirName}}

It will return the previously defined subdirectory name in which the whole website is hosted. It is sometimes useful when you would like to get this name in your templates dynamically. In the default template, it is used to provide proper paths for the template's JavaScript logic. You probably won't need to use it much unless you write your own Harold template for many different projects.

There isn't a possibility to add custom Handlebars helpers, but it is in plans in the future.

SCSS files

Harold uses Scss for styling. All when what can be done with Scss should also be possible here.

You can create many different structures. The Default template uses Scss imports to differentiate sections and tooling classes. For example, you'll find there _utils.scss file with predefined content formatting classes and whole sections like _main-menu.scss. All is then imported in the main.scss file.

@import 'variables';
@import 'utils';
@import 'basic';
@import 'search';
@import 'main-menu';
@import 'docs';
@import 'homepage';

As you can see, all files which we want to import should have the _ prefix. This tells the compiler that it shouldn't create separate .css files from these.

Harold also uses PostCSS and Autoprefixer out of the box. Since version 1.3.0, CSS is automatically minified using cssnano for optimal file sizes. For now there is no way to use custom plugins, but it is planned.

Assets

All images, fonts, and custom JavaScript files should land in the assets directory. Harold will move them to the build directory.

Always refer to these files using absolute paths.

<script src="/assets/js/harold.js"></script>
<img src="/assets/images/img.png" />

See example of assets directory here.

Posts JSON data

There is a special posts.json file located in the jsonData directory. It will be populated on every markdown file change, keeps data about all posts in JSON format. This file is quite essential because the whole search engine uses it. It is its primary purpose in the Default theme, but you can use it for many other use cases. For example, for loading some posts dynamically or creating load more functionality. It could have many different use cases.

Host from a subdirectory

Suppose you need to host your blog created using Harold from subdirectory. For example, www.mywebsite.com/blog/ then you would need to configure hostDirName in the .haroldrc file. Add there the name of your subfolder. The second thing is that you need to be aware of your paths (posts links, images, styles, etc.). The default Harold's templates support relative paths by default. It uses the relativePath handlebars helper for that. So it should work well in both cases when hosted from root and subdirectory.

Next: Custom templates

Contents