Hakyll Pt. 1 – Setup & Initial Customization


SummaryUse the hakyll static site generator to set up a website or blog.
Revised2023-02-11 @ 16:00 UTC

This is part 1 of a multipart series where we will look at getting a website / blog set up with hakyll and customized a fair bit.


  1. Installation & Setup
  2. Configuration Rules
  3. Blog Posts
  4. Working With Templates
  5. Rendering Partials

Installation & Setup

While this is detailed fully on the hakyll installation tutorial, I will repeat it here.

  1. install stack and make sure $HOME/.local/bin is included in your PATH
  2. λ stack install hakyll – should install hakyll-init in $HOME/.local/bin
  3. λ hakyll-init
  4. λ cd
  5. λ stack init
  6. λ stack build
  7. λ stack exec site build
  8. λ stack exec site rebuild – to test the rebuild command
  9. λ stack exec site watch – starts dev server & watches for changes
  10. navigate to http://localhost:8000 to see it!

Configuration Rules

Hakyll gives you the ability to override its existing configuration rules to change anything from the output directory (default _site/) to deploy commands to the host and port for previewing your site locally.

Here is what the default configuration looks like in hakyll (source):

-- | Default configuration for a hakyll application
defaultConfiguration :: Configuration
defaultConfiguration = Configuration
    { destinationDirectory = "_site"
    , storeDirectory       = "_cache"
    , tmpDirectory         = "_cache/tmp"
    , providerDirectory    = "."
    , ignoreFile           = ignoreFile'
    , deployCommand        = "echo 'No deploy command specified' && exit 1"
    , deploySite           = system . deployCommand
    , inMemoryCache        = True
    , previewHost          = ""
    , previewPort          = 8000
    ignoreFile' path
        | "."    `isPrefixOf` fileName = True
        | "#"    `isPrefixOf` fileName = True
        | "~"    `isSuffixOf` fileName = True
        | ".swp" `isSuffixOf` fileName = True
        | otherwise                    = False
          fileName = takeFileName path

The hakyll tutorial on rules, routes and compilers makes reference to a hakyllWith function for customizing configuration, so let’s see how we can use that.

The default hakyll main function in your site.hs file looks like this:

main :: IO ()
main = hakyll $ do

What we can do is change hakyll to hakyllWith and pass a function that we’ll name config that makes use of the defaultConfiguration but returns a new, altered record:

main :: IO ()
main = hakyllWith config $ do
  -- ...

config :: Configuration
config = defaultConfiguration
    { destinationDirectory = "docs"
    , previewPort          = 5000

Whenever we make a change to site.hs, we need to make sure we use stack to build it again and restart our server. We’ll also need to make sure we clean out our old output folder with the clean command. So, all together now:

λ stack exec site clean
λ stack build
λ stack exec site watch

…and now your output will be in the docs/ folder, and your site will be previewable at http://localhost:5000.

Now that we’ve flexed our configuration muscles a bit, let’s look at the posts/ folder to see what we’re working with on the blog side.

Blog Posts

If you open the posts/ folder and select any preset blog post (hint: you can see them online at; make sure you click the “Raw” button to view the raw markdown), you’ll see a standard markdown file containing two sets of content: * metadata (between the --- delimiters) * body content (everything else)

From http://localhost:5000, let’s click on the first post we see: http://localhost:5000/posts/2015-12-07-tu-quoque.html. If we open up the corresponding file, 2015-12-07-tu-quoque.html, in our text editor, we can see there are two metadata fields: title and author. Let’s change them:

title: Some Latin Text
author: Some Roman Person

Refresh the page and see the changes!

But note that despite changing the title of your blog post, the outputted HTML file is still located at http://localhost:5000/posts/2015-12-07-tu-quoque.html. This is because the markdown filename is what currently determines the outputted filename. We will change this in Part 5 of this series, but until then, if you change the title of your post, it would be a good idea to also change the filename.

Feel free to edit these metadata fields and markdown content with your own blog post material.

Next up, we’ll see about how we can customize the templates to work with all the metadata that we might want to include from our posts (description, author, keywords, image, etc).

Working With Templates

There is a hakyll turorial on templates, context and control flow that you should check out. Here, we’re going to adjust the default templates to suit our needs.

The HTML templates can be found in – you guessed it – the templates/ folder.

The first file we will look at is templates/default.html (hint: this template is also viewable online at

Templates are nothing more than .html files but with a caveat (which you’d know about if you read the tutorial above): there is added context – drawn from markdown options or injected before compilation in site.hs – that can be used anywhere, so long as it is between $ (dollar signs). Here is an example that uses the title property that is set in each file:


Cool! Now what if we wanted to use our author metadata?

<meta name="author" content="$author$">

Oh no!

  updated templates/default.html
  [ERROR] Missing field $author$ in context for item about.rst

This is because not all of our files being run through this default template have all the same fields. We can use conditionals to solve this:

$if(author)$<meta name="author" content="$author$">$endif$

<!-- or, if you prefer -->

  <meta name="author" content="$author$">

Blog posts also should have a description and keywords, so let’s add those: to posts/2015-12-07-tu-quoque.markdown:

title: My Blog Post
description: This is my great blog post
keywords: blog, first blog, best blog evar
author: I did it!

We’ll then update our default template to handle those, as well:

$if(author)$<meta name="author" content="$author$">$endif$
$if(keywords)$<meta name="keywords" content="$keywords$">$endif$

If you refresh http://localhost:5000/posts/2015-12-07-tu-quoque.html and open up the web inspector, you’ll now see that the <head> now contains not only your post’s title, but also all the other fields you specified!

There are many other possibilities for this, as well. For instance, if you wanted to have different og:types of pages, you could do:

  <meta property="og:type" content="$type$">
  <meta property="og:type" content="website">

Check out the default template for this website here:

Rendering Partials

Lastly for today, what if we want to reuse templates and specify where they should be rendered from other templates? Enter hakyll partials.

A common use of partials is for navigation across different templates. We can add a new file, templates/nav.html, and place the following in it (add some CSS classes and styling if you want it to look nice):

<nav class="nav">
  <a href="/">Home</a>
  <a href="mailto:[email protected]">Email Me</a>

Now, this partial can be used anywhere. For example, from templates/post.html:


Wrapping Up

In this lesson we learned how to get started with hakyll and learned some of the ways for us to get started customizing it to our own needs. Next time, we’ll dive into site.hs to generate our own sitemap.xml file.

Next up:

Until next time,