sitepipe: A simple to understand static site generator

[ bsd3, library, web ] [ Propose Tags ]


Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


  • No Candidates
Versions [RSS] 0.1.0, 0.1.1,,,,,,,
Dependencies aeson, base (<5), bytestring, containers, directory, exceptions, filepath, Glob, lens, lens-aeson, megaparsec, MissingH, mtl, mustache, optparse-applicative, pandoc, parsec, shelly, text, transformers, unordered-containers, yaml [details]
License BSD-3-Clause
Copyright 2017 Chris Penner
Author Chris Penner
Category Web
Home page
Bug tracker
Source repo head: git clone
Uploaded by ChrisPenner at 2022-10-25T16:47:40Z
Reverse Dependencies 1 direct, 0 indirect [details]
Downloads 4142 total (21 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user [build log]
All reported builds failed as of 2022-10-25 [all 1 reports]

Readme for sitepipe-

[back to package description]


Hey there! If you're just getting started with a static site I'd recommend checking out slick instead. It has better caching support, is faster, and has better pandoc interop! SitePipe isn't deprecated (yet), but I'd recommend trying slick instead! There's a template repo HERE to get you started. Cheers!

Build Status Hackage Join the chat at


Heads up! If you're reading this from the Hackage docs the links will be broken, go read it on Github.


What is it?

It's a simple to understand static site generator for making blogs, personal websites, etc.

What's it look like?

Start by setting up the structure of your website in a site folder:

├── templates
│   ├── post.html
│   └── index.html
└── posts

Here's a dead-simple blog generator using markdown posts, you can see it in action in examples/starter-template, or build on it in the tutorial

{-# language OverloadedStrings #-}
module Main where

import SitePipe

main :: IO ()
main = site $ do
  -- Load all the posts from site/posts/
  posts <- resourceLoader markdownReader ["posts/*.md"]

  -- Build up a context for our index page
  let indexContext :: Value
      indexContext = object [ "posts" .= posts
                            -- The url is where the index page will be written to
                            , "url" .= ("/index.html" :: String)

  -- write out index page and posts via templates
  writeTemplate "templates/index.html" [indexContext]
  writeTemplate "templates/post.html" posts

Wait, another static site generator? What about Hakyll/Jekyll?

Yup, yet another static site generator. I've tried using Hakyll and Jekyll on different occasions and found there was too much magic going on with all of the monadic contexts for me to understand how to customize things for my use-cases. Even adding simple tags/categories to my blog seemed far more complex then it needed to be; Hakyll specifically got me really bogged down; what was the Compiler monad? How does an Item work? How do I add a custom field? Why couldn't I just edit data directly like I'm used to doing in Haskell? They seemed a bit too opinionated without giving me escape hatches to wire in my own functionality. If they're working for you, then great! But they weren't working for me, so that's where SitePipe came from.

Getting Started

Quick Start

The easiest way to get started is to clone this repo and try out the examples in the examples directory. There's a starter-template which is a barebones starting point, and also a slightly more complex blog with tags and an rss feed. You can build either of the examples using Stack by cding into the directory and running stack build && stack exec build-site. This creates a 'dist' folder with the results of the build. A quick way to serve the site is to use Serve.

Serving a site with Serve:

  • npm install -g serve
  • serve dist
  • Navigate to the port which is serving (usually http://localhost:3000)


Read the walkthrough of the system HERE; it'll run you through the basics of how the system works and how to make your own customizations!


How is SitePipe different from other solutions?

Instead of dealing with complex contexts SitePipe works with values. Values are loaded from files and can be rendered into html. What happens to the values in-between is up to you!

SitePipe provides a bunch of helpers for you, but at the end of the day you can fit the pipes together however you like.


Metadata for posts and content is parsed from yaml into Aeson's Value type; Aeson can easily represent nested objects or lists inside your metadata, and there's a rich ecosystem for working with Aeson types! You can load resources in as any object which implements FromJSON (or just leave them as Aeson Values) and you have the option to edit the objects directly without worrying about monadic or external context.


SitePipe has built-in support for Mustache Templates, specifically Justus Adam's implementation in Haskell. This lets you use a well established templating system in your site, complete with template functions, partials, and iteration. Since the underlying data is based on It's clear how templates will behave since resources are based on Aeson's JSON types.


You can load resources in to work on them using a Loader, A loader simply finds and loads files into resources by employing a Reader on some files. A basic resourceLoader loader is provided, which will load all of the files matching a set of file-globs through the provided reader and will return an Aeson Value containing the relevant metadata and content. You should be able to use resourceLoader for most things by customizing the reader function which you pass it.


A reader is a function of the type String -> IO String; the input is the file contents which remain after a yaml header has been stripped off (if it exists). The most common reader is the provided markdownReader which runs a markdown document through pandoc's markdown processor and outputs html. You can write your own readers if you like, either by making a function which operates over the content of the document and matches String -> IO String or by using the provided Pandoc helpers (mkPandocReader, mkPandocReaderWith) which allow you to use any of Pandoc's provided document formats, and optionally specify transformations over the pandoc document before it is rendered to html or some other output format.


Writers take a list of resources (anything with a ToJSON instance, often an Aeson Value) and will write them to the output where the static site will be. The most common writer is writeTemplate which will render the given resource through a given template, but you can also use textWriter, or write your own writer; either writing to disk using liftIO or by using the provided writeWith combinator which given a transformation from a resource to a String (a -> SiteM String) will write the result of the transformation to the place specified by the resource's url.


Some things don't fit into the previous categories. For example copyFiles and copyFilesWith are simple tools which just copy the specified files over as-is into the output directory. You pass either of them a list of file globs and the resulting files will be copied over. copyFiles sends them to the same relative filepath from the source directory to the output directory, while copyFilesWith allows you to transform the filepath to specify a new location for each file.


Sitepipe includes a few utilities which simply make working with sites easier. The included utilities will expand as time goes on.


Feel free to file an issue if you run into any trouble, or come ask in the Chatroom