diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/resources/alerts.tpl b/resources/alerts.tpl
new file mode 100644
--- /dev/null
+++ b/resources/alerts.tpl
@@ -0,0 +1,6 @@
+<div class="alerts">
+  <div class="fade in alert alert-${alert-type} alert-dismissable">
+    <button type="button" class="close" data-dismiss="alert">&times;</button>
+    <strong><alert-text/></strong>
+  </div>
+</div>
diff --git a/snap-utils.cabal b/snap-utils.cabal
new file mode 100644
--- /dev/null
+++ b/snap-utils.cabal
@@ -0,0 +1,44 @@
+name:           snap-utils
+version:        0.1.0
+synopsis:       Snap Framework utility funtions.
+description:    Utilities for Snap Framework apps.
+license:        BSD3
+author:         Luke Hoersten
+maintainer:     Luke Hoersten <luke@hoersten.org>
+build-type:     Simple
+cabal-version:  >= 1.6
+homepage:       https://github.com/LukeHoersten/snap-utils
+category:       Web, Snap
+
+data-files:
+  resources/alerts.tpl
+
+source-repository head
+  type:     git
+  location: https://github.com/LukeHoersten/snap-utils
+
+Library
+  hs-source-dirs: src
+
+  exposed-modules:
+    Snap.Utils
+    Snap.Utils.Alert
+    Snap.Utils.Environment
+    Snap.Utils.ErrorLogger
+    Snap.Utils.TargetPage
+
+  build-depends:
+    base                      >= 4.4     && < 5,
+    bytestring                >= 0.9.1   && < 0.11,
+    MonadCatchIO-transformers >= 0.2.1   && < 0.4,
+    mtl                       >= 2       && < 3,
+    xmlhtml                   >= 0.1     && < 0.3,
+    snap                      >= 0.13    && < 0.14,
+    snap-core                 >= 0.9     && < 0.11,
+    heist                     >= 0.13    && < 0.14,
+    http-types                >= 0.6     && < 0.9,
+    text                      >= 0.11    && < 0.12
+
+
+  ghc-options: -Wall -fwarn-tabs -funbox-strict-fields
+               -fno-warn-orphans -fno-warn-unused-do-bind
diff --git a/src/Snap/Utils.hs b/src/Snap/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/Utils.hs
@@ -0,0 +1,11 @@
+module Snap.Utils
+    ( module Snap.Utils.Alert
+    , module Snap.Utils.Environment
+    , module Snap.Utils.ErrorLogger
+    , module Snap.Utils.TargetPage
+    ) where
+
+import           Snap.Utils.Alert
+import           Snap.Utils.Environment
+import           Snap.Utils.ErrorLogger
+import           Snap.Utils.TargetPage
diff --git a/src/Snap/Utils/Alert.hs b/src/Snap/Utils/Alert.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/Utils/Alert.hs
@@ -0,0 +1,115 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Snap.Utils.Alert
+  ( addAlertSplices
+  , alertRedirect
+  , alertRedirect'
+  , alertSuccess
+  , alertInfo
+  , alertWarning
+  , alertDanger
+  , alertUrl
+  , AlertType(..)
+  ) where
+
+
+import           Control.Applicative   ((<$>), (<*>))
+import           Control.Arrow         ((***))
+import           Control.Monad         (liftM2)
+import           Control.Monad.Trans   (lift)
+import           Data.ByteString       (ByteString)
+import qualified Data.ByteString.Char8 as B
+import           Data.Monoid           (mempty)
+import           Data.Text             (Text)
+import qualified Data.Text.Encoding    as T
+import           Heist                 (HeistConfig (..), RuntimeSplice,
+                                        Splices, mapS, noSplices)
+import           Heist.Compiled        (Splice, callTemplate, codeGen,
+                                        pureSplice, textSplice,
+                                        withLocalSplices, yieldRuntime)
+import           Heist.Interpreted     (callTemplateWithText)
+import           Heist.SpliceAPI       (( #! ), ($$))
+import           Snap.Core             (MonadSnap, getParam, redirect)
+import           Snap.Snaplet          (Initializer, Snaplet)
+import           Snap.Snaplet.Heist    (HasHeist, Heist, SnapletCSplice,
+                                        SnapletISplice, addConfig)
+import           Snap.Utils.Types      (URL)
+
+
+data AlertType = Success | Info | Warning | Danger deriving Show
+
+
+addAlertSplices :: HasHeist b => Snaplet (Heist b) -> Initializer b v ()
+addAlertSplices h = addConfig h $ mempty
+    { hcCompiledSplices    = ("alerts" #! alertCSplice)
+    , hcInterpretedSplices = ("alerts" #! alertISplice)
+    }
+
+
+alertRedirect :: MonadSnap m => AlertType -> Text -> URL -> m a
+alertRedirect typ msg url = alertRedirect' typ msg url []
+
+
+alertRedirect' :: MonadSnap m => AlertType -> Text -> URL -> [(ByteString, ByteString)] -> m a
+alertRedirect' typ msg url params = redirect $ alertUrl typ msg url params
+
+
+alertSuccess :: MonadSnap m => Text -> URL -> m a
+alertSuccess = alertRedirect Success
+
+
+alertInfo :: MonadSnap m => Text -> URL -> m a
+alertInfo = alertRedirect Info
+
+
+alertWarning :: MonadSnap m => Text -> URL -> m a
+alertWarning = alertRedirect Warning
+
+
+alertDanger :: MonadSnap m => Text -> URL -> m a
+alertDanger = alertRedirect Danger
+
+
+alertISplice :: SnapletISplice b
+alertISplice = do
+  mAlert <- liftM2 (,) <$> getParam "alert-type" <*> getParam "alert-text"
+  case mAlert of
+    Just (aType, aText) ->
+        callTemplateWithText "/snippet/alerts" $ do
+              "alert-text" #! T.decodeUtf8 aText
+              "alert-type" #! T.decodeUtf8 aType
+    Nothing -> return []
+
+
+alertCSplice :: SnapletCSplice b
+alertCSplice = do
+    children <- withLocalSplices (alertSplices $$ ss) noSplices (callTemplate "/snippet/alerts")
+    return . yieldRuntime $ do
+        mAlert <- lift $ liftM2 (,) <$> getParam "alert-type" <*> getParam "alert-text"
+        maybe mempty (const $ codeGen children) mAlert
+  where ss = do
+            mAlert <- lift $ liftM2 (,) <$> getParam "alert-type" <*> getParam "alert-text"
+            return $ maybe mempty (T.decodeUtf8 *** T.decodeUtf8) mAlert
+
+
+alertSplices :: Monad m => Splices (RuntimeSplice m (Text, Text) -> Splice m)
+alertSplices = mapS (pureSplice . textSplice) $ do
+    "alert-type" #! fst
+    "alert-text" #! snd
+
+
+alertUrl :: AlertType -> Text -> URL -> [(ByteString, ByteString)] -> URL
+alertUrl typ msg url params = B.append url . uriQueryString $
+  [("alert-text", T.encodeUtf8 msg), ("alert-type", cssType typ)] ++ params
+
+
+cssType :: AlertType -> ByteString
+cssType Success = "success"
+cssType Info    = "info"
+cssType Warning = "warning"
+cssType Danger  = "danger"
+
+
+uriQueryString :: [(ByteString, ByteString)] -> URL
+uriQueryString = B.append "?" . B.intercalate "&" . map (showPairWith "=")
+  where showPairWith sep (k, v) = B.concat [k, sep, v]
diff --git a/src/Snap/Utils/Environment.hs b/src/Snap/Utils/Environment.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/Utils/Environment.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Snap.Utils.Environment where
+
+import           Control.Applicative ((<$>))
+import           Data.Maybe          (fromMaybe)
+import           Data.Monoid         (mempty)
+import           Data.Text           (Text)
+import qualified Data.Text           as T
+import           Heist               (HeistConfig (..), getParamNode)
+import           Heist.Compiled      (codeGen, runChildren, yieldRuntime)
+import           Heist.SpliceAPI     (( #! ))
+import           Snap.Snaplet        (Initializer, Snaplet, getEnvironment)
+import           Snap.Snaplet.Heist  (HasHeist, Heist, SnapletCSplice,
+                                      SnapletISplice, addConfig)
+import           Text.XmlHtml        (Node (..), childNodes)
+
+
+addEnvironmentSplices :: HasHeist b => Snaplet (Heist b) -> Initializer b v ()
+addEnvironmentSplices h = do
+    env <- T.pack <$> getEnvironment
+    addConfig h $ mempty
+        { hcCompiledSplices    = "ifEnvironment" #! envCSplice env
+        , hcInterpretedSplices = "ifEnvironment" #! envISplice env
+        }
+
+
+envISplice :: Text -> SnapletISplice b
+envISplice env = do
+    node <- getParamNode
+    let env' = getAttribute node "name"
+    return $ if env' == env then childNodes node else []
+
+
+envCSplice :: Text -> SnapletCSplice b
+envCSplice env = do
+    node <- getParamNode
+    let env' = getAttribute node "name"
+    children <- runChildren
+    return . yieldRuntime $ if env' == env then codeGen children else mempty
+
+
+getAttribute :: Node -> Text -> Text
+getAttribute node attr =
+    case node of
+        Element _ as _ ->
+            fromMaybe (error $ show node ++ ": missing " ++ T.unpack attr) $
+            lookup attr as
+        _ -> error "Wrong type of node!"
diff --git a/src/Snap/Utils/ErrorLogger.hs b/src/Snap/Utils/ErrorLogger.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/Utils/ErrorLogger.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Snap.Utils.ErrorLogger where
+
+import           Control.Exception     (SomeException)
+import           Control.Monad.CatchIO (catch)
+import           Data.Maybe            (fromJust)
+import           Data.Text             (Text)
+import qualified Data.Text             as T
+import qualified Data.Text.Encoding    as T
+import           Snap.Core             (MonadSnap, getRequest, logError)
+import           Snap.Snaplet          (Handler, SnapletLens, with)
+import           Snap.Snaplet.Auth     (AuthManager, AuthUser, currentUser,
+                                        unUid, userId, userLogin)
+import           Snap.Utils.Alert      (alertDanger)
+import           Snap.Utils.Types      (URL)
+
+
+newtype SysMsg = SysMsg { unSysMsg :: Text } deriving (Show, Eq)
+newtype UsrMsg = UsrMsg { unUsrMsg :: Text } deriving (Show, Eq)
+
+
+topExceptionHandler :: SnapletLens v (AuthManager b) -> Handler b v () -> Handler b v ()
+topExceptionHandler auth = flip catch (logException auth)
+
+
+logException :: SnapletLens v (AuthManager b) -> SomeException -> Handler b v ()
+logException auth e = do
+  rq <- getRequest
+  logErrorRedirect (sysMsg rq) usrMsg url =<< (with auth currentUser)
+    where sysMsg rq = SysMsg $ T.concat ["Uncaught exception: '" , T.pack $ show e, "'\n", T.pack $ show rq]
+          usrMsg = UsrMsg "You hit an application problem."
+          url = "/"
+
+-- Log a system error and redirect the user with a nice message.
+-- TODO log JSON
+logErrorRedirect :: MonadSnap m => SysMsg -> UsrMsg -> URL -> Maybe AuthUser -> m a
+logErrorRedirect sysMsg usrMsg url user = do
+  logError . T.encodeUtf8 $ T.concat ["[User: ", maybe "UNKNOWN" tShowUser user, "] ", unSysMsg sysMsg]
+  alertDanger usrMsg' url
+      where tShowUser u = T.concat [unUid . fromJust $ userId u, " ", userLogin u]
+            usrMsg' = T.concat [unUsrMsg usrMsg, " We've been notified of the problem and are working on it."]
+
+
+logErrorRedirect' :: MonadSnap m => Text -> URL -> AuthUser -> m a
+logErrorRedirect' msg url user = logErrorRedirect (SysMsg msg) (UsrMsg msg) url (Just user)
diff --git a/src/Snap/Utils/TargetPage.hs b/src/Snap/Utils/TargetPage.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/Utils/TargetPage.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections     #-}
+
+module Snap.Utils.TargetPage where
+
+import           Control.Applicative ((<$>))
+import           Control.Monad.Trans (lift)
+import           Data.Monoid         (mempty)
+import           Data.Text           (Text)
+import qualified Data.Text           as T
+import qualified Data.Text.Encoding  as T
+import           Heist               (HeistConfig (..))
+import           Heist.Compiled      (yieldRuntimeText)
+import           Heist.Interpreted   (textSplice)
+import           Heist.SpliceAPI     (( #! ))
+import           Network.HTTP.Types  (urlDecode, urlEncode)
+import           Snap.Core           (MonadSnap, getParam, getRequest, redirect,
+                                      rqURI)
+import           Snap.Snaplet        (Initializer, Snaplet)
+import           Snap.Snaplet.Heist  (HasHeist, Heist, SnapletCSplice,
+                                      SnapletISplice, addConfig)
+import           Snap.Utils.Alert    (AlertType (..), alertRedirect')
+import           Snap.Utils.Types    (URL)
+
+
+-- |Defer the target-page
+deferFromTargetPage :: MonadSnap m => Text -> URL -> m a
+deferFromTargetPage msg interimUrl =
+    deferFromTargetPage' msg interimUrl =<< rqURI <$> getRequest
+
+
+-- |If redirecting from a POST action, the POST action wasn't the
+-- target. The referrer was. Just pass the target-url in directly
+-- instead of grabbing it from the request.
+deferFromTargetPage' :: MonadSnap m => Text -> URL -> URL -> m a
+deferFromTargetPage' msg interimUrl targetUrl =
+    alertRedirect' Warning msg interimUrl . (:[]) . ("target-page",) $
+                   urlEncode True targetUrl
+
+
+continueToTargetPage :: MonadSnap m => m a -> m a
+continueToTargetPage noTargetPage =
+    getParam "target-page" >>= maybe noTargetPage hasTargetPage
+        where hasTargetPage = redirect . urlDecode True
+
+
+addTargetPageSplice :: HasHeist b => Snaplet (Heist b) -> Initializer b v ()
+addTargetPageSplice h = addConfig h $ mempty
+    { hcCompiledSplices    = ("target-page" #! targetPageCSplice)
+    , hcInterpretedSplices = ("target-page" #! targetPageISplice)
+    }
+
+
+targetPageISplice :: SnapletISplice b
+targetPageISplice = do
+    targetPage <- getParam "target-page"
+    textSplice $ maybe "" (T.append "target-page=" . T.decodeUtf8) targetPage
+
+
+targetPageCSplice :: SnapletCSplice b
+targetPageCSplice = return . yieldRuntimeText . lift $ do
+    targetPage <- getParam "target-page"
+    return $ maybe "" (T.append "target-page=" . T.decodeUtf8) targetPage
