Ir al contenido
Nicolás Calderón
Volver

Publico este blog desde la terminal y no necesito nada más

Terminal de código publicando contenido

Este post lo estoy escribiendo en la terminal. No en un editor de texto, no en un dashboard de CMS, no en una interfaz web con drag-and-drop y preview en tiempo real. En una terminal. El archivo va a terminar en src/content/posts/, voy a hacer git push, y en 90 segundos va a estar en producción.

No es minimalismo performativo. Es la conclusión lógica de eliminar todo lo que no necesito.

La tentación del CMS headless

Cuando decides publicar un blog personal en 2026, la industria te presenta un menú de opciones que suenan razonables: Contentful, Sanity, Strapi, Payload, Keystatic, Tina. Todas prometen lo mismo con variaciones cosméticas: un dashboard bonito, content modeling flexible, API GraphQL o REST, preview deployments, roles de usuario, workflows de aprobación.

El problema es que estoy solo. Soy una persona escribiendo posts para un sitio personal. No tengo un equipo editorial, ni necesito workflows de aprobación ni roles de usuario.

Cada feature de un CMS headless resuelve un problema de coordinación entre personas. Si eres una persona, esas features son overhead puro. Tokens de API que administrar, un servicio más que puede caerse, un dashboard más que revisar, un billing más que pagar, un vendor más del que depender.

Un CMS headless para un blog personal de una sola persona es como alquilar una oficina de 200 metros cuadrados para trabajar solo. Puedes hacerlo. Nadie te lo prohíbe. Pero cada mes que pagas el alquiler estás financiando un problema que no tienes.

Lo que realmente necesito

Lo que necesito es escribir texto con frontmatter, que se compile a HTML estático, y que se sirva desde un CDN. Eso es todo. Lo demás es ruido.

El sitio corre sobre Astro 7 con el template AstroPaper. La configuración vive en astro-paper.config.ts:

export default defineAstroPaperConfig({
  site: {
    url: "https://nicolascalderon.digital/",
    title: "Nicolás Calderón",
    author: "Nicolás Calderón",
    lang: "es",
    timezone: "America/Bogota",
  },
  features: {
    lightAndDarkMode: false,
    dynamicOgImage: true,
    search: "pagefind",
  },
});

Los posts viven en src/content/posts/ como archivos .md o .mdx. El content schema está definido con Zod en src/content.config.ts:

const posts = defineCollection({
  loader: glob({ pattern: "**/[^_]*.{md,mdx}", base: `./${BLOG_PATH}` }),
  schema: ({ image }) =>
    z.object({
      author: z.string().default(config.site.author),
      pubDatetime: z.date(),
      title: z.string(),
      featured: z.boolean().optional(),
      draft: z.boolean().optional(),
      tags: z.array(z.string()).default(["others"]),
      ogImage: image().or(z.string()).optional(),
      description: z.string(),
    }),
});

Astro lee los archivos del filesystem, valida el frontmatter contra el schema, genera las páginas estáticas, y Pagefind indexa el contenido para búsqueda client-side. No hay base de datos. No hay API. No hay queries. El filesystem es la base de datos y git es la API.

El workflow real

Así se publica un post en este blog:

1. Abro la terminal
2. Le digo a Claude Code qué quiero escribir
3. Claude Code crea el archivo .md con frontmatter válido
4. git add, git commit, git push
5. Vercel detecta el push, corre astro build, deploya
6. El post está en producción

Paso 5 tarda entre 60 y 90 segundos. Los pasos 1 a 4 tardan lo que tarde la escritura. No hay paso donde abra un browser. No hay paso donde haga login en un dashboard. No hay paso donde configure algo.

El build de Astro incluye astro check (type-checking del content schema), astro build (generación estática), y pagefind --site dist (indexación de búsqueda). Si el frontmatter tiene un campo mal tipado, el build falla y Vercel no deploya. El schema de Zod es el editor de contenido: si pasa validación, el post es publicable.

Claude Code como CMS

Acá es donde la cosa se vuelve interesante. Claude Code no es un CMS. Es un agente que opera en tu filesystem. Pero cuando el filesystem es tu CMS, la distinción se evapora.

