diff --git a/Web/Route/Invertible/Map/Bool.hs b/Web/Route/Invertible/Map/Bool.hs
--- a/Web/Route/Invertible/Map/Bool.hs
+++ b/Web/Route/Invertible/Map/Bool.hs
@@ -7,8 +7,6 @@
   , lookupBool
   ) where
 
-import Data.Semigroup (Semigroup((<>)))
-
 -- |A trivial, flat representation of a 'Bool'-keyed map.
 -- Value existance (but not the values themselves) is strict.
 data BoolMap v = BoolMap
diff --git a/Web/Route/Invertible/Map/Const.hs b/Web/Route/Invertible/Map/Const.hs
--- a/Web/Route/Invertible/Map/Const.hs
+++ b/Web/Route/Invertible/Map/Const.hs
@@ -12,8 +12,6 @@
   , flattenConstDefaultMap
   ) where
 
-import Data.Semigroup (Semigroup((<>)))
-
 import Web.Route.Invertible.Map.Default
 
 -- |A monoid map where every key (additionally) maps to the same constant value, parameterized over the type of the map.
diff --git a/Web/Route/Invertible/Map/Custom.hs b/Web/Route/Invertible/Map/Custom.hs
--- a/Web/Route/Invertible/Map/Custom.hs
+++ b/Web/Route/Invertible/Map/Custom.hs
@@ -6,7 +6,6 @@
   ) where
 
 import Data.Maybe (mapMaybe)
-import Data.Semigroup (Semigroup)
 import Text.Show.Functions ()
 
 newtype CustomMap q a b = CustomMap [(q -> Maybe a, b)]
diff --git a/Web/Route/Invertible/Map/Default.hs b/Web/Route/Invertible/Map/Default.hs
--- a/Web/Route/Invertible/Map/Default.hs
+++ b/Web/Route/Invertible/Map/Default.hs
@@ -10,7 +10,6 @@
   ) where
 
 import Control.Applicative ((<|>))
-import Data.Semigroup (Semigroup((<>)))
 
 -- |A map that also provides a default value, for when a key is not found in the underlying map, parameterized over the type of the map.
 data DefaultMap m v = DefaultMap
diff --git a/Web/Route/Invertible/Map/Monoid.hs b/Web/Route/Invertible/Map/Monoid.hs
--- a/Web/Route/Invertible/Map/Monoid.hs
+++ b/Web/Route/Invertible/Map/Monoid.hs
@@ -12,7 +12,6 @@
 
 import Data.Foldable (fold)
 import qualified Data.Map.Strict as M
-import Data.Semigroup (Semigroup((<>)))
 
 -- |A specialized version of 'M.Map'.
 newtype MonoidMap k a = MonoidMap { monoidMap :: M.Map k a }
diff --git a/Web/Route/Invertible/Map/MonoidHash.hs b/Web/Route/Invertible/Map/MonoidHash.hs
--- a/Web/Route/Invertible/Map/MonoidHash.hs
+++ b/Web/Route/Invertible/Map/MonoidHash.hs
@@ -13,7 +13,6 @@
 import Data.Foldable (fold)
 import Data.Hashable (Hashable)
 import qualified Data.HashMap.Strict as M
-import Data.Semigroup (Semigroup((<>)))
 
 -- |A specialized version of 'M.HashMap'.
 newtype MonoidHashMap k a = MonoidHashMap { monoidHashMap :: M.HashMap k a }
diff --git a/Web/Route/Invertible/Map/Placeholder.hs b/Web/Route/Invertible/Map/Placeholder.hs
--- a/Web/Route/Invertible/Map/Placeholder.hs
+++ b/Web/Route/Invertible/Map/Placeholder.hs
@@ -18,7 +18,6 @@
 import Data.Dynamic (Dynamic)
 import qualified Data.HashMap.Strict as HM
 import qualified Data.Map.Strict as M
-import Data.Semigroup (Semigroup((<>)))
 
 import Web.Route.Invertible.String
 import Web.Route.Invertible.Placeholder
diff --git a/Web/Route/Invertible/Map/Query.hs b/Web/Route/Invertible/Map/Query.hs
--- a/Web/Route/Invertible/Map/Query.hs
+++ b/Web/Route/Invertible/Map/Query.hs
@@ -8,7 +8,6 @@
   ) where
 
 import qualified Data.HashMap.Lazy as HM
-import Data.Semigroup (Semigroup((<>)))
 
 import Web.Route.Invertible.Placeholder
 import Web.Route.Invertible.Map.Placeholder
diff --git a/Web/Route/Invertible/Map/Route.hs b/Web/Route/Invertible/Map/Route.hs
--- a/Web/Route/Invertible/Map/Route.hs
+++ b/Web/Route/Invertible/Map/Route.hs
@@ -20,7 +20,6 @@
 import Data.Dynamic (Dynamic, toDyn)
 import qualified Data.HashMap.Strict as HM
 import qualified Data.Map.Strict as Map
