diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,28 @@
 
 # Releases
 
+## Hakyll 4.16.2.1 (2024-06-02)
+
+- Fix "thread blocked indefinitely in an MVar operation" errors caused by
+    exceptions produced by `Items` (#1014) (contribution by Jasper Van der Jeugt)
+- Improve `+watchServer` and `+previewServer` error messages: they now explain
+    *how* to enable the flag when the user tries to run `watch`/`preview`
+    without them (#1022) (contribution by Jasper Van der Jeugt)
+- Assorted fixes and improvements to documentation (contributions by Alexander
+    Batischev, Adrien, Tanya Bouman)
+- Bump `bytestring` upper bound to 0.13 (contribution by Alexander Batischev)
+- Bump `deepseq` upper bound to 1.6 (contribution by Laurent P. René de Cotret)
+- Bump `template-haskell` upper bound to 2.22 (contribution by Laurent P. René
+    de Cotret)
+- Bump `text` upper bound to 2.2 (contribution by Laurent P. René de Cotret)
+- Bump `file-embed` upper bound to 0.0.17 (contribution by Alexander Batischev)
+- Bump `warp` upper bound to 3.5 (contribution by Alexander Batischev)
+- Bump `filepath` upper bound to 1.6 (contribution by Alexander Batischev)
+- Bump `containers` upper bound to 0.8 (contribution by Andreas Abel)
+- Bump `time` upper bound to 1.15 (contribution by Andreas Abel)
+- Bump `QuickCheck` upper bound to 2.16 (contribution by Andreas Abel)
+- Bump `pandoc` upper bound to 3.3 (contribution by Alexander Batischev)
+
 ## Hakyll 4.16.2.0 (2023-09-20)
 
 - Add errors for multiple `match`es that are routed to the same file
diff --git a/hakyll.cabal b/hakyll.cabal
--- a/hakyll.cabal
+++ b/hakyll.cabal
@@ -1,5 +1,5 @@
 Name:    hakyll
-Version: 4.16.2.0
+Version: 4.16.2.1
 
 Synopsis: A static website compiler library
 Description:
@@ -104,14 +104,17 @@
 Flag previewServer
   Description: Include the preview server
   Default:     True
+  Manual:      True
 
 Flag watchServer
   Description: Include the watch server
   Default:     True
+  Manual:      True
 
 Flag checkExternal
   Description: Include external link checking
   Default:     True
+  Manual:      True
 
 Flag buildWebsite
   Description: Build the hakyll website
@@ -188,13 +191,13 @@
     binary               >= 0.5      && < 0.10,
     blaze-html           >= 0.5      && < 0.10,
     blaze-markup         >= 0.5.1    && < 0.9,
-    bytestring           >= 0.9      && < 0.12,
-    containers           >= 0.3      && < 0.7,
+    bytestring           >= 0.9      && < 0.13,
+    containers           >= 0.3      && < 0.8,
     data-default         >= 0.4      && < 0.8,
-    deepseq              >= 1.3      && < 1.5,
+    deepseq              >= 1.3      && < 1.6,
     directory            >= 1.2.7.0  && < 1.4,
-    file-embed           >= 0.0.10.1 && < 0.0.16,
-    filepath             >= 1.0      && < 1.5,
+    file-embed           >= 0.0.10.1 && < 0.0.17,
+    filepath             >= 1.0      && < 1.6,
     hashable             >= 1.0      && < 2,
     lrucache             >= 1.1.1    && < 1.3,
     mtl                  >= 1        && < 2.4,
@@ -207,9 +210,9 @@
     resourcet            >= 1.1      && < 1.4,
     scientific           >= 0.3.4    && < 0.4,
     tagsoup              >= 0.13.1   && < 0.15,
-    template-haskell     >= 2.14     && < 2.21,
-    text                 >= 0.11     && < 1.3 || >= 2.0 && < 2.1,
-    time                 >= 1.8      && < 1.13,
+    template-haskell     >= 2.14     && < 2.22,
+    text                 >= 0.11     && < 1.3 || >= 2.0 && < 2.2,
+    time                 >= 1.8      && < 1.15,
     time-locale-compat   >= 0.1      && < 0.2,
     unordered-containers >= 0.2      && < 0.3,
     vector               >= 0.11     && < 0.14,
@@ -219,7 +222,7 @@
   If flag(previewServer)
     Build-depends:
       wai             >= 3.2   && < 3.3,
-      warp            >= 3.2   && < 3.4,
+      warp            >= 3.2   && < 3.5,
       http-types      >= 0.9   && < 0.13,
       fsnotify        >= 0.2   && < 0.5
     Cpp-options:
@@ -251,7 +254,7 @@
     Other-Modules:
       Hakyll.Web.Pandoc.Binary
     Build-Depends:
-      pandoc >= 2.11 && < 2.20 || >= 3.0 && < 3.2
+      pandoc >= 2.11 && < 2.20 || >= 3.0 && < 3.3
     Cpp-options:
       -DUSE_PANDOC
 
@@ -283,7 +286,7 @@
 
   Build-Depends:
     hakyll,
-    QuickCheck                 >= 2.8  && < 2.15,
+    QuickCheck                 >= 2.8  && < 2.16,
     tasty                      >= 0.11 && < 1.6,
     tasty-golden               >= 2.3  && < 2.4,
     tasty-hunit                >= 0.9  && < 0.11,
@@ -291,11 +294,11 @@
     -- Copy pasted from hakyll dependencies:
     aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.3,
     base                 >= 4.12     && < 5,
-    bytestring           >= 0.9      && < 0.12,
-    containers           >= 0.3      && < 0.7,
-    filepath             >= 1.0      && < 1.5,
+    bytestring           >= 0.9      && < 0.13,
+    containers           >= 0.3      && < 0.8,
+    filepath             >= 1.0      && < 1.6,
     tagsoup              >= 0.13.1   && < 0.15,
-    text                 >= 0.11     && < 1.3 || >= 2.0 && < 2.1,
+    text                 >= 0.11     && < 1.3 || >= 2.0 && < 2.2,
     unordered-containers >= 0.2      && < 0.3,
     yaml                 >= 0.8.11   && < 0.12
 
@@ -318,7 +321,7 @@
     Cpp-options:
       -DUSE_PANDOC
     Build-Depends:
-      pandoc >= 2.11 && < 2.20 || >= 3.0 && < 3.2
+      pandoc >= 2.11 && < 2.20 || >= 3.0 && < 3.3
 
 
 Executable hakyll-init
@@ -334,7 +337,7 @@
     hakyll,
     base      >= 4.12 && < 5,
     directory >= 1.0  && < 1.4,
-    filepath  >= 1.0  && < 1.5
+    filepath  >= 1.0  && < 1.6
 
 Executable hakyll-website
   Main-is:          site.hs
@@ -351,5 +354,5 @@
     hakyll,
     base      >= 4.12  && < 5,
     directory >= 1.0   && < 1.4,
-    filepath  >= 1.0   && < 1.5,
-    pandoc    >= 2.11  && < 2.20 || >= 3.0 && < 3.2
+    filepath  >= 1.0   && < 1.6,
+    pandoc    >= 2.11  && < 2.20 || >= 3.0 && < 3.3
diff --git a/lib/Hakyll/Check.hs b/lib/Hakyll/Check.hs
--- a/lib/Hakyll/Check.hs
+++ b/lib/Hakyll/Check.hs
@@ -248,7 +248,9 @@
         -- Convert exception to a concise form
         showException e = case cast e of
             Just (Http.HttpExceptionRequest _ e') -> show e'
-            _                                     -> head $ words $ show e
+            _                                     -> case words $ show e of
+              w:_ -> w
+              []  -> error "Hakyll.Check.checkExternalUrl: impossible"
 
 requestExternalUrl :: URL -> Checker (Either SomeException Bool)
 requestExternalUrl url = liftIO $ try $ do
diff --git a/lib/Hakyll/Commands.hs b/lib/Hakyll/Commands.hs
--- a/lib/Hakyll/Commands.hs
+++ b/lib/Hakyll/Commands.hs
@@ -141,24 +141,30 @@
 -- | Print a warning message about the preview serving not being enabled
 #ifndef PREVIEW_SERVER
 previewServerDisabled :: IO ()
-previewServerDisabled =
-    mapM_ putStrLn
-        [ "PREVIEW SERVER"
-        , ""
-        , "The preview server is not enabled in the version of Hakyll. To"
-        , "enable it, set the flag to True and recompile Hakyll."
-        , "Alternatively, use an external tool to serve your site directory."
-        ]
+previewServerDisabled = mapM_ putStrLn
+    [ "PREVIEW SERVER"
+    , ""
+    , "The preview server is not enabled in this version of Hakyll. To enable"
+    , "it, toggle the cabal configuration flag to True, for example by adding"
+    , "the following to your `cabal.project` file:"
+    , ""
+    , "    constraints: hakyll +previewServer"
+    , ""
+    , "Alternatively, use an external tool to serve your site directory."
+    ]
 #endif
 
 #ifndef WATCH_SERVER
 watchServerDisabled :: IO ()
-watchServerDisabled =
-    mapM_ putStrLn
-      [ "WATCH SERVER"
-      , ""
-      , "The watch server is not enabled in the version of Hakyll. To"
-      , "enable it, set the flag to True and recompile Hakyll."
-      , "Alternatively, use an external tool to serve your site directory."
-      ]
+watchServerDisabled = mapM_ putStrLn
+    [ "WATCH SERVER"
+    , ""
+    , "The watch server is not enabled in this version of Hakyll. To enable"
+    , "it, toggle the cabal configuration flag to True, for example by adding"
+    , "the following to your `cabal.project` file:"
+    , ""
+    , "    constraints: hakyll +watchServer"
+    , ""
+    , "Alternatively, use an external tool to serve your site directory."
+    ]
 #endif
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
@@ -5,8 +5,8 @@
 (relative to the destination directory as configured in
 'Hakyll.Core.Configuration.destinationDirectory').
 
-* __If there is no route for an item, the compiled item won't be written
-out to a file__ and so won't appear in the destination (site) directory.
+* __If there is no route for an item, the compiled item won't be written__
+__out to a file__ and so won't appear in the destination (site) directory.
 
 * If an item matches multiple routes, the first route will be chosen.
 
@@ -32,8 +32,9 @@
 That will route the file @posts\/hakyll.md@ from the project directory to
 @posts\/hakyll.md@ in the destination directory.
 
-Note: __The extension of the destination filepath says nothing about the
-content!__ If you set the extension to @.html@, you have to ensure that the
+Note:
+__The extension of the destination filepath says nothing about the content!__
+If you set the extension to @.html@, you have to ensure that the
 compilation result is indeed HTML (for example with the
 'Hakyll.Web.Pandoc.pandocCompiler' to transform Markdown to HTML).
 
@@ -205,9 +206,9 @@
 --------------------------------------------------------------------------------
 {- | Create a route that writes the compiled item to the given destination
 filepath (ignoring any identifier or other data about the item being processed).
-Warning: you should __use a specific destination path only for a single file in
-a single compilation rule__. Otherwise it's unclear which of the contents should
-be written to that route.
+Warning: you should __use a specific destination path only for a single file in__
+__a single compilation rule__. Otherwise it's unclear which of the contents
+should be written to that route.
 
 === __Examples__
 __Route to a specific filepath__
@@ -278,8 +279,8 @@
 {- | Wrapper function around other route construction functions to get
 access to the metadata (of the underlying item being processed) and use that for
 the destination filepath construction.
-Warning: you have to __ensure that the accessed metadata fields actually
-exists__.
+Warning: you have to
+__ensure that the accessed metadata fields actually exist__.
 
 === __Examples__
 __Route that uses a custom slug markdown metadata field__
diff --git a/lib/Hakyll/Core/Runtime.hs b/lib/Hakyll/Core/Runtime.hs
--- a/lib/Hakyll/Core/Runtime.hs
+++ b/lib/Hakyll/Core/Runtime.hs
@@ -10,7 +10,8 @@
 import           Control.Concurrent            (forkIO, getNumCapabilities,
                                                 rtsSupportsBoundThreads)
 import qualified Control.Concurrent.MVar       as MVar
-import           Control.Monad                 (replicateM_, unless, void)
+import           Control.Exception             (SomeException, try)
+import           Control.Monad                 (replicateM_, unless, void, when)
 import           Control.Monad.Reader          (ReaderT, ask, runReaderT)
 import           Control.Monad.Trans           (liftIO)
 import           Data.Foldable                 (for_, traverse_)
@@ -377,7 +378,8 @@
         RunModeNormal -> do
             Logger.header logger "Compiling"
             if rtsSupportsBoundThreads then pickAndChaseAsync else pickAndChase
-            Logger.header logger "Success"
+            errs <- liftIO $ schedulerErrors <$> IORef.readIORef schedulerRef
+            when (null errs) $ Logger.header logger "Success"
             facts <- liftIO $ schedulerFacts <$> IORef.readIORef schedulerRef
             store <- runtimeStore <$> ask
             liftIO $ Store.set store factsKey facts
@@ -496,21 +498,33 @@
                     "an Item with Identifier " ++ show id' ++ " " ++
                     "(you probably want to call makeItem to solve this problem)"
 
-            -- Write if necessary
-            (mroute, _) <- liftIO $ runRoutes routes provider id'
-            case mroute of
-                Nothing    -> return ()
-                Just route -> do
-                    liftIO . IORef.atomicModifyIORef' scheduler $
+            -- Write if necessary.  Note that we want another exception handler
+            -- around this: some compilers may successfully produce a
+            -- 'CompilerResult', but the thing they are supposed to 'write' can
+            -- have an un-evaluated 'error' them.
+            routeOrErr <- liftIO $ try $ do
+                (mroute, _) <- runRoutes routes provider id'
+                for_ mroute $ \route -> do
+                    IORef.atomicModifyIORef' scheduler $
                         schedulerRoute id' route
                     let path = destinationDirectory config </> route
-                    liftIO $ makeDirectories path
-                    liftIO $ write path item
-                    Logger.debug logger $ "Routed to " ++ path
+                    makeDirectories path
+                    write path item
+                save store item
+                pure mroute
 
-            liftIO $ save store item
-            liftIO . IORef.atomicModifyIORef' scheduler $
-                schedulerWrite id' facts
+            case routeOrErr of
+                Left e -> do
+                    liftIO $ IORef.atomicModifyIORef' scheduler $
+                        schedulerError (Just id') $
+                        "An exception was thrown when persisting " ++
+                        "the compiler result: " ++ show (e :: SomeException)
+                    pure SchedulerError
+                Right mroute -> do
+                    for_ mroute $ \route ->
+                        Logger.debug logger $ "Routed to " ++ show route
+                    liftIO . IORef.atomicModifyIORef' scheduler $
+                        schedulerWrite id' facts
 
         CompilerRequire reqs c ->
             liftIO . IORef.atomicModifyIORef' scheduler $
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
@@ -247,7 +247,7 @@
 --------------------------------------------------------------------------------
 -- | A context that contains (in that order)
 --
---     1. A @$body$@ field
+--     1. A @$body$@ 'bodyField'
 --
 --     2. Metadata fields
 --
@@ -256,6 +256,17 @@
 --     4. A @$path$@ 'pathField'
 --
 --     5. A @$title$@ 'titleField'
+--
+-- This order means that all of the fields, except @$body$@,
+-- can have their values replaced by metadata fields of the same name.
+-- For example, a context from a file at @posts/foo.markdown@ has a default title
+-- @foo@.  However, with a metadata field:
+--
+-- > ---
+-- > title: The Foo Story
+-- > ---
+--
+-- The @$title$@ will be replaced with @The Foo Story@.
 defaultContext :: Context String
 defaultContext =
     bodyField     "body"     `mappend`
@@ -271,7 +282,7 @@
 
 
 --------------------------------------------------------------------------------
--- | Constructs a 'field' that contains the body of the item.
+-- | Body of the item, that is, the main content of the underlying file
 bodyField :: String -> Context String
 bodyField key = field key $ return . itemBody
 
@@ -288,7 +299,8 @@
 
 
 --------------------------------------------------------------------------------
--- | Absolute url to the resulting item
+-- | Absolute url to the resulting item.  For an example item that produces a
+-- file @posts/foo.html@, this field contains "posts/foo.html"
 urlField :: String -> Context a
 urlField key = field key $ \i -> do
     let id = itemIdentifier i
@@ -297,13 +309,16 @@
 
 
 --------------------------------------------------------------------------------
--- | Filepath of the underlying file of the item
+-- | Filepath of the underlying file of the item.  For an example
+-- underlying file @posts/foo.markdown@, this field contains
+-- "posts/foo.markdown"
 pathField :: String -> Context a
 pathField key = field key $ return . toFilePath . itemIdentifier
 
 
 --------------------------------------------------------------------------------
--- | This title 'field' takes the basename of the underlying file by default
+-- | Basename of the underlying file of the item.  For an example
+-- underlying file @posts/foo.markdown@, this field contains "foo"
 titleField :: String -> Context a
 titleField = mapContext takeBaseName . pathField
 
diff --git a/tests/Hakyll/Core/Runtime/Tests.hs b/tests/Hakyll/Core/Runtime/Tests.hs
--- a/tests/Hakyll/Core/Runtime/Tests.hs
+++ b/tests/Hakyll/Core/Runtime/Tests.hs
@@ -32,6 +32,7 @@
     , case05
     , case06
     , issue1000
+    , issue1014
     ]
 
 
@@ -77,7 +78,7 @@
     lines example @?=  ["<p>This is an example.</p>"]
 
     bodies <- readFile $ destinationDirectory testConfiguration </> "bodies.txt"
-    head (lines bodies) @?=  "This is an example."
+    head' (lines bodies) @?=  "This is an example."
 
     partial  <- readFile $ providerDirectory    testConfiguration </> "partial.html.out"
     partial' <- readFile $ destinationDirectory testConfiguration </> "partial.html.out"
@@ -85,7 +86,11 @@
 
     cleanTestEnv
 
+  where
+    head' (x:_) = x
+    head' []    = error "Hakyll.Core.Runtime.Tests.case01: impossible"
 
+
 --------------------------------------------------------------------------------
 case02 :: Assertion
 case02 = do
@@ -260,4 +265,26 @@
         | (Logger.Error, msg) <- msgs, "multiple writes" `isInfixOf` msg
         ]
 
+    cleanTestEnv
+
+
+--------------------------------------------------------------------------------
+issue1014 :: Assertion
+issue1014 = do
+    (logger, inMemLog) <- Logger.newInMem
+    (ec, _)            <- run RunModeNormal testConfiguration logger $ do
+        match "*.md" $ do
+            route $ setExtension "html"
+            -- This compiler will succeed due to laziness, but writing the
+            -- result will throw an exception.
+            compile $ makeItem ("hello" ++ error "lazyworld")
+
+    ec @?= ExitFailure 1
+    msgs <- inMemLog
+    assertBool "missing 'lazyworld' error" $ not $ null $
+        [ msg
+        | (Logger.Error, msg) <- msgs, "lazyworld" `isInfixOf` msg
+        ]
+    assertBool "unwanted 'Success' message" $ not $
+        (Logger.Message, "Success") `elem` msgs
     cleanTestEnv