Le digo “escribe un post sobre X con estos tags y esta fecha de publicación”. Claude Code lee la estructura del proyecto, entiende el schema del frontmatter, genera el archivo en la ruta correcta, y lo escribe. No necesita un plugin de Astro. No necesita una integración. Lee los archivos que ya existen, infiere el patrón, y lo replica.

Esto es lo que el archivo CLAUDE.md del proyecto le dice al agente sobre el entorno:

## Development
When starting the dev server, use background mode:
astro dev --background

Eso es toda la configuración que necesita. El resto lo infiere del package.json, del astro.config.ts, del content schema, y de los posts existentes. Cuando le pido un post nuevo, ya sabe que el frontmatter necesita pubDatetime como z.date(), que los tags son un array de strings, que el ogImage puede ser una referencia a @/assets/images/, y que el archivo va en src/content/posts/.

El agente no reemplaza un CMS. Reemplaza la necesidad de un CMS. La diferencia es importante: un CMS te da una interfaz para hacer algo que podrías hacer sin interfaz. El agente hace la cosa directamente.

Lo que gano y lo que pierdo

Lo que gano es obvio: zero dependencias externas para la gestión de contenido. No hay un servicio de terceros que pueda cambiar sus precios, deprecar su API, o irse a bancarrota. Mi contenido vive en archivos de texto plano en un repositorio de git. Puedo leerlo con cat. Puedo buscarlo con grep. Puedo editarlo con cualquier editor de texto que exista hoy o que exista en 20 años. El formato no caduca.

También gano velocidad. El ciclo de publicación es: escribir, push, esperar 90 segundos. No hay fricción intermediaria. No hay una sesión que expire, un token que renovar, un deploy que aprobar.

Lo que pierdo: no tengo preview visual antes de publicar. No tengo un editor WYSIWYG. No tengo un calendario editorial con vista de Kanban. No tengo analytics integrados en el dashboard del CMS. No tengo un botón de “Unpublish” que sea más rápido que cambiar draft: false a draft: true y hacer push.

Pierdo comodidades que nunca necesité. El trade-off es asimétrico a mi favor.

El stack completo, sin misterio

Para que quede documentado y cualquiera pueda replicarlo:

Framework: Astro 7 con MDX, Tailwind CSS 4, Pagefind para búsqueda estática. Shiki para syntax highlighting con el tema Night Owl. Remark plugins para table of contents y collapsible sections. Rehype callouts para admonitions.

Content: Archivos .md en src/content/posts/. Schema validado con Zod via Astro Content Collections (loader glob). Frontmatter tipado: author, pubDatetime, title, tags, description, ogImage. Si el schema no valida, el build falla.

Build: astro check && astro build && pagefind --site dist. Type-check, generación estática, indexación de búsqueda. Un comando.

Deploy: Push a main en GitHub. Vercel detecta el push, corre el build, sirve desde su CDN. Zero configuración después del setup inicial. No hay vercel.json.

Authoring: Claude Code en la terminal. Lee el proyecto, genera el archivo, yo hago push. Alternativamente, abro el .md en cualquier editor y escribo a mano. Las dos rutas convergen en el mismo git push.

OG Images: Generación dinámica con Satori. Cada post genera su propia imagen para redes sociales a partir del título y la descripción. Sin Figma, sin Canva, sin templates manuales.

La lección para builders

La industria del desarrollo web tiene un sesgo sistemático hacia la complejidad. Cada problema se resuelve agregando una capa más: un servicio, una abstracción, una integración, un dashboard. Y cada capa que agregas es una capa que tienes que mantener, monitorear, pagar y entender.

La pregunta que me hago antes de agregar cualquier herramienta es: “¿Esto resuelve un problema que realmente tengo, o un problema que podría tener si mi situación fuera diferente?” Un CMS headless resuelve problemas reales para equipos editoriales de 5 personas. Para mí, resuelve problemas que no existen.

Hay un directorio con archivos de texto, un repositorio de git, y 90 segundos entre git push y producción.

Eso es todo. Y todo es suficiente.


Comparte este post:

Post anterior
75 minutos de la terminal a una campaña de outbound completa
Post siguiente
Puse a Wittgenstein, Quine y Frege a revisar traducciones de un AI. Esto es lo que pasó.