Migrating from Wordpress to Eleventy

| 6 min read

An image of a little girl with her fingers crossed

After almost 18 years on Wordpress, I have decided to give up on it. There are many benefits and upsides to using it such as the robust plugin marketplace and the wide variety of expertise and themes. However I grew so weary of dealing with the near constant onslaught of malware that I decided to move on. I've had to rebuild the Linode servers multiple times. A number of my plugins were related to security, malware scanning and I just gave up on the unpleasant rat race of it all.

In previous day jobs I had built multiple developer blogs (here and here) based on using the Hugo static site generator. That would have been familiar but I felt like trying something else. Jekyll was an option and Ruby is my favorite programming language but this feels like a project losing steam and I didn't want to begin on something already bit-rotting. After perusing my alternatives I landed on Eleventy.

Eleventy is written in Javascript as a node project, and in fact all of the themes/starters are each an individual node project. Where modifications are necessary I felt comfortable coding up whatever was necessary in JavaScript. That is good, because a fair bit was necessary.

I started with the Eleventy Duo theme. There were others for which I liked the aesthetics better or had responsive design out of the box but I couldn't easily make any of them work. This was my top choice that also worked without trouble.

Here are the steps I took, just in case someone in the future wants to follow these footsteps.

First Step

I exported my Wordpress site XML via these steps. Then I used this project to convert the XML to a group of Markdown files, one per post in a date hierarchy. This made the URLs generate almost exactly what they were in Wordpress.

There are many many very similar looking projects when you search on "wordpress to markdown". I tried half a dozen and this was the only one that I found satisfactory. Even with that I did a lot of modification to have it handle tags, podcast metadata and the like so it was absolutely not plug and play.

Second Step

I moved my thousands of Markdown files into my Eleventy project. At first try, it wasn't too shabby. Most of the work I did was just tweaking that original output the way I liked it. For example, I wanted posts that were podcast pages handled differently than blog posts. That turns out to have been easy by writing some custom code that created two new collections. Because I always used the same Wordpress category for podcasts, any post that contains the category "audio" is treated like a podcast and if it does not, it is a blog post. That worked perfectly. Then by modifying the Nunjucks .njk files I was able to make pages look how I liked.

Third Step

With the basics of the site operating, I set about making the podcast feed work. Although the Eleventy docmentation has RSS feed boilerplate code, it does not handle podcasts with the enclosure tags. Thus I had to build all that myself.

I modified the above exporter to get the enclosure URL from the Blubbry Powerpress plugin metadata and write it to a field in the Markdown frontmatter called enclosureUrl. Had I been thinking I might could have also gotten the filesize but I didn't think about that. Now armed with the actual URL of the audio file in most of my posts I could add them to my RSS feed with this snippet.

    
{% if post.data.enclosureUrl %}
<enclosure url="{{post.data.enclosureUrl}}" length="{{post.data.enclosureSize}}" type="{{post.data.enclosureType}}" />
{% endif %}

However, I had over one hundred older posts with no Powerpress metadata and no file sizes so I wrote a Ruby script to recurse through all the posts. If the post was a podcast but enclosureUrl was missing it parsed it out of the body of the post. If the size was missing it got that from the file (since I have them all locally in a Dropbox folder). While I was at it, if the itunesDuration was missing from the frontmatter I used mp3info to get that while I was there.

I was a bit surprised once I got the script working. I thought it wasn't operating correctly because it ran so quickly but sure enough, I had over 600 changed Markdown files and the fields enclosureUrl, enclosureSize and enclosureType (hardcoded to 'audio/mpeg' in the script), and itunesDuration were populated in all.

Fourth Step

At this point, I had all the necessary bits working. The entire history of my blog and podcast rendered properly. I had a podcast feed that was in fact properly handled by my podcatcher Podcast Addict on Android. I was able to have one page for podcasts and another for blogs.

I added pagination to both of the above, as I have many many posts to scroll through. The out-of-the-box pagination in Eleventy Duo wasn't to my liking so I hacked in the pagination from this gist. This has the nice aspect that when you are on page 25 of 50 pages, it shows "1 ... 23 24 25 26 27 ... 50". I liked that better than having a list of 50 numbers which was the default.

I also wanted a tag cloud. I started with Ginger's tag cloud plugin. This creates a data structure of tags with their counts but doesn't have the actual component. I adapted this nice CSS/JavaScript tag cloud to use the data from the above and it worked great. You can see it on this site, where I have a "Recent Tags" cloud for the last 50 posts and an "All Tags" cloud for every post in the history of this blog.

One problem I had with the "All Tags" cloud is that the logic above is linear. It takes the tag with the top count and that is the biggest. I have six size buckets (more made the biggest font too big). Since the most used tag had over 120 posts then the lowest bucket was everything from 1 to 20 uses, which was basically almost every tag on the whole site. I added a filter to go through and add another field for the square root of the count. For the "All Tags" it used the square root to calculate the sizes then the actual count for the display. This made it more cloudy and I was quite happy with the result.

Fifth Step

As I went to make it live, I didn't want to break all the links that others might have to this site and that crawlers have already indexed. The URL scheme is already very close to Wordpress. The only difference is Eleventy has "/posts/" prefixing the date and slug based URL. I considered changing Eleventy config to render in the root directory but I actually like this organization better. Instead I added a redirect rule to my .htaccess to send anything looking for the WordPress style URL to the new one.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(20.*) /posts/$1 [R=301,L]
</IfModule>

Anything looking for an URL starting with a date will now look for that same URL but prefixed with "/posts/". I know from looking at the access logs this is working fine. I did similar stuff with the various RSS feeds of Wordpress to my statically rendered XML files. All of this is working perfectly.

Final Thoughts and TBD

There is still much more to be done. I consider this good enough to make my live public site but much of what I had is missing. My top priorities are:

  • Getting responsive designs in so it looks better on mobile
  • Adding a sidebar or at least some of the convience links to subscribing to the podcast feeds for various apps and sites
  • Finding some sort of comment solution that is satisfactory and hopefuly getting the thousands of previous comments imported
  • Getting some rudimentary ActivityPub solution working. I liked having the Wordpress blog post to the fediverse with the ActivityPub plugin so I will try something like this hackery.
  • Not on the site itself but I need to configure VS Code to take a slug and then create the directories and the initial Markdown file with boilerplate frontmatter

It will take a little time to readjust from the Wordpress frame I have been in for so long. I did things the way I did because it was easy in Wordpress. The bad news is some things are no longer easy but the good news is I can do things what makes sense for me rather than what is most convient for the tool.

I very much like having the 22 year history of the blog and 19 year history of the podcast in a set of human readable text files that can be easily manipulated by VS Code or any other editor. Cleaning up tags in bulk in Wordpress is a drag. Cleaning them up now is doing a "Find and Replace in Files" and takes a few seconds. I also like that I can cut and paste the Markdown from my Obsidian notes directly into blog posts and they work without modification.

I really love that the whole of the blog, all of the content and all the code that renders it is committed to a private repository in Github. Worst case if my server were corrupted tomorrow I could set up a new one and have this site back on it in minutes. Dealing with the Wordpress backups and data dumps was not fun, and I paid something like $100/year for a plugin to handle that. Now I have better for absolutely free.

This is being written the morning after I made the new Eleventy version of the site live. I was allowing for an hour of site downtime to do it but in reality it took about 5 minutes to switch over. So far so good. I'm sure there is more to learn and many more tweaks to the logic and layout but I'm happy with where things are. Mahalo!