From Wordpress to Hakyll

Posted on 27 November 2016

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.

Converting Posts

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.

Jekyll URLs

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.*

Teasers

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>

Responsive Layout

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.

Conclusion

There were some low points during the conversion where I did wonder if I’d made the right choice of tool:

Overall I’m happy with the conversion, and I think the hard bit is over, apart from writing worthwhile posts.