diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # Changelog for Shakebook
 
+## (v0.7.1.0)
+
+* Upgrade to [zipper-extra](https://hackage.haskell.org/package/zipper-extra) v0.1.3.0.
+* Add `postZipper` for creating a `Zipper` from an `IxSet` of Posts.
+
 ## (v0.7.0.0)
 
 * Switch to [ixset-typed](httos://hackage.haskell.org/package/ixset-typed).
diff --git a/shakebook.cabal b/shakebook.cabal
--- a/shakebook.cabal
+++ b/shakebook.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 7e82312f4be709fd5cf0db5e49335216bd6457f8d8e8d5eb86c75e6d3bdcc29f
+-- hash: 3fd5dfe9c0ad89666b06e7a79e4f378fa824b3670c6416345ba0f3af5518c5cc
 
 name:           shakebook
-version:        0.7.0.0
+version:        0.7.1.0
 synopsis:       Shake-based technical documentation generator; HTML & PDF
 description:    Shakebook is a documentation generator aimed at covering all the bases for mathematical, technical and scientific diagrams and typesetting. Shakebook provides combinators for taking markdown files and combining them into documents, but allowing the user to control how. Shakebook provides general combinators for templating single pages, cofree comonads for representing tables of contents, and zipper comonads for representing pagers.
 category:       Web
@@ -56,7 +56,7 @@
     , slick >=1.0.1.1
     , split
     , text-time
-    , zipper-extra
+    , zipper-extra >=0.1.3.0
   default-language: Haskell2010
 
 test-suite shakebook-test
@@ -92,5 +92,5 @@
     , tasty
     , tasty-golden
     , text-time
-    , zipper-extra
+    , zipper-extra >=0.1.3.0
   default-language: Haskell2010
diff --git a/src/Shakebook/Conventions.hs b/src/Shakebook/Conventions.hs
--- a/src/Shakebook/Conventions.hs
+++ b/src/Shakebook/Conventions.hs
@@ -53,6 +53,7 @@
 , YearMonth(..)
 , SrcFile(..)
 , postIndex
+, postZipper
 ) where
 
 import           Control.Comonad.Cofree
@@ -235,6 +236,10 @@
 postIndex rd fp = do
   xs <- batchLoadWithin' fp rd
   return (Ix.fromList $ Post <$> HM.elems xs)
+
+-- | Create a `Zipper [] Post` from an `IxSet xs Post` by ordering by `Posted`.
+postZipper :: (MonadThrow m, Ix.IsIndexOf Posted xs) => Ix.IxSet xs Post -> m (Zipper [] Post)
+postZipper = zipper' . Ix.toDescList (Proxy :: Proxy Posted)
 
 -- | Create a blog navbar object for a posts section, with layers "toc1", "toc2", and "toc3".
 genBlogNavbarData :: IsIndexOf YearMonth ixs => Text -- ^ "Top level title, e.g "Blog"
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,6 +1,5 @@
 {-# LANGUAGE TemplateHaskell #-}
 
-import           Control.Lens      hiding ((:<))
 import           Data.Aeson.With
 import qualified Data.IxSet.Typed as Ix
 import           Data.List.Split
@@ -57,17 +56,11 @@
 
   postsIx <- newCache $ postIndex $ fmap defaultEnrichPost . readMDC
 
-  getBlogNavbar <- newCache $ \fp -> do
+  blogNav <- newCache $ \fp -> do
     xs <- postsIx fp
     return $ genBlogNavbarData "Blog" "/posts/" defaultPrettyMonthFormat defaultMonthUrlFragment xs
 
-  postsZ <- newCache $ \fp -> do
-    xs <- postsIx fp
-    let ys = Ix.toDescList (Proxy :: Proxy Posted) xs
-    zs <- ifor ys $ \i v -> do
-      z <- zipper' (unPost <$> ys)
-      return (viewSrcPath (unPost v), seek i z)
-    return $ HM.fromList zs
+  postsZ <- newCache $ postsIx >=> postZipper
 
   blogIndexPageData <- newCache $ \fp -> do
     xs <- postsIx fp
@@ -97,15 +90,15 @@
       s' = (`within` sourceFolder)
 
       myBuildPage tmpl v out = do
-        rs <- postsIx myPosts
+        rs <- postsZ myPosts
         let v' = withHighlighting pygments
                . withSocialLinks mySocial
                . withSiteTitle siteTitle
-               . withRecentPosts (take numRecentPosts $ Ix.toDescList (Proxy :: Proxy Posted) rs) $ v
+               . withRecentPosts (take numRecentPosts (unzipper rs)) $ v
         buildPageActionWithin (s' tmpl) v' out
 
       myBuildBlogPage tmpl v out = do
-        k <- getBlogNavbar myPosts
+        k <- blogNav myPosts
         myBuildPage tmpl (withJSON k v) out
 
       myBuildPostListPage z out = do
@@ -119,10 +112,9 @@
 
   o' "posts/*.html" %^> \out -> do
     src <- blinkAndMapM sourceFolder withMdExtension out
-    xs <- postsZ myPosts
-    case HM.lookup (T.pack . toFilePath . extract $ src) xs of
-      Nothing -> logError $ "Attempting to lookup non-existent post " <> displayShow src
-      Just x  -> myBuildBlogPage $(mkRelFile "templates/post.html") (extract x) out
+    xs  <- postsZ myPosts
+    xs' <- seekOnThrow (viewSrcPath . unPost) (T.pack . toFilePath . extract $ src) xs
+    myBuildBlogPage $(mkRelFile "templates/post.html") (extract (unPost <$> xs')) out
 
   toc' <- mapM (mapM withHtmlExtension) $ fmap o' tableOfContents
   void . sequence . flip extend toc' $ \xs -> (toFilePath <$> extract xs) %^> \out -> do