-import Data.Semigroup (Semigroup((<>)))
 
 import Web.Route.Invertible.String
 import Web.Route.Invertible.Sequence
diff --git a/Web/Route/Invertible/Map/Sequence.hs b/Web/Route/Invertible/Map/Sequence.hs
--- a/Web/Route/Invertible/Map/Sequence.hs
+++ b/Web/Route/Invertible/Map/Sequence.hs
@@ -33,7 +33,6 @@
 import Control.Invertible.Monoidal.Free
 import Control.Monad (MonadPlus(..))
 import Control.Monad.Trans.State (evalState)
-import Data.Semigroup (Semigroup((<>)))
 
 import Web.Route.Invertible.String
 import Web.Route.Invertible.Placeholder
diff --git a/Web/Route/Invertible/Monoid/Exactly.hs b/Web/Route/Invertible/Monoid/Exactly.hs
--- a/Web/Route/Invertible/Monoid/Exactly.hs
+++ b/Web/Route/Invertible/Monoid/Exactly.hs
@@ -11,7 +11,6 @@
 
 import Control.Applicative (Alternative(..))
 import Control.Monad (MonadPlus(..))
-import Data.Semigroup (Semigroup((<>)))
 
 -- |A 'Maybe'-like monoid that only allows one value, overflowing into 'Conflict' when more than one 'Exactly' are combined (with '<|>' or 'Data.Monoid.<>', which thus function identically).
 data Exactly a
diff --git a/Web/Route/Invertible/Monoid/Prioritized.hs b/Web/Route/Invertible/Monoid/Prioritized.hs
--- a/Web/Route/Invertible/Monoid/Prioritized.hs
+++ b/Web/Route/Invertible/Monoid/Prioritized.hs
@@ -4,8 +4,6 @@
   ( Prioritized(..)
   ) where
 
