From Wordpress to Hakyll
Today I switched my blog from Wordpress to this static version created with Hakyll.
I’ve had a few abortive attempts at switching to a static blog: Windows compatibility headaches with various systems have thwarted me. Even with my beloved Python I encountered Windows-hostile dependencies. This time the Haskell Stack worked for me without a hitch.
There were a few things I wanted to change from the vanilla Hakyll site, which I’ll describe below.
Due to my rather pathetic body of writing, I was able to convert my old posts to markdown manually. The hassle of setting up an automated system didn’t seem worth it: People seem to need to manually tweak the output of these anyway.
Encoding on Windows
On converting my posts to markdown, I found that they were getting truncated after apostrophe and quote characters. After questioning my sanity for longer than I should have, I thought to look for messages on the console. The site generator was complaining…
commitBuffer: invalid argument (invalid character)
The markdown processor was helpfully adding smart quotes, but the required encoding couldn’t be written. Fortunately the solution for this is in the FAQ.
I wanted my URLs to be similar to my Wordpress ones: without a “.html” on the end. I found a very useful blog post that showed how to do this. This was also a useful exercise in learning how the Hakyll routing works and how to customize how the site is built.
One thing that was missing from that how-to for a Haskell noob like me was how to add a dependency to a Haskell project. (Previously I’ve not strayed from the standard library and short tutorial exercises.)
Could not find module `System.FilePath'
… meant that I needed to add the correct dependency to my project’s .cabal file. In this case filepath == 1.4.*
I implemented teasers as described in the docs, followed by a short list of recent posts at the bottom. The latter was a fun challenge:
match "pages/index.html" $ do route dropFirstDir compile $ do let posts = loadAllSnapshots "posts/*" "content" teasers <- fmap (take numTeasersOnIndex) . recentFirst =<< posts recent <- fmap (take numRecentLinks . drop numTeasersOnIndex) . recentFirst =<< posts let indexCtx = listField "teasers" teaserCtx (return teasers) `mappend` listField "posts" postCtx (return recent) `mappend` constField "title" "" `mappend` defaultContext
I also moved the source text for index and about pages into a subdirectory to keep them separate from the Haskell machinery, requiring a custom route:
dropFirstDir :: Routes dropFirstDir = customRoute $ joinPath . tail . splitPath . toFilePath
I wanted the current item in the top-level navigation not to be a hyperlink. I have a solution that works, although I wonder if there’s a more elegant way. The source text for a page can define an identifier in the metadata, such as isHome. These are used in the template below to remove the link markup on that page:
<div class="nav"> $if(isHome)$ <span>Home</span> $else$ <a href="/">Home</a> $endif$ $if(isArchive)$ <span>Archive</span> $else$ <a href="/archive/">Archive</a> $endif$ $if(isAbout)$ <span>About</span> $else$ <a href="/about/">About</a> $endif$ </div>
My main goal in the conversion was better rendering on mobile devices. While trying out responsive Wordpress themes, I managed to break my Wordpress site, requiring a detour into phpMyAdmin to rescue it, and this was the final push into to converting to a static site.
The default Hakyll theme isn’t really responsive, but I think I’ve managed to bend Bronx to my will.
There were some low points during the conversion where I did wonder if I’d made the right choice of tool:
Some things that I was attempting led to digressions into Haskell topics that I hadn’t bargained for. While learning Haskell is a goal of mine, it seemed at times to be getting in the way of configuring a blog.
Edits to site.hs are slow to compile, although generating the site after that is very fast, so in the long run, it should be worth it.
Overall I’m happy with the conversion, and I think the hard bit is over, apart from writing worthwhile posts.