Chau Wordpress, hola Hugo
Hace tiempo que venía con ganas de dejar de usar Wordpress, y finalmente lo hice.

Mis problemas con Wordpress
Wordpress es un software muy flexible. Tiene un montón de opciones. Podés básicamente hacer lo que quieras. El problema es que eso incluye un montón de complejidad e ineficiencia. Si querés manejar un blog con cincuenta autores publicando diez posts por día y manejando múltiples drafts, y de paso agregarle un ecommerce o algo así, Wordpress rinde. Si querés un blog para una sola persona con un post cada dos meses… bueno, capaz que es un poco mucho.
Por qué elegí Wordpress
En el momento en que elegí Wordpress, tenía en la cabeza que quería integración con ActivityPub. En ese momento, había bastantes menos opciones que ahora. Después del cambio de dueños de Twitter, hubo un mayor interés en ActivityPub, y ahora hay muchas más opciones. Wordpress era también la opción por defecto.
Qué cambió
El tema de los comentarios fue lo que me terminó de convencer. Tener un blog con comentarios abiertos hoy en día es inviable. Está lleno de spammers. Levantás un blog, hacés un post, y entrás a recibir comentarios y comentarios. Wordpress tiene plugins anti-spam, pero en mi experiencia caen en tres categorías: los pagos, los que no bloquean nada, y los que bloquean demasiado. Capaz que los pagos también caen en las otras dos categorías, no los probé. Al final, me rendí con la idea de tener comentarios. No vale la pena el esfuerzo.
Por otro lado, la integración con ActivityPub no es tan útil al final. Lo de que sea automático ayuda, pero de nuevo, hago un post cada dos meses con suerte. No necesito automático, puedo poner el link en mi cuenta de Mastodón y listo. Y creo que es más probable que alguien responda ahí que en los comentarios, lo que también apunta a “no necesito los comentarios”.
Hugo
Hugo es un generador de sitios estáticos. En lugar de tener todo un sistema con base de datos, y cuentas de usuario, y todos los chiches, Hugo agarra una lista de archivos markdown fácil de manejar, y te genera un montón de HTML que podés subir a cualquier servidor web, y todo funciona.
En un principio pensé en otro motor de blogs, algo más chico que Wordpress, pero todavía dinámico. Pero la verdad es que no tiene sentido. No necesito un editor web para escribir texto. Mi computadora tiene un editor de texto. Más de uno, de hecho. Así que, Hugo. Hugo no es la única opción en cuanto a generadores de sitios estáticos. No sé cuál es el mejor. Probé Hugo, me gustó, hace lo que necesito, y ya está.
Migrando
Cuando me decidí a cambiar a Hugo, quería cumplir los siguientes objetivos, en orden de prioridad:
- Mantener los posts anteriores
- Mantener las URLs anteriores de los posts
- Mantener la URL del feed RSS
- Mantener el guid de cada post en el RSS, para evitar que aparezcan los posts de nuevo en los lectores RSS
Creo que pude lograr todos los objetivos
Migrar los posts
Para migrar los posts usé wp2hugo, una utilidad que a partir de un export XML de Wordpress, te arma un sitio Hugo. Hay otras opciones, de nuevo, no sé cuál es la mejor. El problema es que el sitio generado no me servía para lo que quería. Para empezar, ya tenía un sitio andando con el theme configurado más o menos como quería, y wp2hugo te arma un sitio de cero. Así que copié los posts y las imágenes al sitio que tenía armado. Tampoco me gustaba la organización, así que tuve que ir post por post corrigiendo la ubicación de las imágenes y otros archivos. Con 20 posts, fue un poco molesto, pero realizable. Si hubiese tenido cientos de posts, capaz que tenía que automatizar una solución, o buscar otra herramienta para migrar.
Además de eso, algunos posts no funcionaban (todos los que tuviesen imágenes con un texto alternativo de más de una línea), y tuve que arreglarlos a mano. Pero eso sí fueron pocos casos.
Y como último detalle, la fecha y hora de los posts me importó cualquier cosa. En lugar de la fecha del post, me puso la fecha de la última interacción, así que los tuve que corregir a mano.
wp2hugo no sólo migró el contenido de los posts, también los metadatos como fecha y hora de publicación, autor (siempre yo, pero no importa), y, más importante, la URL. Así que los puntos 1 y 2 de arriba ya quedaron logrados.
Deploy
Una cosa es tener un sitio estático, algo muy diferente sería publicar “a mano” cada vez que quiero hacer un post. Así que hice lo que (creo) es estándar entre los usuarios de Hugo: un repositorio git (en Forgejo) con un workflow que se encargue de publicar el sitio. Como servidor web, un nginx sencillo.
El workflow que uso es este:
name: Publicar el blog
on:
push:
branches: [ main ]
workflow_dispatch:
schedule:
- cron: '22 19,22 * * *'
jobs:
build-and-deploy:
runs-on: docker
container:
image: node:24-alpine
steps:
- name: Actualizar lista de paquetes
run: apk update
- name: Instalar Hugo
run: apk add hugo
# Necesito git instalado para poder cargar submódulos
- name: Instalar git
run: apk add git
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Generar sitio
env:
HUGO_ENV: production
run: |
hugo \
--minify \
--cleanDestinationDir
- name: Validar sitio generado
run: |
if [ ! -f "public/index.html" ]; then
echo "ERROR: Cagamos, no hay un index.html"
exit 1
fi
echo "Sitio generado"
- name: Instalar ssh
run: apk add openssh && ssh -V
- name: Preparar SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- name: Instalar rsync
run: apk add rsync && rsync --version
- name: Mandar al servidor
run: |
rsync -avz --delete \
-e "ssh -o StrictHostKeyChecking=no" \
./public/ \
${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DEPLOY_PATH }}
echo "Listo el pollo"
Creé un usuario específico con acceso de escritura al directorio del blog, y todos los datos van en los secrets de Forgejo, bien prolijo. Además de correr el workflow cuando hago un commit, se corre también dos veces al día (19:22 y 22:22), y me permite ejecutarlo cuando quiera. Hugo permite marcar posts con una fecha futura, y no los genera, pero como la generación es estática, tenés que volver a correrla cuando pase la fecha. Con el cron se soluciona ese problema.
Mantener el feed RSS
Solucionados los posts, las URLs, y el deploy, me queda sólo el tema del feed RSS. No sé si alguien más lee este blog, y en particular si alguien lo lee por RSS (me niego a recolectar estadísticas), pero algo que me ha molestado en otros casos es ver que cambian algo en el RSS y de repente me aparecen un montón de posts viejos en mi FreshRSS. Quiero intentar evitar eso.
Lo primero es mantener la URL. Wordpress usa la URL /feed/ para el RSS. Hugo usa index.xml (en particular, en este caso, /posts/index.xml es la que me interesa). Esto se soluciona simple con un poco de configuración de nginx:
# Redirigir feed RSS de WordPress
location = /feed/ {
return 301 /posts/index.xml;
}
location = /feed {
return 301 /posts/index.xml;
}
Podría haber puesto un único bloque location usando prefijos, pero la verdad es que preferí poner eso por si en el futuro hago algún post o algo que empiece por /feed. No debería pasar, pero bueno, son tres líneas extra, en algo que no debería necesitar cambiar.
Lo otro es cómo hacer saber al lector RSS que los posts son los mismos de antes. Para eso, mi intención es mantener el guid de los posts. Al importar, wp2hugo me guardó el guid en el front matter, pero el RSS generado no lo incluía. No tengo muy claro para qué usa Hugo el campo guid si es que lo usa para algo, pero, por defecto al menos, no lo usa para generar el RSS.
Por suerte Hugo es bastante personalizable, y lo único que tuve que hacer es copiar el template RSS por defecto a /layouts/rss.xml y modificarlo. El cambio es sencillo, sólo cambiar la línea que muestra el guid por esta:
<guid>{{ with .Params.guid }}{{ . }}{{ else }}{{ .Permalink }}{{ end }}</guid>
No lo probé, la verdad. Al momento de escribir esto, el dominio sigue apuntando al blog viejo. Para cuando se publique, vamos a ver qué pasa. Si sale todo bien, debería ser totalmente transparente la migración.
Qué falta
Me queda en el debe un par de cambios menores, sobre todo de texto. El theme que estoy usando (poison) tiene textos hardcodeados en inglés, y debería cambiarlos. En particular, me molesta el que dice “All rights reserved” en el sidebar, porque mi idea siempre es que todo lo que escribo es algún sabor de Creative Commons. No debería ser muy difícil de arreglar, pero bueno, eventualmente lo voy a hacer.
Por ahora, quedó listo el blog en Hugo.