↓ Skip to main content
  1. Posts/

The Admin Workshop - Migrating from Wordpress to Hugo

Rubén Infante Docio
Author
Rubén Infante Docio
Software engineer
Table of Contents

For years I have had this blog in mind, thinking, let’s see if I make an entry about this or that, time has gone by and when I review it I have realized that I have not written a single post for 8 years.

In today’s age of AI, it may not make sense to maintain a blog, but I would like to maintain my writing skills and communicate the personal projects I do.

This blog was created 19 years ago with the name “Mis Cosas Geek”, using “Geek” as a term associated with a person fascinated by technology, however in recent years the term has been increasingly associated with pop culture, video games, comics, movies and cult music, as well as science fiction and fantasy and that is not the purpose of this blog, therefore, from now on dear silicon lovers this site will be called “The Admin’s workshop” and we will continue talking about electronic botched, development and some new topics such as home automation and 3D printing.

The new Stack
#

This name change could not be the only novelty and because of this it has also been migrated at a technological level going to generate static HTML, the reasons for this change are, to avoid maintaining a database and writing in Markdown, as well as SEO, performance, accessibility and security improvements (basically because it is static html :D).

Choosing a static site generator
#

The choice has been Hugo, in this new stage I want to focus on writing instead of maintaining plugins, updates, SEO etc while maintaining the best possible performance. Hugo brings the following advantages over similar technologies such as Astro, Jekyll or Next.js.

  • It has no dependencies
  • The learning curve is low.
  • Its speed is very high.
  • It is modern, compared above all to Jekyll
  • Simplicity

The next thing is to choose a theme that is robust, well-designed, with built-in functionalities and that is not overloaded, after comparing multiple existing themes Blowfish was chosen.

Export
#

For the migration, the first thing is to extract the contents and files from the old Wordpress using one of the libraries available on Github. After comparing multiple tools such as wordpress-export-to-markdown,markdown-exporter-for-wordpress and wp2hugo, the latter was the clear winner due to the following advantages:

  • Extract comments
  • It allows you to download not only the referenced images and files but also all the existing ones in Wordpress
  • Transform Wordpress HTML correctly by including code blocks
  • Allows you to adjust the structure of files and folders in the new blog
  • Maintains wordpress and GUID slugs

The Deployment Flow
#

In terms of deployment, although in my professional life I have used Jenkins, Gitlab CI/CD templates and Azure Devops, I had never used Github Actions and the experience has been wonderful.

For the “hosting” of the site, Cloudflare Workers has been used which allows free deployments even in private repositories, unlimited requests, 20,000 files, 25Mb per file, real 301 redirects (to maintain redirects of the old Wordpress RSS feeds), 404 pages per language among others.

It’s all in code: .github/workflows/publish.yml (Action “Publish”) and Cloudflare wrangler.jsonc (Worker).

Each push a main launches the Action, which does two jobs in a row:

1. Translation
#

I would like the blog to reach more people, for this I have chosen the most spoken languages in the world and the posts will be automatically translated into them:

  1. Spanish
  2. English
  3. Mandarin Chinese
  4. Hindi
  5. Spanish
  6. French
  7. Arabic
  8. Bengali
  9. Portuguese
  10. Russian

To do this, I use translate.py that translates new or modified posts using Microsoft Translator free of charge and uploads the translations to the repository. If there is nothing, the script finishes in seconds without spending any fee. - If the translation fails or the quota runs out: it is displayed anyway, so the post in Spanish is published anyway. - You can resume pending translations or redeploy by hand: Actions → Publish → Run workflow. - GitHub secrets: AZURE_TRANSLATOR_KEY, AZURE_TRANSLATOR_REGION, CLOUDFLARE_API_TOKEN (“Edit Cloudflare Workers” template, zone talleradmin.com) and CLOUDFLARE_ACCOUNT_ID.

2. The deployment
#

Build with Hugo (the latest version available with Blowfish support) and deploy with wrangler.jsonc in Cloudflare’s Worker.

The Newsletter
#

