Marco's Blog

All content personal opinions or work.
en eo

Porting a Template from WordPress to Hugo

2024-01-04 8 min read Geek marco

Having a series of sites running on WordPress has become a major chore, because they all have to be constantly updated. Plugins, themes, language packs, and even WordPress itself. If you don’t upgrade, then you risk exploits; if you upgrade it becomes an almost daily task, with all the different versions of everything. Finally, if you reduce the number of plugins, you lose the most important reason why you are using a CMS in the first place.

I caved in and finally decided to start porting the sites that can to something simpler, static. Basically, it’s sites that don’t allow user accounts and are not dynamic. I still have a bunch of those, and had them on WordPress because it was (and is) easier to publish to it than writing HTML files. But Hugo (and the other crop of static site generators) makes it a breeze to have full functionality (minus dynamic. mostly) with Markdown, which is in essence what you write in the Classic Editor on WordPress.

The main problem with moving to Hugo, though, is keeping the same look and feel as before the transition. That’s particularly important for sites that have a specific look that isn’t easily replicated with the available themes on Hugo’s site. Hugo is relatively young, so there are plenty templates that have one or more features, but very few that combine them - look for instance for something that has light/dark mode and dynamic menus and is mobile ready, etc. (There probably is one theme named Marco Is An Idiot that does all that just to prove me wrong).

What you need to do is port not only the content, but the theme also. Which is really hard to automate, unlike the transition of the content, for which there are guides all over the place - most importantly on the Hugo site.

What Does Migrating a Theme Mean?

A content management system (CMS) functions with two components: one manages the content (hence the name) and deals with coordinating who can create, consume, access content in what order and location; the other manages how that content looks and how the site overall works and feels.

The two functions are largely independent (as long as they know how content is stored). The former is generally called backend and the latter frontend. In CMS speak specifically, the backend is also called a headless CMS, where headless refers to the Legend of Sleepy Hollow. Okay, I made that up.

Migrating content is done from the backend to the backend. What changes is what format the content is stored in. Content itself is also composed of two parts: visible content / text, and metadata that refers to things about the content (like tags, or publish date) that is used by the frontend to display additional information. In CMS terms, the metadata is also called front matter.

A theme in a CMS is a way to translate the content (with front matter) to something the user can actually see and read. In most cases on the web, this means translating from whatever is used on the backend to a specific combination of HTML/CSS/JS (HCJ) that determines look, feel, and functionality of the site.

How this translation from backend store to HCJ happens is the secret sauce of the CMS. WordPress uses PHP as the translation language, which means its files contain instructions like:

    if ( have_posts() ) :

Here, the odd <?php part means that a series of PHP commands are going to follow (that is, not HCJ). The remainder will do something if there are posts to display. The theme files are full of these instructions and are processed by WordPress to insert the content and front matter of posts.

Hugo, on the other hand, does not use PHP. Instead, it prefers a templating language that is (a) not dynamic as PHP and (b) completely incompatible with it. The same command as above would look something like this:

{{ range .Pages }} {{ end }}

There is more to it than just this. WordPress organizes its themes in a specific fashion and uses plugins to enhance the functionality of the theme. Hugo organizes themes entirely differently and is limited in the ability of plugins to change the functionality of the site.

Actually Migrating a WordPress Theme to Hugo

Initially, and naively, I tried to take the original theme and re-write it, trying to find the correspondence between files. But that quickly became too complicated for me, because WordPress and Hugo organize things completely differently.

In the end I realized HCJ is the common thread between the two systems. I don’t have to translate PHP files, functions, and plugins to Hugo. I just need to create a Hugo theme that generates the same combination of HCJ that WordPress does. And that’s a whole lot easier, especially if your site is not complex or reliant on a lot of different content manipulations.

The process of migrating is then relatively straightforward:

  1. Create a new hugo site with a new template (follow the tutorial on
  2. For each type of page (home page, article, list), load it from WordPress and save as HTML - complete
  3. Find what is common between the different pages - the master template - and save it as layouts/_default/baseof.html in your hugo site
  4. Add the Hugo markers you’ll find in the baseof.html file that was generated in the theme directory (see below)
  5. Create a template for each type of content, following the guidelines on - one for home, one for content lists, one for content pages
  6. Profit!

The baseof.html file mentioned above will contain something like this:

{{ block "main" . }}{{ end }}

Here, you can extend the base template and add the secret sauce that will fetch lists, home page, and content articles.

You will need some familiarity with Hugo to make everything work, but you can get a lot of mileage from downloading a theme that does a lot of what you want with the content, to read the functions and adapt the template functionality.

Accessory Files

When you save the HTML version of the site as a complete page, you get a single HTML file and a set of accessory files in a folder. This folder contains all the files that the original page loads, and the HTML is saved such that it references the folder name.

When setting up your Hugo project, you want to put these files into the /static folder of the site. Files in this folder will be available directly to the site. You can keep the template unchanged if you just copy the folder to the static directory.

In general, you want to avoid doing that, because the folder name is generally concocted from the title of the site and so will probably have spaces and other “special” characters. I find it most useful to copy the files into subfolders by extension (that is, /css or /js etc.) and then change the location/href in the templates accordingly.

You should try finding absolute references in the downloaded HTML and download the corresponding files and place them in static, as well.

Limitations of This Approach

I already mentioned the first limitation: Hugo sites, by their own nature, are not dynamic. By that I don’t mean they can’t do things like animations or transitions, but that there is no way to decide who gets to see what. You can still make the site dynamic, but you have to do it on the front-end.

Hugo’s templating infrastructure is also limited compared to PHP, which is a full programming language capable of pretty much anything a computer can do. PHP can autonomously connect to databases, send messages and emails, etc. Hugo doesn’t do that.

Importantly, WordPress also has a much larger number of infrastructure hooks that can allow plugins to access a whole lot more information than what the Hugo environment offers.

Problems Encountered

Aside from the original issue, which was that trying to make the templates correspond to each other was too hard, a few more issues surfaced:

  1. I was not very familiar with Hugo and still am not, but the templating language sometimes doesn’t do what is advertised. I am not sure if the documentation is wrong or if there are special cases at work here
  2. Markdown, which is the primary tool to format Hugo content, is relatively poor in formatting options. There is no way to change fonts, for instance, or to create a multi-column format. I found that using other formats is easier for extended formatting, but the limit is that current import/export tools prefer Markdown
  3. In the same vein, I found Markdown transfer pretty bad. Some content was reformatted entirely wrong, and tables seem to generally not be translated at all (the cells are simply placed into the Markdown as separate lines)
  4. I focused only on the content I wanted to render, so the theme was not completely ported. Only the portions that I could surface were addressed and the original theme may have functionality that is not available after the port


All in all, the process of migrating my static WordPress site to Hugo took less than four days of inconsistent work. In a pinch, it could all have been handled in a single day. So, it was a big success on that front.

The big question to ask is why you would want to convert a site. In my case, the content didn’t change much and WordPress changed more frequently, so switching to a static site was a pretty straightforward choice. I would feel differently if I added content more frequently, of if I needed access to content limited based on user account.

It is absolutely true that the static site loads much, much faster than the WordPress site. That’s both because WordPress has to talk to a database and does its rendering on the fly, and because the database and other infrastructure required by WordPress are relatively expensive computationally. I could put a series of Hugo sites on one single server and they are still faster than having WordPress et al on separate servers. That becomes a pretty neat cost saving.