En 2024 vi un video de NetworkChuck sobre por qué deberías tener un blog. La idea era construir un segundo cerebro — un lugar para documentar lo que aprendes, poder recuperarlo después y compartirlo con otros. Eso fue suficiente para por fin animarme a hacerlo.
Mi primer setup fue un VPS de $8 al mes. Funcionaba, pero también significaba conectarme por SSH para las actualizaciones de seguridad, cuidar configuraciones de Nginx y estar pendiente del uptime de un solo servidor en una sola región. Nunca tuve una caída de verdad, pero el riesgo siempre estuvo ahí en mi cabeza — y la mayor parte de ese servidor pasaba inactivo mientras lo pagaba todos los meses.
Así que reconstruí todo. La nueva versión es Hugo con un tema personalizado, desplegado como un Cloudflare Worker. Sin servidores que gestionar, distribución global y una capa de API para las funcionalidades que quería agregar. Déjame contarte cómo encaja todo.
Por qué Hugo
Para un blog sin contenido dinámico, Hugo es difícil de superar. Compila todo a HTML estático en tiempo de build, maneja varios idiomas de forma nativa y la salida corre donde sea. Los tiempos de build se mantienen por debajo de un minuto incluso con docenas de posts.
Sí consideré un framework basado en JS, pero un blog personal no tiene razón para enviarle un runtime de hidratación a cada lector. Hugo mantiene la salida limpia y de eso se trataba.
Por qué Workers y no Pages
Cloudflare Pages hubiera manejado el hosting estático sin problema. La razón por la que fui por Workers es que quería una capa de API — específicamente dos cosas:
- Reacciones — los lectores pueden reaccionar a los posts con emojis, y los conteos viven en Cloudflare KV.
- Newsletter — los registros de email llegan a una base de datos Cloudflare D1 (SQLite).
Con Workers el mismo deployment sirve tanto el sitio estático como estos endpoints. Sin backend separado, sin servicios extra que conectar. Todo el router es esto:
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === '/api/reactions') return handleReactions(request, env);
if (url.pathname === '/api/subscribe') return handleSubscribe(request, env);
return env.ASSETS.fetch(request);
},
};
Todo lo demás pasa directo a los assets estáticos. Hugo los genera, el Worker los sirve.
El pipeline del build
Desplegar es una sola línea:
hugo --minify && npx pagefind --site public && npx wrangler deploy
Tres pasos detrás:
- Hugo compila el contenido y el tema personalizado en
./public - Pagefind indexa esa salida para la búsqueda — sin servicio de búsqueda externo
- Wrangler sube los assets y hace push del Worker a Cloudflare
Pagefind terminó siendo mi parte favorita. Corre en tiempo de build, envía un índice pequeño junto con la salida estática, y hace toda la búsqueda en el navegador. Sin agregar cosas adicionales que hagan más lento el build.
Reacciones con KV
KV es el almacén clave-valor de Cloudflare, y aparece como un binding dentro del Worker. Cada reacción de emoji es solo una clave por slug de post:
{slug}:{index} → conteo
Un GET /api/reactions?slug=mi-post lee los cuatro contadores en paralelo, y un POST /api/reactions?slug=mi-post&i=2 sube uno de ellos. El handler completo son unas 25 líneas.
KV es eventualmente consistente, lo cual suena aterrador hasta que recuerdas que estamos contando reacciones de emoji. Nadie necesita ver el número subir en tiempo real.
Newsletter con D1
D1 es el SQLite gestionado de Cloudflare. La tabla de suscriptores no tiene nada del otro mundo:
CREATE TABLE subscribers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
source TEXT,
country TEXT,
referrer TEXT,
language TEXT
);
Un pequeño truco: el Worker lee cf-ipcountry de los headers de la solicitud, así obtengo el país del suscriptor sin tener que pedírselo. Las migraciones corren con un script de shell que lleva registro de qué archivos ya aplicó en una tabla _migrations.
Dos idiomas desde el inicio
Escribo todo en inglés y español, así que el setup multi idioma de Hugo era obligatorio. Usa directorios de contenido separados por idioma:
content/
english/posts/
spanish/posts/
Cada post tiene su propio slug por idioma, así las URLs quedan limpias en ambos lados — sin un prefijo /en/ ensuciando la versión en inglés. El tema simplemente cambia el idioma de la interfaz según la sección en la que estés.
Qué cambió en realidad
Antes era un servidor en una región, mantenimiento manual y sin API. Después del rediseño son más de 275 ubicaciones alrededor del mundo, cero servidores que yo tenga que tocar, reacciones y newsletter integrados, búsqueda de texto completo, tema oscuro/claro y un homepage estilo terminal CLI.
Y los $8 al mes se fueron — el nivel gratuito de Cloudflare cubre toda esta configuración. No es mal trato.