I’m not sure if newsletters are still alive or dying, so I’ve created one with [Brevo](Para Newsletter) to test it. I’ve been surprised that a feature that 20 years ago offered hundreds of websites for free to webmasters is now ultra-limited paid services. Still, according to what I’ve been able to research, Claude and I Brevo seems to offer the best free plan with RSS automation.

Other topics reviewed and migrated
#

  • Accessibility

  • Adsense

  • Google Search console: migrate the blog since the domain has also changed rubeninfante.com to talleradmin.com

  • Aliexpress and Amazon Affiliate Links

  • Optimize images

    sudo apt install optipng jpegoptim
    find static/wp-content -iname '_.png' -print0 | xargs -0 -P"$(nproc)" -n4 optipng -o2 -strip all -quiet
    find static/wp-content -iname '_.jp\*g' -print0 | xargs -0 jpegoptim --strip-all --all-progressive -q
  • Review robots.txt

  • Review of accents in categories and tags

  • In my case I have cleaned up tags and categories using the static/_redirects file

  • Clean up unused files

  • I’ve added images to each page and a generic one for when there is no static/img/social-default.jpg and assets/img/background.jpg

  • Review Wordpress plugins before migration and disable all but the essential ones.

  • Modify and create redirects from rubeninfante.com to talleradmin.com

In daily work
#

Commands
#

  • Create a new item

    hugo new posts/mi-nuevo-articulo/index.md
    • If an image in the mi-nuevo-articulo folder is called feature.[jpg,png,webp] it will be used as the main image, header and social media
  • Check for broken links

    • Create the lychee.toml file at the root of the site with content

        # lychee.toml — enlaces que no se comprueban (expresiones regulares)
        exclude = [
          "#nolychee$",   # la marca de evitar enlaces
          "https://web.archive.org/web/*",
          "reddit.com/submit",
          "facebook.com/sharer",
          "linkedin.com/shareArticle"
        ]
        exclude_path = ["^public/(en|zh-cn|hi|fr|ar|bn|pt-br|ru)/"]
    • Run the command:

        hugo --gc --cleanDestinationDir
        lychee --root-dir "$PWD/public" 'public/**/*.html' --format markdown --output informe.md
    • Review informe.md

      • Add https://web.archive.org/web/to the beginning of the url if the link no longer exists
      • Add #nolychee to the end of a link or to the lychee.toml file if the detection is incorrect due to anti-bots or another reason

To update the theme
#

  • Update the theme

      cd themes/blowfish
      OLD=$(git rev-parse HEAD)                                  # para comparar después
      git fetch --depth 1 origin main && git checkout -B main origin/main
  • Review the files that you have overwritten on the subject

      cd ../..
      for f in $(cd layouts && find . -type f); do git -C themes/blowfish diff --quiet $OLD HEAD -- "layouts/${f#./}" 2>/dev/null || echo "Revisar: layouts/${f#./}"; done

Images
#

It is important to know the routes in which to store the images of the Blog:

  • assets for images that the theme uses and can optimize: the background (defaultBackgroundImage), the default featured image (defaultFeaturedImage), and your author photo. That’s why background.jpg is fine there: the theme shrinks it and the 3.4 MB isn’t served.

  • static for which you need a fixed and unchanged URL: favicons, ads.txt, PDFs, files to download or images to which you link from outside. As an example I social-default.jpg put it there because Whatsapp and X ask for a stable URL.

  • Imágenes de un post concreto: In the folder of the post itself (page bundle).

  • NOTE In hugo.toml I set up static also as assets. That’s why static/ images are also optimized when the theme uses them as a cover. It’s a fix for the WordPress migration.

      [[module.mounts]]
        source = "static"
        target = "assets"

Add a language
#

  • Files to be modified/added

      config/_default/languages.xx.toml
      config/_default/menus.xx.toml
      i18n/xx.toml (Sólo en el caso del que el tema no incluya el idioma)
  • Modify /config/_default/params.toml

      newsletter.formAction]
        es = "https://..."
        xx = "https://..."