diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+0.11
+-----------------------------------------------------------------------------
+- Change error renderer to return text and optionally additional response
+  headers (Roman Borschel).
+- Update upper bounds.
+
 0.10.2
 -----------------------------------------------------------------------------
 - Update `bytestring-conversion` to version 0.2
diff --git a/src/Network/Wai/Routing/Route.hs b/src/Network/Wai/Routing/Route.hs
--- a/src/Network/Wai/Routing/Route.hs
+++ b/src/Network/Wai/Routing/Route.hs
@@ -34,11 +34,10 @@
 import Control.Monad
 import Control.Monad.Trans.State.Strict hiding (get, put)
 import Data.ByteString (ByteString)
-import Data.CaseInsensitive (mk)
 import Data.Either
 import Data.Function
 import Data.List hiding (head, delete)
-import Data.Maybe (fromMaybe, mapMaybe, catMaybes)
+import Data.Maybe (mapMaybe, catMaybes)
 import Data.Monoid
 import Network.HTTP.Types
 import Network.Wai
@@ -75,9 +74,10 @@
 -- to @IO@.
 type App m = RoutingReq -> Continue m -> m ResponseReceived
 
--- | Function to turn an 'Error' value into a 'Lazy.ByteString'.
+-- | Function to turn an 'Error' value into a 'Lazy.ByteString'
+-- to send as the response body and a list of additional response headers.
 -- Clients can provide their own renderer using 'renderer'.
-type Renderer = Error -> Maybe Lazy.ByteString
+type Renderer = Error -> Maybe (Lazy.ByteString, ResponseHeaders)
 
 -- | Data added to a route via 'attach' is returned in this @Meta@ record.
 data Meta a = Meta
@@ -100,7 +100,7 @@
         x = case catMaybes [s, r, l] of
                [] -> Nothing
                xs -> Just (Lazy.intercalate " " xs)
-    in maybe x (\y -> (<> (" -- " <> y)) <$> x) m
+    in plainText <$> maybe x (\y -> (<> (" -- " <> y)) <$> x) m
   where
     reason2str  NotAvailable = "not-available"
     reason2str  TypeError    = "type-error"
@@ -108,6 +108,7 @@
     message2str s  = Lazy.fromStrict s
     labels2str  [] = Nothing
     labels2str  xs = Just $ "[" <> Lazy.intercalate "," xs <> "]"
+    plainText    s = (s, [(hContentType, "text/plain")])
 
 -- | The Routes monad state type.
 data St a m = St
@@ -189,7 +190,7 @@
         Just (f, v) -> f (fromReq v (fromRequest rq)) k
         Nothing     -> k notFound
   where
-    notFound = responseLBS status404 [] ""
+    notFound = responseLBS status404 [] Lazy.empty
 
 -- | Prior to WAI 3.0 applications returned a plain 'Response'. @continue@
 -- turns such a function into a corresponding CPS version. For example:
@@ -275,8 +276,9 @@
     validMethods = C.intercalate "," $ nub (C.pack . show . _method <$> rr)
 
 allow :: HeaderName
-allow = mk "Allow"
+allow = "Allow"
 
 respond :: Renderer -> Error -> ResponseHeaders -> Response
-respond f e h = responseLBS (status e) h (fromMaybe mempty (f e))
-
+respond f e r = case f e of
+    Just (b, h) -> responseLBS (status e) (r ++ h) b
+    Nothing     -> responseLBS (status e) r Lazy.empty
diff --git a/test/Tests/Wai/Route.hs b/test/Tests/Wai/Route.hs
--- a/test/Tests/Wai/Route.hs
+++ b/test/Tests/Wai/Route.hs
@@ -30,6 +30,7 @@
 tests = testGroup "Network.Wai.Routing"
     [ testCase "Sitemap" testSitemap
     , testCase "Media Selection" testMedia
+    , testCase "Custom Error Renderer" testErrorRenderer
     ]
 
 testSitemap :: IO ()
@@ -250,3 +251,24 @@
     let rq = defaultRequest { rawPathInfo = "/media" }
     rs <- apply m $ fromReq [] . fromRequest . withHeader "Accept" h $ rq
     Lazy.fromStrict res @=? responseBody rs
+
+-------------------------------------------------------------------------------
+-- Custom Error Renderer Tests
+
+testErrorRenderer :: IO ()
+testErrorRenderer = do
+    let [(_, h)] = prepare sitemapErrorRenderer
+    let rq = defaultRequest { rawPathInfo = "/error" }
+    rs <- apply h $ fromReq [] . fromRequest $ rq
+    status400               @=? responseStatus rs
+    Just "application/json" @=? lookup hContentType (responseHeaders rs)
+    "{\"error\":\"foo\"}"   @=? responseBody rs
+
+sitemapErrorRenderer :: Routes a IO ()
+sitemapErrorRenderer = do
+    let f r = ("{\"error\":\"" <> r <> "\"}", [(hContentType, "application/json")])
+    renderer $ fmap (f . Lazy.fromStrict) . source
+    get "/error" (continue handler) $ query "foo"
+  where
+    handler :: Int -> IO Response
+    handler = const . return $ responseLBS status200 [] ""
diff --git a/wai-routing.cabal b/wai-routing.cabal
--- a/wai-routing.cabal
+++ b/wai-routing.cabal
@@ -1,5 +1,5 @@
 name:                wai-routing
-version:             0.10.2
+version:             0.11
 synopsis:            Declarative routing for WAI.
 license:             OtherLicense
 license-file:        LICENSE
@@ -76,15 +76,15 @@
         Network.Wai.Routing.Predicate
 
     build-depends:
-        attoparsec            >= 0.10  && < 0.13
+        attoparsec            >= 0.10  && < 1.0
       , base                  == 4.*
-      , bytestring            >= 0.10  && < 0.11
+      , bytestring            >= 0.10  && < 1.0
       , bytestring-conversion == 0.2.*
       , cookie                == 0.4.*
-      , case-insensitive      >= 1.1   && < 1.3
-      , http-types            == 0.8.*
+      , case-insensitive      >= 1.1   && < 2.0
+      , http-types            >= 0.8   && < 1.0
       , transformers          >= 0.3   && < 0.5
-      , wai                   == 3.0.*
+      , wai                   >= 3.0   && < 4.0
       , wai-predicates        == 0.8.*
       , wai-route             == 0.2.*
 
@@ -122,7 +122,7 @@
     ghc-options:      -Wall -O2 -fwarn-tabs
     build-depends:
         base          == 4.*
-      , criterion     >= 1.0.0.2 && < 1.1
+      , criterion     >= 1.0.0.2 && < 2.0
       , http-types
       , wai
       , wai-predicates
