Creating hierarchy in a blog is a useful technique. It allows you to have different sections by type of content. It lets users navigate by their interests quickly. It makes your blog organized.
My need for a hierarchy came when working on another blog - Daily Reading Notes. That blog involves notes on books I read daily. Since each book has multiple reading notes, I wanted to allow two forms of navigation: by a grouping of all notes for a particular book and by posted date. The groupings also needed to be dynamic. I didn't want to write code or updated configurations every time I added a new book.
Hakyll didn't support this out of the box, and I didn't find examples of anyone using similar layouts. So, I created my own approach which I am now sharing.
Structuring the posts directory
In order to build a dynamic hierarchy, the directory structures needs to support association of the books with their related posts.
We start our implementation by creating a page and a folder with the name
of the book inside the
posts directory. This naming convention allows us to
link the book's page to the folder containing the posts.
Our directory structure should have the following layout:
posts +-- book-title.md +-- book-title | +-- post1.md | +-- post2.md
Matching the Routes
With the directory structure in place, we can proceed to creating matchers for the books and posts pages.
For the books pages, we match all the files in the
posts directory while
ignoring directories and their contents. We also set the extension of these file
html. The extension need to be specified because the original files are
written in markdown.
For the posts pages, we use
posts/*/* as the matcher string. The first
tells ~match to look inside any directory within
posts. The second
~match~ to gets all the files inside the subdirectory. These file are also
written in markdown and need their extension set to
The matchers with extensions look like this:
main :: IO () main = hakyll $ do match "posts/*" $ do route $ setExtension "html" ... match "posts/*/*" route $ setExtension "html" ...
Getting the Posts for a Book
Now that we are matching the books and posts pages, we need to group the posts by their respective book. For this purpose, we write a small helper function that takes the output of Hakyll's ~getResourceFilePath~ function.
Our function takes the file path for the book page, strips out everything but the file name, and add a glob pattern to the end.
getBookFolder :: FilePath -> Pattern getBookFolder = fromGlob . (++ "/*") . takeWhile (/= '.') . drop 3 . show
We use this function in the books pages matcher to get a list of the book's posts
as an input for the newly created
bookCtx goes in the
main :: IO () main = hakyll $ do match "posts/*" $ do route $ setExtension "html" compile $ do filepath <- getResourceFilePath posts <- recentFirst =<< loadAll (getBookFolder filepath) let bookCtx = listField "posts" defaultContext (return posts) ...
compile block is similar to the auto generated example that lists all the
posts. Except, we generate the path for the posts relatively to the current file.
In the final step, we apply templates to both outputs. The book pages need a new
template that uses the
posts list context field we generated in the previous
step. The posts can continue using the default
post template. In addition to
the individual templates, both the matchers will need the default template and the
The final output of the two matchers looks like this:
main :: IO () main = hakyll $ do match "posts/*" $ do route $ setExtension "html" compile $ do filepath <- getResourceFilePath posts <- recentFirst =<< loadAll (getBookFolder filepath) let bookCtx = listField "posts" defaultContext (return posts) getResourceBody >>= applyAsTemplate bookCtx >>= loadAndApplyTemplate "templates/book.html" bookCtx >>= loadAndApplyTemplate "templates/default.html" bookCtx >>= relativizeUrls match "posts/*/*" $ do route $ setExtension "html" compile $ pandocCompiler >>= loadAndApplyTemplate "templates/post.html" postCtx >>= loadAndApplyTemplate "templates/default.html" postCtx >>= relativizeUrls
With these matchers in place, we have a hierarchy of books containing the posts related to them. We can add books by creating a new page and folder with the name of the book. This setup made it easy for me to maintain a hierarchical site. I hope it does the same for you.