-import Data.Semigroup (Semigroup((<>)))
-
 -- |A trival monoid allowing each item to be given a priority when combining.
 data Prioritized a = Prioritized
   { priority :: !Int -- ^ The priority this value, where larger numeric values have higher priority and take precedence over lower priorities.
diff --git a/Web/Route/Invertible/Path.hs b/Web/Route/Invertible/Path.hs
--- a/Web/Route/Invertible/Path.hs
+++ b/Web/Route/Invertible/Path.hs
@@ -19,7 +19,6 @@
 
 import Control.Invertible.Monoidal
 import qualified Data.ByteString.Builder as B
-import Data.Monoid ((<>))
 import qualified Data.Invertible as I
 import Data.String (IsString(..))
 import qualified Data.Text as T
diff --git a/Web/Route/Invertible/Render.hs b/Web/Route/Invertible/Render.hs
new file mode 100644
--- /dev/null
+++ b/Web/Route/Invertible/Render.hs
@@ -0,0 +1,43 @@
+-- |Render requests in various ways
+module Web.Route.Invertible.Render
+  ( renderRequestBuilder
+  , renderUrlRequestBuilder
+  , renderHamletUrl
+  ) where
+
+import           Control.Arrow (second)
+import qualified Data.ByteString.Lazy as BSL
+import qualified Data.ByteString.Builder as B
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import           Network.HTTP.Types.URI (Query, encodePathSegments, renderQueryBuilder, simpleQueryToQuery, queryTextToQuery)
+
+import Web.Route.Invertible.Request
+import Web.Route.Invertible.Query
+import Web.Route.Invertible.Route
+
+-- |This renders a request along with additional query parameters as @[//host][/path][?query]@.
+-- Each component is included only if it's specified by the route.
+renderRequestBuilder :: Request -> Query -> B.Builder
+renderRequestBuilder r q =
+     bh (requestHost r)
+  <> bp (requestPath r)
+  <> renderQueryBuilder True ((simpleQueryToQuery $ paramsQuerySimple $ requestQuery r) ++ q)
+  where
+  bh [] = mempty
+  bh [x] = sl <> sl <> B.byteString x
+  bh (x:l) = bh l <> B.char8 '.' <> B.byteString x
+  bp [] = sl
+  bp p = encodePathSegments p
+  sl = B.char8 '/'
+
+-- |This renders a request along with additional query parameters as @scheme:[//host][/path][?query]@.
+-- It's roughly equivalent to rendering the 'Web.Route.Invertible.URI.requestURI'.
+renderUrlRequestBuilder :: Request -> Query -> B.Builder
+renderUrlRequestBuilder r q =
+  B.string8 (if requestSecure r then "https:" else "http:") <> renderRequestBuilder r q
+
+-- |A 'Text.Hamlet.Render' function, suitable for passing to a @'Text.Hamlet.HtmlUrl' (RouteAction a b, a)@ template.
+renderHamletUrl :: (RouteAction a b, a) -> [(T.Text, T.Text)] -> T.Text
+renderHamletUrl (r, a) q = TE.decodeUtf8 $ BSL.toStrict $ B.toLazyByteString
+  $ renderRequestBuilder (requestActionRoute r a) $ queryTextToQuery $ map (second Just) q
diff --git a/Web/Route/Invertible/Result.hs b/Web/Route/Invertible/Result.hs
--- a/Web/Route/Invertible/Result.hs
+++ b/Web/Route/Invertible/Result.hs
@@ -5,7 +5,6 @@
   ) where
 
 import qualified Data.ByteString.Char8 as BSC
-import Data.Semigroup (Semigroup((<>)))
 import Data.Typeable (Typeable)
 import Network.HTTP.Types.Header (ResponseHeaders, hAllow)
 import Network.HTTP.Types.Status (Status, notFound404, methodNotAllowed405, internalServerError500)
diff --git a/Web/Route/Invertible/Sequence.hs b/Web/Route/Invertible/Sequence.hs
--- a/Web/Route/Invertible/Sequence.hs
+++ b/Web/Route/Invertible/Sequence.hs
@@ -27,7 +27,6 @@
 import           Control.Invertible.Monoidal
 import           Control.Invertible.Monoidal.Free
 import           Control.Monad (MonadPlus, mzero, guard)
-import           Control.Monad.Fail (MonadFail)
 import qualified Data.Invertible as I
 import           Data.String (IsString(..))
 
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,13 +1,16 @@
 {-# LANGUAGE OverloadedStrings #-}
 module Main (main) where
 
-import Data.Maybe (fromJust)
-import Network.URI (parseURI)
-import System.Exit (exitSuccess, exitFailure)
+import qualified Data.ByteString.Lazy as BS
+import qualified Data.ByteString.Builder as BSB
+import           Data.Maybe (fromJust)
+import           Network.URI (parseURI)
+import           System.Exit (exitSuccess, exitFailure)
 import qualified Test.HUnit as U
 
 import Web.Route.Invertible
 import Web.Route.Invertible.URI
+import Web.Route.Invertible.Render
 
 getThing :: RouteAction Int String
 getThing = routeMethod GET *< routePath ("thing" *< parameter) `RouteAction` \i ->
@@ -62,17 +65,23 @@
 req :: RouteAction r a -> r -> (Method, String)
 req a = fmap show . routeActionURI a
 
+ren :: RouteAction r a -> r -> BS.ByteString
+ren r a = BSB.toLazyByteString $ renderUrlRequestBuilder (requestActionRoute r a) []
+
 tests :: U.Test
 tests = U.test
   [ rte GET "http:/thing/0"           U.~?= RouteResult "get thing0"
   , rte GET "http:/thing/-3?type=x"   U.~?= RouteResult "get thing-3:x"
   , req getThingQ (98, '=')           U.~?= (GET, "http:/thing/98?type=%3D")
+  , ren getThingQ (98, '=')           U.~?= "http:/thing/98?type=%3D"
   , rte PUT "http:/thing/09"          U.~?= RouteResult "put thing9"
   , req postThing "foo"               U.~?= (POST, "http:/thing/foo")
+  , ren postThing "foo"               U.~?= "http:/thing/foo"
   , rte OPTIONS "http:/thing/xxx"     U.~?= AllowedMethods [POST]
   , rte PUT "http:/thing/0/sub/12/34" U.~?= RouteResult "thing0 sub 12 34"
   , rte GET "http:/things/0/12/34"    U.~?= RouteResult "ignore thing0"
   , req ignoredThing (-3)             U.~?= (GET, "http:/things/-3/ign")
+  , ren ignoredThing (-3)             U.~?= "http:/things/-3/ign"
   , rte POST "http://foo.com/foo"     U.~?= AllowedMethods [GET]
   , rte POST "http:/foo"              U.~?= RouteNotFound
   ]
diff --git a/web-inv-route.cabal b/web-inv-route.cabal
--- a/web-inv-route.cabal
+++ b/web-inv-route.cabal
@@ -1,5 +1,5 @@
 name:                web-inv-route
-version:             0.1.2.3
+version:             0.1.3.0
 synopsis:            Composable, reversible, efficient web routing using invertible invariants and bijections
 description:
   Utilities to route HTTP requests, mainly focused on path components.  Routes are specified using bijections and invariant functors, allowing run-time composition (routes can be distributed across modules), reverse and forward routing derived from the same specification, and O(log n) lookups.
@@ -46,6 +46,7 @@
     Web.Route.Invertible
     Web.Route.Invertible.Common
     Web.Route.Invertible.Internal
+    Web.Route.Invertible.Render
   other-modules:
     Web.Route.Invertible.String
     Web.Route.Invertible.Parameter
