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.
With Harold, the workflow looks like this:
src directory, and Harold transforms everything into standard .css and .html files in the build directory.partials, pages, posts/docs and assets files.localhost:3000.Since v1.3.0, all directories are optional, making Harold flexible for different project types:
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!
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"
}
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
Use case: Complex site with blog, pages, and custom features.
Complete structure: See "Directories structure" section below.
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:
posts directory? Harold will build pages onlyassets? No problem, just pages will be builtstyles? Harold will skip CSS generationpages or partials? Harold will warn but continueThis 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.
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/
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 usetitle - defines post titlepublicationDate - defines publication date in format YYYY-MM-DDOptional front matter fields supported by Harold:
excerpt - Short description of the post (used in post lists)tags - Array of tags for categorization and filteringcoverImage - URL or path to cover/featured imageCustom 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:
loading="lazy" for better performancedecoding="async" for non-blocking rendering
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:

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

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 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 pathalt - Alt text for accessibilitywidth - Image width (auto-detected if not provided)height - Image height (auto-detected if not provided)loading - "lazy" (default) or "eager" for above-the-fold imagesdecoding - "async" (default) or "sync"className - CSS class namesrcset - Responsive image sourcessizes - Media query sizesformatDate
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.
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.
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.
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.
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.