diff --git a/hakyll.cabal b/hakyll.cabal
--- a/hakyll.cabal
+++ b/hakyll.cabal
@@ -1,5 +1,5 @@
 Name:    hakyll
-Version: 3.2.0.3
+Version: 3.2.0.4
 
 Synopsis: A static website compiler library
 Description:
@@ -68,6 +68,7 @@
     old-locale  >= 1.0   && < 2.0,
     old-time    >= 1.0   && < 1.3,
     pandoc      >= 1.6   && < 2.0,
+    parsec      >= 2.1   && < 3.2,
     process     >= 1.0   && < 1.4,
     regex-base  >= 0.93  && < 1.0,
     regex-pcre  >= 0.93  && < 1.0,
diff --git a/src/Hakyll/Web/Page.hs b/src/Hakyll/Web/Page.hs
--- a/src/Hakyll/Web/Page.hs
+++ b/src/Hakyll/Web/Page.hs
@@ -132,13 +132,13 @@
 
 -- | Add a number of default metadata fields to a page. These fields include:
 --
--- * @$url@
+-- * @$url$@
 --
--- * @$category@
+-- * @$category$@
 --
--- * @$title@
+-- * @$title$@
 --
--- * @$path@
+-- * @$path$@
 --
 addDefaultFields :: Compiler (Page a) (Page a)
 addDefaultFields =   (getRoute &&& id >>^ uncurry addRoute)
diff --git a/src/Hakyll/Web/Page/Read.hs b/src/Hakyll/Web/Page/Read.hs
--- a/src/Hakyll/Web/Page/Read.hs
+++ b/src/Hakyll/Web/Page/Read.hs
@@ -4,57 +4,53 @@
     ( readPage
     ) where
 
-import Control.Applicative ((<$>), (<*>))
-import Control.Arrow (second, (***))
-import Control.Monad.State (State, get, put, evalState)
-import Data.List (isPrefixOf)
-import Data.Map (Map)
+import Control.Applicative ((<$>), (<*>), (<*))
 import qualified Data.Map as M
 
-import Hakyll.Web.Page.Internal
+import Text.Parsec.Char (alphaNum, anyChar, char, newline, oneOf, string)
+import Text.Parsec.Combinator (choice, many1, manyTill, option, skipMany1)
+import Text.Parsec.Prim (many, parse, skipMany, (<?>))
+import Text.Parsec.String (Parser)
+
 import Hakyll.Core.Util.String
+import Hakyll.Web.Page.Internal
 
--- | We're using a simple state monad as parser
---
-type LineParser = State [String]
+-- | Space or tab, no newline
+inlineSpace :: Parser Char
+inlineSpace = oneOf ['\t', ' '] <?> "space"
 
--- | Read the metadata section from a page
+-- | Parse a single metadata field
 --
-parseMetadata :: LineParser (Map String String)
-parseMetadata = get >>= \content -> case content of
-    -- No lines means no metadata
-    [] -> return M.empty
-    -- Check if the file begins with a delimiter
-    (l : ls) -> if not (isPossibleDelimiter l)
-        then -- No delimiter means no metadata
-             return M.empty
-        else do -- Break the metadata section
-                let (metadata, rest) = second (drop 1) $ break (== l) ls
-                -- Put the rest back
-                put rest
-                -- Parse the metadata
-                return $ M.fromList $ map parseMetadata' metadata
+metadataField :: Parser (String, String)
+metadataField = do
+    key <- manyTill alphaNum $ char ':'
+    skipMany1 inlineSpace
+    value <- manyTill anyChar newline
+    trailing' <- many trailing
+    return (key, trim $ value ++ concat trailing')
   where
-    -- Check if a line can be a delimiter
-    isPossibleDelimiter = isPrefixOf "---"
-
-    -- Parse a "key: value" string to a (key, value) tupple
-    parseMetadata' = (trim *** trim . drop 1) . break (== ':')
+    trailing = (++) <$> many1 inlineSpace <*> manyTill anyChar newline
 
--- | Read the body section of a page
+-- | Parse a metadata block, including delimiters and trailing newlines
 --
-parseBody :: LineParser String
-parseBody = do
-    body <- get
-    put []
-    return $ unlines body
+metadata :: Parser [(String, String)]
+metadata = do
+    open <- many1 (char '-') <* many inlineSpace <* newline
+    metadata' <- many metadataField
+    _ <- choice $ map (string . replicate (length open)) ['-', '.']
+    skipMany inlineSpace
+    skipMany1 newline
+    return metadata'
 
--- | Read an entire page
+-- | Parse a Hakyll page
 --
-parsePage :: LineParser (Page String)
-parsePage = Page <$> parseMetadata <*> parseBody
+page :: Parser ([(String, String)], String)
+page = do
+    metadata' <- option [] metadata
+    body <- many anyChar
+    return (metadata', body)
 
--- | Read a page from a string
---
 readPage :: String -> Page String
-readPage = evalState parsePage . lines
+readPage input = case parse page "page" input of
+    Left err      -> error (show err)
+    Right (md, b) -> Page (M.fromList md) b
