diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,59 @@
 
 # Releases
 
+## Hakyll 4.16.0.0 (2023-04-27)
+
+- Bump `base` *lower* bound to 4.12 (GHC >= 8.6). Hakyll already failed to build
+    on earlier versions due to the template-haskell requirement, and nobody
+    complained about that, so I assume nobody cares if the support is properly
+    dropped (contribution by Alexander Batischev)
+- Export `Hakyll.Tags.simpleRenderLink` (contribution by Alexander Batischev)
+- Add `Hakyll.Web.Pandoc.Biblio.pandocBibliosCompiler` to load multiple bib
+    files by glob (contribution by Liang-Ting Chen)
+- Fix "Store.set: resource busy" error (contribution by Jasper Van der Jeugt)
+- Teach `Hakyll.Web.Template.Context.getItemUTC` about another date format,
+    "%d.%m.%Y" (contribution by dukzcry)
+- Add `Hakyll.Web.Html.withTagListM`, a monadic version of `withTagList`
+    (contribution by 0xd34df00d)
+- Fix all the warnings and enable `-Werror` in CI (contribution by Alexander
+    Batischev)
+- Miscellaneous updates and fixes to the docs (contributions by Tony Zorman,
+    Alexander Batischev, malteneuss, Agustín Mista, Martin Bukatovič, Muhammad
+    Aviv Burhanudin, Jacek Galowicz, Daniel Mlot, Yoo Chung, Robert Pearce)
+- Export `Hakyll.defaultCommands`, i.e. Hakyll's set of commands (`build`,
+    `check`, `clean` etc.) (contribution by Alexander Batischev)
+- Add a `Hakyll.Core.Configuration.Configuration.previewSettings` field which
+    lets the user override the settings used by the preview server (contribution
+    by Christopher League and Brian McKenna)
+- Make email address in RSS/Atom feeds optional (just set it to an empty string)
+    (contribution by Robert)
+- `Hakyll.Web.Meta.TwitterCard`: use `name` instead of `property` for better
+    spec compliance (contribution by ncaq)
+- Add a `Hakyll.Core.Configuration.Configuration.checkHtmlFile` predicate which
+    dictates what files will get link-checked by the `check` command. Default
+    predicate accepts files with .html and .xhtml extensions (contribution by
+    Yoo Chung, with earlier contribution from Michael Orlitzky)
+- Add support for GHC 9.2 (contribution by Laurent P. René de Cotret)
+- Bump `optparse-applicative` upper bound to allow 0.17 (contribution by
+    Alexander Batischev)
+- Bump `pandoc` upper bound to allow 2.19 (contribution by Alexander Batischev)
+- Allow `text` 2.0 (contribution by Alexander Batischev)
+- Bump `vector` upper bound to allow 0.13 (contribution by Alexander Batischev)
+- Allow `aeson` 2.1 (contribution by Alexander Batischev)
+- Bump `fsnotify` upper bound to allow 0.4 (contribution by Alexander Batischev)
+- Bump `resourcet` upper bound to allow 1.3 (contribution by Alexander
+    Batischev)
+- Bump `template-haskell` upper bound to 2.20 (GHC 9.4.3) (contribution by
+    Alexander Batischev)
+- Allow `pandoc` 3.0. Note that the behavior of Hakyll's `readPandocBiblios` and
+    `readPandocBiblio` is different whether pandoc 2 or 3 is installed
+    (contribution by Laurent P. René de Cotret) 
+- Bump `mtl` upper bound to allow 2.3 (contribution by Alexander Batischev)
+- Bump `pandoc` upper bound to allow 3.1 (contribution by Laurent P. René de
+    Cotret)
+- Bump `template-haskell` upper bound to 2.21 and `time` to 1.12 (GHC 9.6.1)
+    (contribution by Laurent P. René de Cotret)
+
 ## Hakyll 4.15.1.1 (2022-01-20)
 
 - Extend the documentation for `Hakyll.Core.Identifier` (contribution by
diff --git a/data/templates/atom.xml b/data/templates/atom.xml
--- a/data/templates/atom.xml
+++ b/data/templates/atom.xml
@@ -6,7 +6,9 @@
     <id>$root$$url$</id>
     <author>
         <name>$authorName$</name>
+        $if(authorEmail)$
         <email>$authorEmail$</email>
+        $endif$
     </author>
     <updated>$updated$</updated>
     $body$
diff --git a/hakyll.cabal b/hakyll.cabal
--- a/hakyll.cabal
+++ b/hakyll.cabal
@@ -1,5 +1,5 @@
 Name:    hakyll
-Version: 4.15.1.1
+Version: 4.16.0.0
 
 Synopsis: A static website compiler library
 Description:
@@ -60,8 +60,10 @@
 Extra-source-files:
   CHANGELOG.md
   tests/data/biblio/chicago.csl
-  tests/data/biblio/cites-meijer.golden
-  tests/data/biblio/cites-multiple.golden
+  tests/data/biblio/cites-meijer-pandoc2.golden
+  tests/data/biblio/cites-meijer-pandoc3.golden
+  tests/data/biblio/cites-multiple-pandoc2.golden
+  tests/data/biblio/cites-multiple-pandoc3.golden
   tests/data/biblio/cites-multiple.markdown
   tests/data/biblio/default.html
   tests/data/biblio/page.markdown
@@ -177,8 +179,8 @@
     Paths_hakyll
 
   Build-Depends:
-    aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.1,
-    base                 >= 4.8      && < 5,
+    aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.2,
+    base                 >= 4.12     && < 5,
     binary               >= 0.5      && < 0.10,
     blaze-html           >= 0.5      && < 0.10,
     blaze-markup         >= 0.5.1    && < 0.9,
@@ -192,31 +194,31 @@
     hashable             >= 1.0      && < 2,
     lifted-async         >= 0.10     && < 1,
     lrucache             >= 1.1.1    && < 1.3,
-    mtl                  >= 1        && < 2.3,
+    mtl                  >= 1        && < 2.4,
     network-uri          >= 2.6      && < 2.7,
-    optparse-applicative >= 0.12     && < 0.17,
+    optparse-applicative >= 0.12     && < 0.18,
     parsec               >= 3.0      && < 3.2,
     process              >= 1.6      && < 1.7,
     random               >= 1.0      && < 1.3,
     regex-tdfa           >= 1.1      && < 1.4,
-    resourcet            >= 1.1      && < 1.3,
+    resourcet            >= 1.1      && < 1.4,
     scientific           >= 0.3.4    && < 0.4,
     tagsoup              >= 0.13.1   && < 0.15,
-    template-haskell     >= 2.14     && < 2.18,
-    text                 >= 0.11     && < 1.3,
-    time                 >= 1.8      && < 1.12,
+    template-haskell     >= 2.14     && < 2.21,
+    text                 >= 0.11     && < 1.3 || >= 2.0 && < 2.1,
+    time                 >= 1.8      && < 1.13,
     time-locale-compat   >= 0.1      && < 0.2,
     unordered-containers >= 0.2      && < 0.3,
-    vector               >= 0.11     && < 0.13,
+    vector               >= 0.11     && < 0.14,
+    wai-app-static       >= 3.1      && < 3.2,
     yaml                 >= 0.8.11   && < 0.12
 
   If flag(previewServer)
     Build-depends:
       wai             >= 3.2   && < 3.3,
       warp            >= 3.2   && < 3.4,
-      wai-app-static  >= 3.1   && < 3.2,
       http-types      >= 0.9   && < 0.13,
-      fsnotify        >= 0.2   && < 0.4
+      fsnotify        >= 0.2   && < 0.5
     Cpp-options:
       -DPREVIEW_SERVER
     Other-modules:
@@ -225,7 +227,7 @@
 
   If flag(watchServer)
     Build-depends:
-      fsnotify        >= 0.2   && < 0.4
+      fsnotify        >= 0.2   && < 0.5
     Cpp-options:
       -DWATCH_SERVER
     Other-modules:
@@ -246,7 +248,7 @@
     Other-Modules:
       Hakyll.Web.Pandoc.Binary
     Build-Depends:
-      pandoc >= 2.11 && < 2.18
+      pandoc >= 2.11 && < 2.20 || >= 3.0 && < 3.2
     Cpp-options:
       -DUSE_PANDOC
 
@@ -284,13 +286,13 @@
     tasty-hunit                >= 0.9  && < 0.11,
     tasty-quickcheck           >= 0.8  && < 0.11,
     -- Copy pasted from hakyll dependencies:
-    aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.1,
-    base                 >= 4.8      && < 5,
+    aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.2,
+    base                 >= 4.12     && < 5,
     bytestring           >= 0.9      && < 0.12,
     containers           >= 0.3      && < 0.7,
     filepath             >= 1.0      && < 1.5,
     tagsoup              >= 0.13.1   && < 0.15,
-    text                 >= 0.11     && < 1.3,
+    text                 >= 0.11     && < 1.3 || >= 2.0 && < 2.1,
     unordered-containers >= 0.2      && < 0.3,
     yaml                 >= 0.8.11   && < 0.12
 
@@ -312,7 +314,10 @@
       Hakyll.Web.Pandoc.FileType.Tests
     Cpp-options:
       -DUSE_PANDOC
+    Build-Depends:
+      pandoc >= 2.11 && < 2.20 || >= 3.0 && < 3.2
 
+
 Executable hakyll-init
   Main-is:          Init.hs
   Ghc-options:      -Wall -threaded
@@ -324,9 +329,9 @@
 
   Build-depends:
     hakyll,
-    base      >= 4   && < 5,
-    directory >= 1.0 && < 1.4,
-    filepath  >= 1.0 && < 1.5
+    base      >= 4.12 && < 5,
+    directory >= 1.0  && < 1.4,
+    filepath  >= 1.0  && < 1.5
 
 Executable hakyll-website
   Main-is:          site.hs
@@ -341,7 +346,7 @@
 
   Build-depends:
     hakyll,
-    base      >= 4     && < 5,
+    base      >= 4.12  && < 5,
     directory >= 1.0   && < 1.4,
     filepath  >= 1.0   && < 1.5,
-    pandoc    >= 2.11  && < 2.18
+    pandoc    >= 2.11  && < 2.20 || >= 3.0 && < 3.2
diff --git a/lib/Hakyll/Check.hs b/lib/Hakyll/Check.hs
--- a/lib/Hakyll/Check.hs
+++ b/lib/Hakyll/Check.hs
@@ -19,15 +19,11 @@
 import           Control.Monad.Trans.Resource (runResourceT)
 import           Data.List                    (isPrefixOf)
 import qualified Data.Map.Lazy                as Map
-#if MIN_VERSION_base(4,9,0)
-import           Data.Semigroup               (Semigroup (..))
-#endif
 import           Network.URI                  (unEscapeString)
 import           System.Directory             (doesDirectoryExist,
                                                doesFileExist)
 import           System.Exit                  (ExitCode (..))
-import           System.FilePath              (takeDirectory, takeExtension,
-                                               (</>))
+import           System.FilePath              (takeDirectory, (</>))
 import qualified Text.HTML.TagSoup            as TS
 
 
@@ -88,7 +84,6 @@
 
 
 --------------------------------------------------------------------------------
-#if MIN_VERSION_base(4,9,0)
 instance Semigroup CheckerWrite where
     (<>) (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
         CheckerWrite (f1 + f2) (o1 + o2)
@@ -96,12 +91,6 @@
 instance Monoid CheckerWrite where
     mempty  = CheckerWrite 0 0
     mappend = (<>)
-#else
-instance Monoid CheckerWrite where
-    mempty                                            = CheckerWrite 0 0
-    mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
-        CheckerWrite (f1 + f2) (o1 + o2)
-#endif
 
 
 --------------------------------------------------------------------------------
@@ -139,7 +128,7 @@
     let htmls =
             [ destinationDirectory config </> file
             | file <- files
-            , takeExtension file == ".html"
+            , checkHtmlFile config file
             ]
 
     forM_ htmls checkFile
diff --git a/lib/Hakyll/Commands.hs b/lib/Hakyll/Commands.hs
--- a/lib/Hakyll/Commands.hs
+++ b/lib/Hakyll/Commands.hs
@@ -124,8 +124,8 @@
 server :: Configuration -> Logger -> String -> Int -> IO ()
 #ifdef PREVIEW_SERVER
 server conf logger host port = do
-    let destination = destinationDirectory conf
-    staticServer logger destination host port
+    let settings = previewSettings conf $ destinationDirectory conf
+    staticServer logger settings host port
 #else
 server _ _ _ _ = previewServerDisabled
 #endif
diff --git a/lib/Hakyll/Core/Compiler/Internal.hs b/lib/Hakyll/Core/Compiler/Internal.hs
--- a/lib/Hakyll/Core/Compiler/Internal.hs
+++ b/lib/Hakyll/Core/Compiler/Internal.hs
@@ -44,9 +44,6 @@
 import           Control.Monad.Except           (MonadError (..))
 import           Data.List.NonEmpty             (NonEmpty (..))
 import qualified Data.List.NonEmpty             as NonEmpty
-#if MIN_VERSION_base(4,9,0)
-import           Data.Semigroup                 (Semigroup (..))
-#endif
 import           Data.Set                       (Set)
 import qualified Data.Set                       as S
 
@@ -97,7 +94,6 @@
 
 
 --------------------------------------------------------------------------------
-#if MIN_VERSION_base(4,9,0)
 instance Semigroup CompilerWrite where
     (<>) (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
         CompilerWrite (d1 ++ d2) (h1 + h2)
@@ -105,12 +101,6 @@
 instance Monoid CompilerWrite where
     mempty  = CompilerWrite [] 0
     mappend = (<>)
-#else
-instance Monoid CompilerWrite where
-    mempty = CompilerWrite [] 0
-    mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
-        CompilerWrite (d1 ++ d2) (h1 + h2)
-#endif
 
 
 --------------------------------------------------------------------------------
@@ -161,7 +151,7 @@
 
 --------------------------------------------------------------------------------
 instance Monad Compiler where
-    return x = compilerResult $ CompilerDone x mempty
+    return = pure
     {-# INLINE return #-}
 
     Compiler c >>= f = Compiler $ \r -> do
@@ -195,7 +185,7 @@
 
 --------------------------------------------------------------------------------
 instance Applicative Compiler where
-    pure x = return x
+    pure x = compilerResult $ CompilerDone x mempty
     {-# INLINE pure #-}
 
     f <*> x = f >>= \f' -> fmap f' x
diff --git a/lib/Hakyll/Core/Configuration.hs b/lib/Hakyll/Core/Configuration.hs
--- a/lib/Hakyll/Core/Configuration.hs
+++ b/lib/Hakyll/Core/Configuration.hs
@@ -11,9 +11,11 @@
 --------------------------------------------------------------------------------
 import           Data.Default     (Default (..))
 import           Data.List        (isPrefixOf, isSuffixOf)
+import qualified Network.Wai.Application.Static as Static
 import           System.Directory (canonicalizePath)
 import           System.Exit      (ExitCode)
-import           System.FilePath  (isAbsolute, normalise, takeFileName, makeRelative)
+import           System.FilePath  (isAbsolute, makeRelative, normalise,
+                                   takeExtension, takeFileName)
 import           System.IO.Error  (catchIOError)
 import           System.Process   (system)
 
@@ -46,6 +48,10 @@
       -- want to use the test, you should use 'shouldIgnoreFile'.
       --
       ignoreFile           :: FilePath -> Bool
+    , -- | Function to determine HTML files whose links are to be checked.
+      --
+      -- In 'defaultConfiguration', files with the @.html@ extension are checked.
+      checkHtmlFile        :: FilePath -> Bool
     , -- | Function to determine files and directories that should not trigger
       -- a rebuild when touched in watch mode.
       --
@@ -87,6 +93,9 @@
       -- One can also override the port as a command line argument:
       -- ./site preview -p 1234
       previewPort          :: Int
+    , -- | Override other settings used by the preview server. Default is
+      -- 'Static.defaultFileServerSettings'.
+      previewSettings      :: FilePath -> Static.StaticSettings
     }
 
 --------------------------------------------------------------------------------
@@ -102,12 +111,14 @@
     , tmpDirectory         = "_cache/tmp"
     , providerDirectory    = "."
     , ignoreFile           = ignoreFile'
+    , checkHtmlFile        = flip elem [".html", ".xhtml"] . takeExtension
     , watchIgnore          = const False
     , deployCommand        = "echo 'No deploy command specified' && exit 1"
     , deploySite           = system . deployCommand
     , inMemoryCache        = True
     , previewHost          = "127.0.0.1"
     , previewPort          = 8000
+    , previewSettings      = Static.defaultFileServerSettings
     }
   where
     ignoreFile' path
diff --git a/lib/Hakyll/Core/Identifier.hs b/lib/Hakyll/Core/Identifier.hs
--- a/lib/Hakyll/Core/Identifier.hs
+++ b/lib/Hakyll/Core/Identifier.hs
@@ -1,6 +1,6 @@
 --------------------------------------------------------------------------------
 -- | An identifier is a type used to uniquely name an item. An identifier
--- is similar to a file path, but can contain additional details (e.g. 
+-- is similar to a file path, but can contain additional details (e.g.
 -- item's version). Examples of identifiers are:
 --
 -- * @posts/foo.markdown@
@@ -24,9 +24,7 @@
 
 --------------------------------------------------------------------------------
 import           Control.DeepSeq     (NFData (..))
-import           Data.List           (intercalate)
-import           System.FilePath     (dropTrailingPathSeparator, splitPath,
-                                      pathSeparator, normalise)
+import           System.FilePath     (normalise)
 
 
 --------------------------------------------------------------------------------
@@ -130,7 +128,7 @@
 
 
 --------------------------------------------------------------------------------
-{- | Parse an identifier from a file path string. For example, 
+{- | Parse an identifier from a file path string. For example,
 
 @
 -- e.g. file on disk: 'posts\/hakyll.md'
@@ -154,7 +152,7 @@
 
 
 --------------------------------------------------------------------------------
-{- | Set or override the version of an identifier in order to specify which version of an 'Hakyll.Core.Item.Item' 
+{- | Set or override the version of an identifier in order to specify which version of an 'Hakyll.Core.Item.Item'
 to 'Hakyll.Core.Compiler.load' from the 'Hakyll.Core.Store.Store'. For example,
 
 @
diff --git a/lib/Hakyll/Core/Identifier/Pattern/Internal.hs b/lib/Hakyll/Core/Identifier/Pattern/Internal.hs
--- a/lib/Hakyll/Core/Identifier/Pattern/Internal.hs
+++ b/lib/Hakyll/Core/Identifier/Pattern/Internal.hs
@@ -1,6 +1,5 @@
 -- | This internal module is mostly here to prevent CPP conflicting with Haskell
 -- comments.
-{-# LANGUAGE CPP #-}
 module Hakyll.Core.Identifier.Pattern.Internal
     ( GlobComponent (..)
     , Pattern (..)
@@ -13,12 +12,6 @@
 
 
 --------------------------------------------------------------------------------
-#if MIN_VERSION_base(4,9,0)
-import           Data.Semigroup         (Semigroup (..))
-#endif
-
-
---------------------------------------------------------------------------------
 import           Hakyll.Core.Identifier
 
 
@@ -78,15 +71,9 @@
 
 
 --------------------------------------------------------------------------------
-#if MIN_VERSION_base(4,9,0)
 instance Semigroup Pattern where
     (<>) = And
 
 instance Monoid Pattern where
     mempty  = Everything
     mappend = (<>)
-#else
-instance Monoid Pattern where
-    mempty  = Everything
-    mappend = And
-#endif
diff --git a/lib/Hakyll/Core/Item.hs b/lib/Hakyll/Core/Item.hs
--- a/lib/Hakyll/Core/Item.hs
+++ b/lib/Hakyll/Core/Item.hs
@@ -12,7 +12,6 @@
 
 --------------------------------------------------------------------------------
 import           Data.Binary                   (Binary (..))
-import           Data.Foldable                 (Foldable (..))
 import           Data.Typeable                 (Typeable)
 import           Prelude                       hiding (foldr)
 
diff --git a/lib/Hakyll/Core/Metadata.hs b/lib/Hakyll/Core/Metadata.hs
--- a/lib/Hakyll/Core/Metadata.hs
+++ b/lib/Hakyll/Core/Metadata.hs
@@ -16,7 +16,9 @@
 
 --------------------------------------------------------------------------------
 import           Control.Monad                  (forM)
+#if !MIN_VERSION_base(4,13,0)
 import           Control.Monad.Fail             (MonadFail)
+#endif
 import           Data.Binary                    (Binary (..), getWord8,
                                                  putWord8, Get)
 #if MIN_VERSION_aeson(2,0,0)
diff --git a/lib/Hakyll/Core/Provider/Metadata.hs b/lib/Hakyll/Core/Provider/Metadata.hs
--- a/lib/Hakyll/Core/Provider/Metadata.hs
+++ b/lib/Hakyll/Core/Provider/Metadata.hs
@@ -19,7 +19,6 @@
 import           Data.List.Extended            (breakWhen)
 import qualified Data.Map                      as M
 import           Data.Maybe                    (fromMaybe)
-import           Data.Monoid                   ((<>))
 import qualified Data.Text                     as T
 import qualified Data.Text.Encoding            as T
 import qualified Data.Yaml                     as Yaml
diff --git a/lib/Hakyll/Core/Routes.hs b/lib/Hakyll/Core/Routes.hs
--- a/lib/Hakyll/Core/Routes.hs
+++ b/lib/Hakyll/Core/Routes.hs
@@ -39,7 +39,6 @@
 
 Take a look at the built-in routes here for detailed usage examples.
 -}
-{-# LANGUAGE CPP        #-}
 {-# LANGUAGE Rank2Types #-}
 module Hakyll.Core.Routes
     ( Routes
@@ -57,9 +56,6 @@
 
 
 --------------------------------------------------------------------------------
-#if MIN_VERSION_base(4,9,0)
-import           Data.Semigroup                 (Semigroup (..))
-#endif
 import           System.FilePath                (replaceExtension, normalise)
 
 
@@ -92,7 +88,6 @@
 
 
 --------------------------------------------------------------------------------
-#if MIN_VERSION_base(4,9,0)
 instance Semigroup Routes where
     (<>) (Routes f) (Routes g) = Routes $ \p id' -> do
         (mfp, um) <- f p id'
@@ -103,15 +98,6 @@
 instance Monoid Routes where
     mempty  = Routes $ \_ _ -> return (Nothing, False)
     mappend = (<>)
-#else
-instance Monoid Routes where
-    mempty = Routes $ \_ _ -> return (Nothing, False)
-    mappend (Routes f) (Routes g) = Routes $ \p id' -> do
-        (mfp, um) <- f p id'
-        case mfp of
-            Nothing -> g p id'
-            Just _  -> return (mfp, um)
-#endif
 
 
 --------------------------------------------------------------------------------
@@ -247,8 +233,8 @@
 source file being processed.
 See 'Hakyll.Core.Identifier.Identifier' for details.
 
-Hint: The name "gsub" comes from a similar function in 
-[R](https://www.r-project.org) and can be read as "globally substituting" 
+Hint: The name "gsub" comes from a similar function in
+[R](https://www.r-project.org) and can be read as "globally substituting"
 (globally in the Unix sense of repeated, not just once).
 
 === __Examples__
diff --git a/lib/Hakyll/Core/Rules.hs b/lib/Hakyll/Core/Rules.hs
--- a/lib/Hakyll/Core/Rules.hs
+++ b/lib/Hakyll/Core/Rules.hs
@@ -1,19 +1,43 @@
 --------------------------------------------------------------------------------
--- | This module provides a declarative DSL in which the user can specify the
--- different rules used to run the compilers.
---
--- The convention is to just list all items in the 'Rules' monad, routes and
--- compilation rules.
---
--- A typical usage example would be:
---
+-- | This module provides a declarative Domain Specific Language (DSL) to
+-- generate a static site by specifying transformation 'Rules' (although the
+-- use case is not limited to static sites).
+-- Each rule normally consists of three parts:
+-- 
+-- 1. Source files (like Markdown files) to process (collected with e.g.
+-- 'match' or 'create').
+-- 2. Compilation steps (like Markdown to HTML) to transform files' content
+-- to some output content (steps are collected within 
+-- 'Hakyll.Core.Compiler.Compiler' and executed with 'compile').
+-- 3. Routing to determine if and where an output content will be written out. 
+-- For a static site this determines under which URL the output content will 
+-- be available (configured with 'route' and 'Hakyll.Core.Routes.Routes').
+-- 
+-- A typical usage example looks as follows:
+-- 
+-- > -- write 'match "posts/**.md"' instead of 'match $ fromGlob "posts/**.md"'
+-- > {-# LANGUAGE OverloadedStrings #-}    
+-- > ...
+-- >
 -- > main = hakyll $ do
--- >     match "posts/*" $ do
--- >         route   (setExtension "html")
--- >         compile someCompiler
+-- >
+-- >     -- Rule 1
+-- >     -- Source files: all Markdown files like 'hakyll.md' in the 'posts' directory
+-- >     match "posts/**.md" $ do          
+-- >         -- Routing: Only replace extension, so '<destination-directory>/posts/hakyll.html'.
+-- >         route $ setExtension "html"   
+-- >         -- Compilation step(s): Transform Markdown file content into HTML output.
+-- >         compile pandocCompiler        
+-- >
+-- >     -- Rule 2
 -- >     match "css/*" $ do
--- >         route   idRoute
+-- >         route idRoute
 -- >         compile compressCssCompiler
+-- >     ...
+-- __The order of rules doesn't matter.__ 
+-- 
+-- See official [Hakyll Rules tutorial](https://jaspervdj.be/hakyll/tutorials/03-rules-routes-compilers.html)
+-- for other examples.
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE OverloadedStrings          #-}
 module Hakyll.Core.Rules
@@ -130,18 +154,122 @@
     setMatches ids env = env {rulesMatches = ids}
 
 --------------------------------------------------------------------------------
-match :: Pattern -> Rules () -> Rules ()
+{- | Add a selection of which source files to process (using the
+given [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming\))) to the
+given remaining 'Rules' value.
+
+The expanded, relative path of the matched source file on disk (relative to the
+project directory configured with 'Hakyll.Core.Configuration.providerDirectory')
+becomes the identifier under which the compilation result is saved to the
+'Hakyll.Core.Store.Store' (in case you want to 'Hakyll.Core.Compiler.load' it
+within another rule).
+See 'Hakyll.Core.Identifier.Identifier' for details.
+
+=== __Examples__
+
+__Select all markdown files within a directory (but without subdirectories)__
+
+> -- Match all Markdown files in the immediate 'posts' directory
+> -- e.g. '<project-directory>/posts/hakyll.md'
+> -- but NOT  '<project-directory>/posts/haskell/monad.md'
+> match "posts/*.md" $ do
+>     route $ setExtension "html"
+>     compile pandocCompiler
+
+__Select all markdown files within a directory (including subdirectories recursively)__
+
+> -- Match all Markdown files in the 'posts' directory and any subdirectory
+> -- e.g. '<project-directory>/posts/hakyll.md'
+> -- and  '<project-directory>/posts/haskell/monad.md'
+> match "posts/**.md" $ do
+>     route $ setExtension "html"
+>     compile pandocCompiler
+See 'Hakyll.Core.Identifier.Pattern.Pattern' or search "glob patterns" online
+for more details. To control where the compilation result will be written out,
+use routing functions like 'Hakyll.Core.Routes.setExtension'.
+-}
+match :: Pattern  -- ^ Glob pattern
+      -> Rules () -- ^ Remaining processing parts
+      -> Rules () -- ^ Result
 match pattern = matchInternal pattern $ getMatches pattern
 
 
 --------------------------------------------------------------------------------
-matchMetadata :: Pattern -> (Metadata -> Bool) -> Rules () -> Rules ()
+{- | Add a selection of which source files to process (using the
+given [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming\)) and
+metadata predicate) to the given remaining 'Rules' values. Same as 'match' but
+allows to filter files further based on their (metadata) content (a file is
+added only when the metadata predicate returns @True@).
+
+The expanded, relative path of the matched source file on disk (relative to the
+project directory configured with 'Hakyll.Core.Configuration.providerDirectory')
+becomes the identifier under which the compilation result is saved to
+the 'Hakyll.Core.Store.Store' (in case you want to 'Hakyll.Core.Compiler.load'
+it within another rule).
+See 'Hakyll.Core.Identifier.Identifier' for details.
+
+=== __Examples__
+__Select all markdown files with enabled draft flag within a directory__
+
+> matchMetadata "posts/*.md" (\meta -> maybe False (=="true") $ lookupString "draft" meta) $ do
+>     route $ setExtension "html"
+>     compile pandocCompiler
+
+For example, the following 'posts/hakyll.md' file with @draft: true@ metadata
+would match:
+
+> ---
+> draft: true
+> title: Hakyll Post
+> ...
+> ---
+> In this blog post we learn about Hakyll ...
+Note that files that have @draft: false@ or no such draft field at all, would
+not match. You can use helper functions like 'Hakyll.Core.Metadata.lookupString'
+to access a specific metadata field, and 'Data.Maybe.maybe' to work with
+'Data.Maybe.Maybe'. To control where the compilation result will be written out,
+use routing functions like 'Hakyll.Core.Routes.setExtension'.
+-}
+matchMetadata :: Pattern             -- ^ Glob pattern
+              -> (Metadata -> Bool)  -- ^ Metadata predicate
+              -> Rules ()            -- ^ Remaining processing parts
+              -> Rules ()            -- ^ Result
 matchMetadata pattern metadataPred = matchInternal pattern $
     map fst . filter (metadataPred . snd) <$> getAllMetadata pattern
 
 
 --------------------------------------------------------------------------------
-create :: [Identifier] -> Rules () -> Rules ()
+{- | Assign (and thereby create) the given identifier(s) to content that has no
+underlying source file on disk. That content must be created within the
+'compile' part of the given remaining 'Rules' value. The given identifier is the
+id under which the compilation is saved to the 'Hakyll.Core.Store.Store' (in
+case you want to 'Hakyll.Core.Compiler.load' it within another rule).
+See 'Hakyll.Core.Identifier.Identifier' for details.
+
+Use this function for example to create an overview page that doesn't have or
+need its content prepared in a file (unlike blog posts which normally have a
+corresponding Markdown source file on disk).
+
+=== __Examples__
+__Create a webpage without an underlying source file__
+
+> -- saved with implicit identifier 'index.html' to Store
+> create ["index.html"] $ do
+>
+>     -- compilation result is written to '<destination-directory>/index.html'
+>     route idRoute
+>
+>     -- create content without a source file from disk
+>     compile $ makeItem ("<h1>Hello World</h1>" :: String)
+Note how you can use 'Hakyll.Core.Compiler.makeItem' to create content inline
+(to be processed as a 'Hakyll.Core.Compiler.Compiler' value) as if that content
+was loaded from a file (as it's the case when using 'match').
+To control where the compilation result will be written out, use routing
+functions like 'Hakyll.Core.Routes.idRoute'.
+-}
+create :: [Identifier] -- ^ Identifiers to assign to created content in next argument
+       -> Rules ()     -- ^ Remaining processing parts that must create content
+       -> Rules ()     -- ^ Resulting rule
 create ids rules = do
     flush
     -- TODO Maybe check if the resources exist and call tellResources on that
@@ -151,7 +279,54 @@
 
 
 --------------------------------------------------------------------------------
-version :: String -> Rules () -> Rules ()
+{- | Add the given version name to the implicit identifier(s) under which the
+compilation result of the given remaining 'Rules' value is saved to the
+'Hakyll.Core.Store.Store'.
+See 'Hakyll.Core.Identifier.Identifier' for details.
+
+Use this wrapper function for example when you need to compile the same source
+file into two or more different results, each with a different version name.
+The version is needed to distinguish between these different compilation results
+in the store, otherwise they would get the same conflicting identifier in the
+store.
+
+Warning:
+__If you add a version name with this function, you need to supply the same name__
+when you 'Hakyll.Core.Compiler.load' the content from the store from
+within another rule.
+
+=== __Examples__
+__Compile source file into differently versioned outputs and load both__
+
+> -- e.g. file on disk: 'posts/hakyll.md'
+>
+> -- saved with implicit identifier ('posts/hakyll.md', no-version)
+> match "posts/*" $ do
+>     route $ setExtension "html"
+>     compile pandocCompiler
+>
+> -- saved with implicit identifier ('posts/hakyll.md', version 'raw')
+> match "posts/*" $ version "raw" $ do
+>     route idRoute
+>     compile getResourceBody
+>
+> -- use compilation results from rules above
+> create ["index.html"] $ do
+>     route idRoute
+>     compile $ do
+>         -- load no-version version
+>         compiledPost <- load (fromFilePath "posts/hakyll.md")
+>         -- load version 'raw'
+>         rawPost <- load . setVersion (Just "raw") $ fromFilePath "posts/hakyll.md"
+>         ...
+Note how a version name is needed to distinguish the unversioned and the "raw"
+version when loading the Hakyll post for the @index.html@ page.
+To control where the compilation result will be written out, use routing
+functions like 'Hakyll.Core.Routes.idRoute' and 'Hakyll.Core.Routes.setExtension'.
+-}
+version :: String   -- ^ Version name to add
+        -> Rules () -- ^ Remaining processing parts
+        -> Rules () -- ^ Result
 version v rules = do
     flush
     Rules $ local setVersion' $ unRules $ rules >> flush
@@ -160,19 +335,116 @@
 
 
 --------------------------------------------------------------------------------
--- | Add a compilation rule to the rules.
---
--- This instructs all resources to be compiled using the given compiler.
-compile :: (Binary a, Typeable a, Writable a) => Compiler (Item a) -> Rules ()
+{- | Add (or replace) the given compilation steps within the given
+'Hakyll.Core.Compiler.Compiler' value to the current 'Rules' value.
+__This functions controls HOW the content within a rule is processed__ (use one
+of the 'match' functions to control WHAT content is processed).
+
+The compilation result is saved to the 'Hakyll.Core.Store.Store' under an
+implicit identifier.
+See 'Hakyll.Core.Identifier.Identifier' for details.
+
+If there's routing attached to the rule where this function is used, the
+compilation result is also written out to a file according to that route.
+See 'route' and 'Hakyll.Core.Routes.Routes' for details.
+
+=== __Examples__
+__Compile Markdown to HTML__
+
+> -- Select all Markdown files in 'posts' directory
+> match "posts/**.md" $ do
+>
+>     route $ setExtension "html"
+>
+>     -- use pandoc to transform Markdown to HTML in a single step
+>     compile pandocCompiler
+Note how we set the content to be processed with
+'Hakyll.Web.Pandoc.pandocCompiler'. The content comes implicitly from the
+matched Markdown files on disk. We don't have to pass that content around
+manually. Every file is processed the same way within this one rule.
+
+To control where the compilation result will be written out, use routing
+functions like 'Hakyll.Core.Routes.setExtension'.
+Here the compilation result of a file like @posts\/hakyll.md@ is written out
+to @posts\/hakyll.html@.
+
+__Compile Markdown to HTML and embed it in a template__
+
+> -- Select all Markdown files in 'posts' directory
+> match "posts/**.md" $ do
+>     route $ setExtension "html"
+>     compile $
+>         pandocCompiler >>=
+>           loadAndApplyTemplate "templates/post.html" defaultContext
+>
+> -- To Hakyll templates are just plain files that have to be processed
+> -- and placed into the store like any other file (but without routing).
+> -- e.g. file on disk: 'templates/post.html'
+> match "templates/*" $ compile templateBodyCompiler
+Note how a Markdown post that is compiled to HTML using
+'Hakyll.Web.Pandoc.pandocCompiler' in a first step and then embedded into
+a HTMl 'Hakyll.Web.Template.Template' in a second step by using
+'Hakyll.Web.Template.loadAndApplyTemplate'.
+We can use templates to control the design and layout of a webpage.
+A template may look as follows:
+
+> <h1>$title$</h1>
+> $body$
+See "Hakyll.Web.Template" to see examples of the templating syntax.
+-}
+compile :: (Binary a, Typeable a, Writable a) => Compiler (Item a) -- ^ How to transform content
+                                              -> Rules ()          -- ^ Result
 compile compiler = Rules $ modify $ \s ->
     s {rulesCompiler = Just (fmap SomeItem compiler)}
 
 
 --------------------------------------------------------------------------------
--- | Add a route.
---
--- This adds a route for all items matching the current pattern.
-route :: Routes -> Rules ()
+{- | Add (or replace) routing in the current 'Rules' value.
+__This functions controls IF and WHERE the compiled results are written out__
+(use one of the 'match' functions to control WHAT content is processed and
+'compile' to control HOW).
+See 'Hakyll.Core.Routes.Routes' and 'Hakyll.Core.Identifier.Identifier' for
+details on how output filepaths are computed.
+
+Hint:
+__If there's no route attached to a rule, the compilation result is not written out__.
+However, the compilation result is saved to the 'Hakyll.Core.Store.Store'
+and can be loaded and used within another rule. This behavior is needed,
+for example, for templates.
+
+=== __Examples__
+__Rules with and without routing__
+
+> -- e.g. file on disk: 'templates/post.html'
+>
+> -- Rule 1 (without routing)
+> match "templates/*" $ do
+>     -- compilation result saved to store with implicit identifier, e.g. 'templates/post.html'
+>     compile templateCompiler
+>
+> -- Rule 2 (with routing)
+> match "posts/**.md" $ do
+>     route $ setExtension "html"
+>     compile $ do
+>        -- load compiled result of other rule with explicit identifier.
+>        postTemplate <- loadBody "templates/post.html"
+>        pandocCompiler >>= applyTemplate postTemplate defaultContext
+Note that we don't set a route in the first rule to avoid writing out our
+compiled templates.
+However, we can still 'Hakyll.Core.Compiler.load' (or
+'Hakyll.Core.Compiler.loadBody') the compiled templates to apply them in a
+second rule.
+The content for 'Hakyll.Web.Template.templateCompiler' comes implicitly from the
+matched template files on disk. We don't have to pass that content around
+manually. See 'match' and 'compile' for details.
+
+To control where a compilation result will be written out (as done in the second
+rule), use routing functions like 'Hakyll.Core.Routes.setExtension'.
+
+See "Hakyll.Web.Template" for examples of templates and the templating syntax.
+-}
+route :: Routes   -- ^ Where to output compilation results
+      -> Rules () -- ^ Result
 route route' = Rules $ modify $ \s -> s {rulesRoute = Just route'}
 
 
diff --git a/lib/Hakyll/Core/Rules/Internal.hs b/lib/Hakyll/Core/Rules/Internal.hs
--- a/lib/Hakyll/Core/Rules/Internal.hs
+++ b/lib/Hakyll/Core/Rules/Internal.hs
@@ -13,14 +13,13 @@
 
 
 --------------------------------------------------------------------------------
+#if !MIN_VERSION_base(4,13,0)
 import           Control.Monad.Fail             (MonadFail)
+#endif
 import           Control.Monad.Reader           (ask)
 import           Control.Monad.RWS              (RWST, runRWST)
 import           Control.Monad.Trans            (liftIO)
 import qualified Data.Map                       as M
-#if MIN_VERSION_base(4,9,0)
-import           Data.Semigroup                 (Semigroup (..))
-#endif
 import           Data.Set                       (Set)
 
 
@@ -57,7 +56,6 @@
 
 
 --------------------------------------------------------------------------------
-#if MIN_VERSION_base(4,9,0)
 instance Semigroup RuleSet where
     (<>) (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
         RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
@@ -65,12 +63,6 @@
 instance Monoid RuleSet where
     mempty  = RuleSet mempty mempty mempty mempty
     mappend = (<>)
-#else
-instance Monoid RuleSet where
-    mempty = RuleSet mempty mempty mempty mempty
-    mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
-        RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
-#endif
 
 
 --------------------------------------------------------------------------------
diff --git a/lib/Hakyll/Core/Store.hs b/lib/Hakyll/Core/Store.hs
--- a/lib/Hakyll/Core/Store.hs
+++ b/lib/Hakyll/Core/Store.hs
@@ -16,14 +16,18 @@
 
 
 --------------------------------------------------------------------------------
-import qualified Data.Hashable        as DH
+import           Control.Monad        (when)
 import           Data.Binary          (Binary, decode, encodeFile)
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.Cache.LRU.IO    as Lru
+import qualified Data.Hashable        as DH
+import qualified Data.IORef           as IORef
 import           Data.List            (intercalate)
+import qualified Data.Map             as Map
 import           Data.Maybe           (isJust)
 import           Data.Typeable        (TypeRep, Typeable, cast, typeOf)
-import           System.Directory     (createDirectoryIfMissing, doesFileExist, removeFile)
+import           System.Directory     (createDirectoryIfMissing, doesFileExist,
+                                       removeFile)
 import           System.FilePath      ((</>))
 import           System.IO            (IOMode (..), hClose, openFile)
 import           System.IO.Error      (catchIOError, ioeSetFileName,
@@ -38,9 +42,11 @@
 --------------------------------------------------------------------------------
 data Store = Store
     { -- | All items are stored on the filesystem
-      storeDirectory :: FilePath
-    , -- | Optionally, items are also kept in-memory
-      storeMap       :: Maybe (Lru.AtomicLRU FilePath Box)
+      storeDirectory  :: FilePath
+    , -- | See 'set'
+      storeWriteAhead :: IORef.IORef (Map.Map String Box)
+      -- | Optionally, items are also kept in-memory
+    , storeMap        :: Maybe (Lru.AtomicLRU FilePath Box)
     }
 
 
@@ -72,10 +78,12 @@
     -> IO Store  -- ^ Store
 new inMemory directory = do
     createDirectoryIfMissing True directory
+    writeAhead <- IORef.newIORef Map.empty
     ref <- if inMemory then Just <$> Lru.newAtomicLRU csize else return Nothing
     return Store
-        { storeDirectory = directory
-        , storeMap       = ref
+        { storeDirectory  = directory
+        , storeWriteAhead = writeAhead
+        , storeMap        = ref
         }
   where
     csize = Just 500
@@ -92,16 +100,16 @@
 --------------------------------------------------------------------------------
 -- | Auxiliary: add an item to the in-memory cache
 cacheInsert :: Typeable a => Store -> String -> a -> IO ()
-cacheInsert (Store _ Nothing)    _   _     = return ()
-cacheInsert (Store _ (Just lru)) key x =
+cacheInsert (Store _ _ Nothing)    _   _     = return ()
+cacheInsert (Store _ _ (Just lru)) key x =
     Lru.insert key (Box x) lru
 
 
 --------------------------------------------------------------------------------
 -- | Auxiliary: get an item from the in-memory cache
 cacheLookup :: forall a. Typeable a => Store -> String -> IO (Result a)
-cacheLookup (Store _ Nothing)    _   = return NotFound
-cacheLookup (Store _ (Just lru)) key = do
+cacheLookup (Store _ _ Nothing)    _   = return NotFound
+cacheLookup (Store _ _ (Just lru)) key = do
     res <- Lru.lookup key lru
     return $ case res of
         Nothing      -> NotFound
@@ -112,15 +120,15 @@
 
 --------------------------------------------------------------------------------
 cacheIsMember :: Store -> String -> IO Bool
-cacheIsMember (Store _ Nothing)    _   = return False
-cacheIsMember (Store _ (Just lru)) key = isJust <$> Lru.lookup key lru
+cacheIsMember (Store _ _ Nothing)    _   = return False
+cacheIsMember (Store _ _ (Just lru)) key = isJust <$> Lru.lookup key lru
 
 
 --------------------------------------------------------------------------------
 -- | Auxiliary: delete an item from the in-memory cache
 cacheDelete :: Store -> String -> IO ()
-cacheDelete (Store _ Nothing)    _   = return ()
-cacheDelete (Store _ (Just lru)) key = do
+cacheDelete (Store _ _ Nothing)    _   = return ()
+cacheDelete (Store _ _ (Just lru)) key = do
     _ <- Lru.delete key lru
     return ()
 
@@ -129,31 +137,61 @@
 -- | Store an item
 set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
 set store identifier value = withStore store "set" (\key path -> do
-    encodeFile path value
+    -- We need to avoid concurrent writes to the filesystem.  Imagine the
+    -- follow scenario:
+    --
+    --  *  We compile multiple posts
+    --  *  All of these fetch some common metadata
+    --  *  This metadata is missing; we fetch it and then store it.
+    --
+    -- To solve this, we skip duplicate writes by tracking their status
+    -- in 'storeWriteAhead'.  Since this set will usually be small, the
+    -- required locking should be fast.  Additionally the actual IO operation
+    -- still happens outside of the locking.
+    first <- IORef.atomicModifyIORef' (storeWriteAhead store) $
+        \wa -> case Map.lookup key wa of
+            Nothing -> (Map.insert key (Box value) wa, True)
+            Just _  -> (wa, False)
+
     cacheInsert store key value
+
+    -- Only the thread that stored the writeAhead should actually write this
+    -- file.  That way, only one thread at a time will try to write this.
+    -- Release the writeAhead value once we're done.
+    when first $ do
+        encodeFile path value
+        IORef.atomicModifyIORef' (storeWriteAhead store) $
+            \wa -> (Map.delete key wa, ())
   ) identifier
 
 
 --------------------------------------------------------------------------------
 -- | Load an item
-get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
+get :: forall a. (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
 get store = withStore store "get" $ \key path -> do
-    -- First check the in-memory map
-    ref <- cacheLookup store key
-    case ref of
-        -- Not found in the map, try the filesystem
-        NotFound -> do
-            exists <- doesFileExist path
-            if not exists
-                -- Not found in the filesystem either
-                then return NotFound
-                -- Found in the filesystem
-                else do
-                    v <- decodeClose path
-                    cacheInsert store key v
-                    return $ Found v
-        -- Found in the in-memory map (or wrong type), just return
-        s -> return s
+    -- Check the writeAhead value
+    writeAhead <- IORef.readIORef $ storeWriteAhead store
+    case Map.lookup key writeAhead of
+        Just (Box x) -> case cast x of
+            Just x' -> pure $ Found x'
+            Nothing -> pure $ WrongType (typeOf (undefined :: a)) (typeOf x)
+        Nothing -> do
+            -- Check the in-memory map
+            ref <- cacheLookup store key
+            case ref of
+                -- Not found in the map, try the filesystem
+                NotFound -> do
+                    exists <- doesFileExist path
+                    if not exists
+                        -- Not found in the filesystem either
+                        then return NotFound
+                        -- Found in the filesystem
+                        else do
+                            v <- decodeClose path
+                            cacheInsert store key v
+                            return $ Found v
+                -- Found in the in-memory map (or wrong type), just return
+                s -> return s
   where
     -- 'decodeFile' from Data.Binary which closes the file ASAP
     decodeClose path = do
@@ -167,8 +205,12 @@
 -- | Strict function
 isMember :: Store -> [String] -> IO Bool
 isMember store = withStore store "isMember" $ \key path -> do
-    inCache <- cacheIsMember store key
-    if inCache then return True else doesFileExist path
+    writeAhead <- IORef.readIORef $ storeWriteAhead store
+    if Map.member key writeAhead
+        then pure True
+        else do
+            inCache <- cacheIsMember store key
+            if inCache then return True else doesFileExist path
 
 
 --------------------------------------------------------------------------------
diff --git a/lib/Hakyll/Core/Util/File.hs b/lib/Hakyll/Core/Util/File.hs
--- a/lib/Hakyll/Core/Util/File.hs
+++ b/lib/Hakyll/Core/Util/File.hs
@@ -10,13 +10,18 @@
 
 
 --------------------------------------------------------------------------------
-import           Control.Concurrent  (threadDelay)
-import           Control.Exception   (SomeException, catch)
-import           Control.Monad       (filterM, forM, when)
+import           Control.Monad       (filterM, forM)
 import           System.Directory    (createDirectoryIfMissing,
-                                      doesDirectoryExist, getDirectoryContents,
-                                      removeDirectoryRecursive, removePathForcibly)
+                                      doesDirectoryExist, getDirectoryContents)
 import           System.FilePath     (takeDirectory, (</>))
+#ifndef mingw32_HOST_OS
+import           Control.Monad       (when)
+import           System.Directory    (removeDirectoryRecursive)
+#else
+import           Control.Concurrent  (threadDelay)
+import           Control.Exception   (SomeException, catch)
+import           System.Directory    (removePathForcibly)
+#endif
 
 
 --------------------------------------------------------------------------------
@@ -66,19 +71,17 @@
 --      https://github.com/haskell/directory/issues/96
 --      https://github.com/haskell/win32/pull/129
 --
--- The hacky solution is to retry deleting directories a few times, 
+-- The hacky solution is to retry deleting directories a few times,
 -- with a delay, on Windows only.
 removeDirectory = retryWithDelay 10 . removePathForcibly
-#endif
 
-
 --------------------------------------------------------------------------------
 -- | Retry an operation at most /n/ times (/n/ must be positive).
 --   If the operation fails the /n/th time it will throw that final exception.
 --   A delay of 100ms is introduced between every retry.
 retryWithDelay :: Int -> IO a -> IO a
-retryWithDelay i x 
+retryWithDelay i x
     | i <= 0    = error "Hakyll.Core.Util.File.retry: retry count must be 1 or more"
     | i == 1    = x
     | otherwise = catch x $ \(_::SomeException) -> threadDelay 100 >> retryWithDelay (i-1) x
-
+#endif
diff --git a/lib/Hakyll/Main.hs b/lib/Hakyll/Main.hs
--- a/lib/Hakyll/Main.hs
+++ b/lib/Hakyll/Main.hs
@@ -1,7 +1,5 @@
 --------------------------------------------------------------------------------
 -- | Module providing the main hakyll function and command-line argument parsing
-{-# LANGUAGE CPP #-}
-
 module Hakyll.Main
     ( -- * Entry points
       hakyll
@@ -15,6 +13,7 @@
     , Command(..)
     , optionParser
     , commandParser
+    , defaultCommands
     , defaultParser
     , defaultParserPure
     , defaultParserPrefs
@@ -29,7 +28,6 @@
 
 
 --------------------------------------------------------------------------------
-import           Data.Monoid               ((<>))
 import qualified Options.Applicative       as OA
 
 
@@ -153,47 +151,49 @@
 
 
 commandParser :: Config.Configuration -> OA.Parser Command
-commandParser conf = OA.subparser $ foldr ((<>) . produceCommand) mempty commands
+commandParser conf =  OA.subparser $ foldr ((<>) . produceCommand) mempty (defaultCommands conf)
     where
-    portParser = OA.option OA.auto (OA.long "port" <> OA.help "Port to listen on" <> OA.value (Config.previewPort conf))
-    hostParser = OA.strOption (OA.long "host" <> OA.help "Host to bind on" <> OA.value (Config.previewHost conf))
-
     produceCommand (c,a,b) = OA.command c (OA.info (OA.helper <*> a) (b))
 
-    commands =
-        [ ( "build"
-          , pure Build <*> OA.flag RunModeNormal RunModePrintOutOfDate (OA.long "dry-run" <> OA.help "Don't build, only print out-of-date items")
-          , OA.fullDesc <> OA.progDesc "Generate the site"
-          )
-        , ( "check"
-          , pure Check <*> OA.switch (OA.long "internal-links" <> OA.help "Check internal links only")
-          , OA.fullDesc <> OA.progDesc "Validate the site output"
-          )
-        , ( "clean"
-          , pure Clean
-          , OA.fullDesc <> OA.progDesc "Clean up and remove cache"
-          )
-        , ( "deploy"
-          , pure Deploy
-          , OA.fullDesc <> OA.progDesc "Upload/deploy your site"
-           )
-        , ( "preview"
-          , pure Preview <*> portParser
-          , OA.fullDesc <> OA.progDesc "[DEPRECATED] Please use the watch command"
-          )
-        , ( "rebuild"
-          , pure Rebuild
-          , OA.fullDesc <> OA.progDesc "Clean and build again"
-          )
-        , ( "server"
-          , pure Server <*> hostParser <*> portParser
-          , OA.fullDesc <> OA.progDesc "Start a preview server"
-          )
-        , ( "watch"
-          , pure Watch <*> hostParser <*> portParser <*> OA.switch (OA.long "no-server" <> OA.help "Disable the built-in web server")
-          , OA.fullDesc <> OA.progDesc "Autocompile on changes and start a preview server.  You can watch and recompile without running a server with --no-server."
-          )
-        ]
+
+defaultCommands :: Config.Configuration -> [(String, OA.Parser Command, OA.InfoMod a)]
+defaultCommands conf =
+    [ ( "build"
+      , pure Build <*> OA.flag RunModeNormal RunModePrintOutOfDate (OA.long "dry-run" <> OA.help "Don't build, only print out-of-date items")
+      , OA.fullDesc <> OA.progDesc "Generate the site"
+      )
+    , ( "check"
+      , pure Check <*> OA.switch (OA.long "internal-links" <> OA.help "Check internal links only")
+      , OA.fullDesc <> OA.progDesc "Validate the site output"
+      )
+    , ( "clean"
+      , pure Clean
+      , OA.fullDesc <> OA.progDesc "Clean up and remove cache"
+      )
+    , ( "deploy"
+      , pure Deploy
+      , OA.fullDesc <> OA.progDesc "Upload/deploy your site"
+       )
+    , ( "preview"
+      , pure Preview <*> portParser
+      , OA.fullDesc <> OA.progDesc "[DEPRECATED] Please use the watch command"
+      )
+    , ( "rebuild"
+      , pure Rebuild
+      , OA.fullDesc <> OA.progDesc "Clean and build again"
+      )
+    , ( "server"
+      , pure Server <*> hostParser <*> portParser
+      , OA.fullDesc <> OA.progDesc "Start a preview server"
+      )
+    , ( "watch"
+      , pure Watch <*> hostParser <*> portParser <*> OA.switch (OA.long "no-server" <> OA.help "Disable the built-in web server")
+      , OA.fullDesc <> OA.progDesc "Autocompile on changes and start a preview server.  You can watch and recompile without running a server with --no-server."
+      )
+    ]
+    where
+    portParser = OA.option OA.auto (OA.long "port" <> OA.help "Port to listen on" <> OA.value (Config.previewPort conf))
+    hostParser = OA.strOption (OA.long "host" <> OA.help "Host to bind on" <> OA.value (Config.previewHost conf))
 
 
 --------------------------------------------------------------------------------
diff --git a/lib/Hakyll/Preview/Poll.hs b/lib/Hakyll/Preview/Poll.hs
--- a/lib/Hakyll/Preview/Poll.hs
+++ b/lib/Hakyll/Preview/Poll.hs
@@ -18,7 +18,7 @@
 
 #ifdef mingw32_HOST_OS
 import           Control.Concurrent             (threadDelay)
-import           Control.Exception              (IOException, throw, try)
+import           Control.Exception              (IOException, try)
 import           System.Directory               (doesFileExist)
 import           System.Exit                    (exitFailure)
 import           System.FilePath                ((</>))
diff --git a/lib/Hakyll/Preview/Server.hs b/lib/Hakyll/Preview/Server.hs
--- a/lib/Hakyll/Preview/Server.hs
+++ b/lib/Hakyll/Preview/Server.hs
@@ -18,15 +18,14 @@
 import qualified Hakyll.Core.Logger    as Logger
 
 staticServer :: Logger               -- ^ Logger
-             -> FilePath             -- ^ Directory to serve
+             -> Static.StaticSettings -- ^ Static file server settings
              -> String               -- ^ Host to bind on
              -> Int                  -- ^ Port to listen on
              -> IO ()                -- ^ Blocks forever
-staticServer logger directory host port = do
+staticServer logger settings host port = do
     Logger.header logger $ "Listening on http://" ++ host ++ ":" ++ show port
     Logger.flush logger -- ensure this line is logged before Warp errors
-    Warp.runSettings warpSettings $
-        Static.staticApp (Static.defaultFileServerSettings directory)
+    Warp.runSettings warpSettings $ Static.staticApp settings
   where
     warpSettings = Warp.setLogger noLog
         $ Warp.setHost (fromString host)
diff --git a/lib/Hakyll/Web/Feed.hs b/lib/Hakyll/Web/Feed.hs
--- a/lib/Hakyll/Web/Feed.hs
+++ b/lib/Hakyll/Web/Feed.hs
@@ -73,7 +73,8 @@
       feedDescription :: String
     , -- | Name of the feed author.
       feedAuthorName  :: String
-    , -- | Email of the feed author.
+    , -- | Email of the feed author.  Set this to the empty String to leave out
+      -- the email address.
       feedAuthorEmail :: String
     , -- | Absolute root URL of the feed site (e.g. @http://jaspervdj.be@)
       feedRoot        :: String
@@ -102,7 +103,7 @@
         [ itemContext
         , constField "root" (feedRoot config)
         , constField "authorName"  (feedAuthorName config)
-        , constField "authorEmail" (feedAuthorEmail config)
+        , emailField
         ]
 
     feedContext = mconcat
@@ -110,7 +111,7 @@
          , constField "title"       (feedTitle config)
          , constField "description" (feedDescription config)
          , constField "authorName"  (feedAuthorName config)
-         , constField "authorEmail" (feedAuthorEmail config)
+         , emailField
          , constField "root"        (feedRoot config)
          , urlField   "url"
          , updatedField
@@ -124,6 +125,10 @@
         (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
             StringField s -> return s
             _             -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
+
+    emailField = case feedAuthorEmail config of
+        ""    -> missingField
+        email -> constField "authorEmail" email
 
 --------------------------------------------------------------------------------
 -- | Render an RSS feed using given templates with a number of items.
diff --git a/lib/Hakyll/Web/Html.hs b/lib/Hakyll/Web/Html.hs
--- a/lib/Hakyll/Web/Html.hs
+++ b/lib/Hakyll/Web/Html.hs
@@ -4,6 +4,7 @@
     ( -- * Generic
       withTags
     , withTagList
+    , withTagListM
 
       -- * Headers
     , demoteHeaders
@@ -23,13 +24,14 @@
 
 
 --------------------------------------------------------------------------------
+import           Control.Monad                   (void)
+import           Control.Monad.Identity          (Identity(runIdentity))
 import           Data.Char                       (digitToInt, intToDigit,
                                                   isDigit, toLower)
 import           Data.Either                     (fromRight)
 import           Data.List                       (isPrefixOf, intercalate)
 import           Data.Maybe                      (fromMaybe)
 import qualified Data.Set                        as S
-import           Control.Monad                   (void)
 import           System.FilePath                 (joinPath, splitPath,
                                                   takeDirectory)
 import           Text.Blaze.Html                 (toHtml)
@@ -51,7 +53,12 @@
 
 -- | Map over all tags (as list) in the document
 withTagList :: ([TS.Tag String] -> [TS.Tag String]) -> String -> String
-withTagList f = renderTags' . f . parseTags'
+withTagList f = runIdentity . withTagListM (pure . f)
+
+-- | Map over all tags (as list) in the document, in a monad
+withTagListM :: Monad m => ([TS.Tag String] -> m [TS.Tag String]) -> String -> m String
+withTagListM f = fmap renderTags' . f . parseTags'
+{-# INLINE withTagListM #-}
 
 --------------------------------------------------------------------------------
 -- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
diff --git a/lib/Hakyll/Web/Meta/TwitterCard.hs b/lib/Hakyll/Web/Meta/TwitterCard.hs
--- a/lib/Hakyll/Web/Meta/TwitterCard.hs
+++ b/lib/Hakyll/Web/Meta/TwitterCard.hs
@@ -59,5 +59,5 @@
 twitterCardTemplateString :: String
 twitterCardTemplateString =
   "<meta name=\"twitter:card\" content=\"summary\" />\
-  \$if(twitter-creator)$<meta property=\"twitter:creator\" content=\"$twitter-creator$\" />$endif$\
-  \$if(twitter-site)$<meta property=\"twitter:site\" content=\"$twitter-site$\" />$endif$"
+  \$if(twitter-creator)$<meta name=\"twitter:creator\" content=\"$twitter-creator$\" />$endif$\
+  \$if(twitter-site)$<meta name=\"twitter:site\" content=\"$twitter-site$\" />$endif$"
diff --git a/lib/Hakyll/Web/Pandoc/Biblio.hs b/lib/Hakyll/Web/Pandoc/Biblio.hs
--- a/lib/Hakyll/Web/Pandoc/Biblio.hs
+++ b/lib/Hakyll/Web/Pandoc/Biblio.hs
@@ -10,7 +10,8 @@
 -- source into a 'Pandoc' type and need to add processing for the bibliography,
 -- you can use 'processPandocBiblio' instead.
 -- 'pandocBiblioCompiler' is a convenience wrapper which works like 'pandocCompiler',
--- but also takes paths to compiled bibliography and csl files.
+-- but also takes paths to compiled bibliography and csl files;
+-- 'pandocBibliosCompiler' is similar but instead takes a glob pattern for bib files.
 {-# LANGUAGE Arrows                     #-}
 {-# LANGUAGE DeriveDataTypeable         #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -25,6 +26,7 @@
     , processPandocBiblio
     , processPandocBiblios
     , pandocBiblioCompiler
+    , pandocBibliosCompiler
     ) where
 
 
@@ -40,6 +42,7 @@
 import           Hakyll.Core.Compiler
 import           Hakyll.Core.Compiler.Internal
 import           Hakyll.Core.Identifier
+import           Hakyll.Core.Identifier.Pattern (fromGlob)
 import           Hakyll.Core.Item
 import           Hakyll.Core.Writable
 import           Hakyll.Web.Pandoc
@@ -131,7 +134,7 @@
             , Pandoc.FileInfo zeroTime . unBiblio . itemBody $ y
             )
           )
-          [0 ..]
+          [0 :: Integer ..]
           biblios
 
         stFiles = foldr ((.) . uncurry Pandoc.insertInFileTree)
@@ -167,6 +170,19 @@
     bib <- load $ fromFilePath bibFileName
     liftM writePandoc
         (getResourceBody >>= readPandocBiblio ropt csl bib)
+    where ropt = defaultHakyllReaderOptions
+            { -- The following option enables citation rendering
+              readerExtensions = enableExtension Ext_citations $ readerExtensions defaultHakyllReaderOptions
+            }
+
+--------------------------------------------------------------------------------
+-- | Compiles a markdown file via Pandoc. Requires the .csl and .bib files to be known to the compiler via match statements.
+pandocBibliosCompiler :: String -> String -> Compiler (Item String)
+pandocBibliosCompiler cslFileName bibFileName = do
+    csl  <- load    $ fromFilePath cslFileName
+    bibs <- loadAll $ fromGlob bibFileName
+    liftM writePandoc
+        (getResourceBody >>= readPandocBiblios ropt csl bibs)
     where ropt = defaultHakyllReaderOptions
             { -- The following option enables citation rendering
               readerExtensions = enableExtension Ext_citations $ readerExtensions defaultHakyllReaderOptions
diff --git a/lib/Hakyll/Web/Redirect.hs b/lib/Hakyll/Web/Redirect.hs
--- a/lib/Hakyll/Web/Redirect.hs
+++ b/lib/Hakyll/Web/Redirect.hs
@@ -6,7 +6,6 @@
     , createRedirects
     ) where
 
-import           Control.Applicative    ((<$>))
 import           Control.Monad          (forM_, when)
 import           Data.Binary            (Binary (..))
 import           Data.List              (sort, group)
diff --git a/lib/Hakyll/Web/Tags.hs b/lib/Hakyll/Web/Tags.hs
--- a/lib/Hakyll/Web/Tags.hs
+++ b/lib/Hakyll/Web/Tags.hs
@@ -58,6 +58,7 @@
     , tagsField
     , tagsFieldWith
     , categoryField
+    , simpleRenderLink
     , sortTagsBy
     , caseInsensitiveTags
     ) where
@@ -283,7 +284,7 @@
 renderTagList = renderTags makeLink (intercalate ", ")
   where
     makeLink tag url count _ _ = renderHtml $
-        H.a ! A.href (toValue url) $ toHtml (tag ++ " (" ++ show count ++ ")")
+        H.a ! A.href (toValue url) ! A.rel "tag" $ toHtml (tag ++ " (" ++ show count ++ ")")
 
 
 --------------------------------------------------------------------------------
@@ -335,6 +336,7 @@
 simpleRenderLink tag (Just filePath) = Just $
     H.a ! A.title (H.stringValue ("All pages tagged '"++tag++"'."))
         ! A.href (toValue $ toUrl filePath)
+        ! (A.rel "tag")
         $ toHtml tag
 
 
diff --git a/lib/Hakyll/Web/Template/Context.hs b/lib/Hakyll/Web/Template/Context.hs
--- a/lib/Hakyll/Web/Template/Context.hs
+++ b/lib/Hakyll/Web/Template/Context.hs
@@ -54,11 +54,10 @@
 --------------------------------------------------------------------------------
 import           Control.Applicative           (Alternative (..))
 import           Control.Monad                 (msum)
+#if !MIN_VERSION_base(4,13,0)
 import           Control.Monad.Fail            (MonadFail)
-import           Data.List                     (intercalate, tails)
-#if MIN_VERSION_base(4,9,0)
-import           Data.Semigroup                (Semigroup (..))
 #endif
+import           Data.List                     (intercalate, tails)
 import           Data.Time.Clock               (UTCTime (..))
 import           Data.Time.Format              (formatTime, parseTimeM)
 import           Data.Time.Locale.Compat       (TimeLocale, defaultTimeLocale)
@@ -106,18 +105,12 @@
 --------------------------------------------------------------------------------
 -- | Tries to find a key in the left context,
 -- or when that fails in the right context.
-#if MIN_VERSION_base(4,9,0)
 instance Semigroup (Context a) where
     (<>) (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
 
 instance Monoid (Context a) where
     mempty  = missingField
     mappend = (<>)
-#else
-instance Monoid (Context a) where
-    mempty                          = missingField
-    mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
-#endif
 
 
 --------------------------------------------------------------------------------
@@ -326,6 +319,8 @@
 --
 --   * @2010-09-06@
 --
+--   * @06.09.2010@
+--
 --   * @September 06, 2010@
 --
 -- Alternatively, when the metadata has a field called @path@ in a
@@ -391,6 +386,7 @@
         , "%Y-%m-%d %H:%M:%S%Z"
         , "%Y-%m-%d %H:%M:%S"
         , "%Y-%m-%d"
+        , "%d.%m.%Y"
         , "%B %e, %Y %l:%M %p"
         , "%B %e, %Y"
         , "%b %d, %Y"
diff --git a/lib/Hakyll/Web/Template/Internal/Element.hs b/lib/Hakyll/Web/Template/Internal/Element.hs
--- a/lib/Hakyll/Web/Template/Internal/Element.hs
+++ b/lib/Hakyll/Web/Template/Internal/Element.hs
@@ -12,7 +12,7 @@
 
 
 --------------------------------------------------------------------------------
-import           Control.Applicative     ((<|>), (<*))
+import           Control.Applicative     ((<|>))
 import           Control.Monad           (void)
 import           Control.Arrow           (left)
 import           Data.Binary             (Binary, get, getWord8, put, putWord8)
diff --git a/lib/Hakyll/Web/Template/List.hs b/lib/Hakyll/Web/Template/List.hs
--- a/lib/Hakyll/Web/Template/List.hs
+++ b/lib/Hakyll/Web/Template/List.hs
@@ -7,6 +7,7 @@
 -- * An image list in a gallery
 --
 -- * A sitemap
+{-# LANGUAGE CPP           #-}
 {-# LANGUAGE TupleSections #-}
 module Hakyll.Web.Template.List
     ( applyTemplateList
@@ -20,7 +21,9 @@
 
 --------------------------------------------------------------------------------
 import           Control.Monad               (liftM)
+#if !MIN_VERSION_base(4,13,0)
 import           Control.Monad.Fail          (MonadFail)
+#endif
 import           Data.List                   (intersperse, sortBy)
 import           Data.Ord                    (comparing)
 import           Data.Time.Locale.Compat     (defaultTimeLocale)
diff --git a/tests/Hakyll/Core/UnixFilter/Tests.hs b/tests/Hakyll/Core/UnixFilter/Tests.hs
--- a/tests/Hakyll/Core/UnixFilter/Tests.hs
+++ b/tests/Hakyll/Core/UnixFilter/Tests.hs
@@ -22,23 +22,22 @@
 
 --------------------------------------------------------------------------------
 tests :: TestTree
-tests = testGroup "Hakyll.Core.UnixFilter.Tests"
+tests = testGroup "Hakyll.Core.UnixFilter.Tests" $
 #ifdef mingw32_HOST_OS
-    -- The `rev` utility is not present by default on Windows
-    [ testCase "unixFilter false" unixFilterFalse
-    , testCase "unixFilter error" unixFilterError
-    ]
+    [] -- The `rev` utility is not present by default on Windows
 #else
-    [ testCase "unixFilter rev"   unixFilterRev
-    , testCase "unixFilter false" unixFilterFalse
+    [ testCase "unixFilter rev"   unixFilterRev ]
+#endif
+    ++
+    [ testCase "unixFilter false" unixFilterFalse
     , testCase "unixFilter error" unixFilterError
     ]
-#endif
 
 testMarkdown :: Identifier
 testMarkdown = "russian.md"
 
 --------------------------------------------------------------------------------
+#ifndef mingw32_HOST_OS
 unixFilterRev :: H.Assertion
 unixFilterRev = do
     store    <- newTestStore
@@ -50,6 +49,7 @@
   where
     compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
     rev      = map reverse . lines
+#endif
 
 
 --------------------------------------------------------------------------------
diff --git a/tests/Hakyll/Web/Pandoc/Biblio/Tests.hs b/tests/Hakyll/Web/Pandoc/Biblio/Tests.hs
--- a/tests/Hakyll/Web/Pandoc/Biblio/Tests.hs
+++ b/tests/Hakyll/Web/Pandoc/Biblio/Tests.hs
@@ -1,5 +1,6 @@
 --------------------------------------------------------------------------------
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP               #-}
 module Hakyll.Web.Pandoc.Biblio.Tests
     ( tests
     ) where
@@ -16,7 +17,6 @@
 --------------------------------------------------------------------------------
 import           Hakyll
 import           Hakyll.Core.Runtime
-import           Hakyll.Web.Pandoc.Biblio
 import qualified Hakyll.Core.Logger         as Logger
 import           TestSuite.Util
 
@@ -38,7 +38,7 @@
 goldenTest01 =
     goldenVsString
         "biblio01"
-        (goldenTestsDataDir </> "cites-meijer.golden")
+        (goldenTestsDataDir </> goldenTest)
         (do
             -- Code lifted from https://github.com/jaspervdj/hakyll-citeproc-example.
             logger <- Logger.new Logger.Error
@@ -66,12 +66,19 @@
             cleanTestEnv
 
             return output)
+    
+    where 
+        goldenTest = 
+            if pandocMajorVersion == 2
+                then "cites-meijer-pandoc2.golden" 
+                else "cites-meijer-pandoc3.golden"
 
+
 goldenTest02 :: TestTree
 goldenTest02 =
     goldenVsString
         "biblio02"
-        (goldenTestsDataDir </> "cites-meijer.golden")
+        (goldenTestsDataDir </> goldenTest)
         (do
             -- Code lifted from https://github.com/jaspervdj/hakyll-citeproc-example.
             logger <- Logger.new Logger.Error
@@ -99,12 +106,17 @@
             cleanTestEnv
 
             return output)
+    where
+        goldenTest = 
+            if pandocMajorVersion == 2
+                then "cites-meijer-pandoc2.golden" 
+                else "cites-meijer-pandoc3.golden"
 
 goldenTest03 :: TestTree
 goldenTest03 =
     goldenVsString
         "biblio03"
-        (goldenTestsDataDir </> "cites-multiple.golden")
+        (goldenTestsDataDir </> goldenTest)
         (do
             -- Code lifted from https://github.com/jaspervdj/hakyll-citeproc-example.
             logger <- Logger.new Logger.Error
@@ -134,3 +146,16 @@
             cleanTestEnv
 
             return output)
+    where
+        goldenTest = 
+            if pandocMajorVersion == 2 
+                then "cites-multiple-pandoc2.golden" 
+                else "cites-multiple-pandoc3.golden"
+
+--------------------------------------------------------------------------------
+pandocMajorVersion :: Int
+#if MIN_VERSION_pandoc(3,0,0)
+pandocMajorVersion = 3
+#else
+pandocMajorVersion = 2   
+#endif
diff --git a/tests/TestSuite/Util.hs b/tests/TestSuite/Util.hs
--- a/tests/TestSuite/Util.hs
+++ b/tests/TestSuite/Util.hs
@@ -15,7 +15,6 @@
 
 --------------------------------------------------------------------------------
 import           Data.List                     (intercalate, isInfixOf)
-import           Data.Monoid                   (mempty)
 import qualified Data.Set                      as S
 import           Test.Tasty
 import           Test.Tasty.HUnit
diff --git a/tests/data/biblio/cites-meijer-pandoc2.golden b/tests/data/biblio/cites-meijer-pandoc2.golden
new file mode 100644
--- /dev/null
+++ b/tests/data/biblio/cites-meijer-pandoc2.golden
@@ -0,0 +1,16 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>This page cites a paper.</title>
+    </head>
+    <body>
+        <h1>This page cites a paper.</h1>
+        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>
+<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">
+<div id="ref-meijer1991functional" class="csl-entry" role="doc-biblioentry">
+Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.
+</div>
+</div>
+    </body>
+</html>
diff --git a/tests/data/biblio/cites-meijer-pandoc3.golden b/tests/data/biblio/cites-meijer-pandoc3.golden
new file mode 100644
--- /dev/null
+++ b/tests/data/biblio/cites-meijer-pandoc3.golden
@@ -0,0 +1,16 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>This page cites a paper.</title>
+    </head>
+    <body>
+        <h1>This page cites a paper.</h1>
+        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>
+<div id="refs" class="references csl-bib-body hanging-indent" role="list">
+<div id="ref-meijer1991functional" class="csl-entry" role="listitem">
+Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.
+</div>
+</div>
+    </body>
+</html>
diff --git a/tests/data/biblio/cites-meijer.golden b/tests/data/biblio/cites-meijer.golden
deleted file mode 100644
--- a/tests/data/biblio/cites-meijer.golden
+++ /dev/null
@@ -1,16 +0,0 @@
-<!doctype html>
-<html lang="en">
-    <head>
-        <meta charset="utf-8">
-        <title>This page cites a paper.</title>
-    </head>
-    <body>
-        <h1>This page cites a paper.</h1>
-        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>
-<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">
-<div id="ref-meijer1991functional" class="csl-entry" role="doc-biblioentry">
-Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.
-</div>
-</div>
-    </body>
-</html>
diff --git a/tests/data/biblio/cites-multiple-pandoc2.golden b/tests/data/biblio/cites-multiple-pandoc2.golden
new file mode 100644
--- /dev/null
+++ b/tests/data/biblio/cites-multiple-pandoc2.golden
@@ -0,0 +1,20 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>This page cites a paper and a book.</title>
+    </head>
+    <body>
+        <h1>This page cites a paper and a book.</h1>
+        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>
+<p>And also a book <span class="citation" data-cites="lipovaca2012">(Lipovača 2012)</span>.</p>
+<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">
+<div id="ref-lipovaca2012" class="csl-entry" role="doc-biblioentry">
+Lipovača, Miran. 2012. <em>Learn You a Haskell for Great Good! A Beginner’s Guide</em>. San Francisco, CA: No Starch Press.
+</div>
+<div id="ref-meijer1991functional" class="csl-entry" role="doc-biblioentry">
+Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.
+</div>
+</div>
+    </body>
+</html>
diff --git a/tests/data/biblio/cites-multiple-pandoc3.golden b/tests/data/biblio/cites-multiple-pandoc3.golden
new file mode 100644
--- /dev/null
+++ b/tests/data/biblio/cites-multiple-pandoc3.golden
@@ -0,0 +1,20 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>This page cites a paper and a book.</title>
+    </head>
+    <body>
+        <h1>This page cites a paper and a book.</h1>
+        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>
+<p>And also a book <span class="citation" data-cites="lipovaca2012">(Lipovača 2012)</span>.</p>
+<div id="refs" class="references csl-bib-body hanging-indent" role="list">
+<div id="ref-lipovaca2012" class="csl-entry" role="listitem">
+Lipovača, Miran. 2012. <em>Learn You a Haskell for Great Good! A Beginner’s Guide</em>. San Francisco, CA: No Starch Press.
+</div>
+<div id="ref-meijer1991functional" class="csl-entry" role="listitem">
+Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.
+</div>
+</div>
+    </body>
+</html>
diff --git a/tests/data/biblio/cites-multiple.golden b/tests/data/biblio/cites-multiple.golden
deleted file mode 100644
--- a/tests/data/biblio/cites-multiple.golden
+++ /dev/null
@@ -1,20 +0,0 @@
-<!doctype html>
-<html lang="en">
-    <head>
-        <meta charset="utf-8">
-        <title>This page cites a paper and a book.</title>
-    </head>
-    <body>
-        <h1>This page cites a paper and a book.</h1>
-        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>
-<p>And also a book <span class="citation" data-cites="lipovaca2012">(Lipovača 2012)</span>.</p>
-<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">
-<div id="ref-lipovaca2012" class="csl-entry" role="doc-biblioentry">
-Lipovača, Miran. 2012. <em>Learn You a Haskell for Great Good! A Beginner’s Guide</em>. San Francisco, CA: No Starch Press.
-</div>
-<div id="ref-meijer1991functional" class="csl-entry" role="doc-biblioentry">
-Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.
-</div>
-</div>
-    </body>
-</html>
diff --git a/web/site.hs b/web/site.hs
--- a/web/site.hs
+++ b/web/site.hs
@@ -1,12 +1,10 @@
 --------------------------------------------------------------------------------
 {-# LANGUAGE OverloadedStrings #-}
-import           Control.Arrow    (second)
 import           Control.Monad    (filterM, forM_)
-import           Data.List        (isPrefixOf, sortBy)
+import           Data.List        (sortBy)
 import           Data.Ord         (comparing)
 import           Hakyll
 import           System.Directory (copyFile)
-import           System.FilePath  (dropTrailingPathSeparator, splitPath)
 import qualified Text.Pandoc      as Pandoc
 
 
