TL;DR
Quería un sitio donde escribir sobre cada proyecto que construyo, siempre con la misma estructura, y luego compartir cada post en LinkedIn y en X. La web está hecha con Astro y corre como un contenedor de nginx en mi VPS con Dokploy. Cada post también se publica en dev.to y Hashnode, con un enlace de vuelta al original aquí.
¿Qué es esto?
Es mi build log personal. Cada número es un proyecto, y lo repaso siempre en el mismo orden: qué es, por qué lo construí, cómo está hecho, una demo, qué salió mal, las cifras y qué aprendí. Este es el número 01, y va sobre el propio blog.
¿Por qué lo construí?
Siempre he querido un sitio público donde compartir lo que voy encontrando mientras construyo cosas. No me refiero a historias de éxito bien pulidas. Me refiero a cómo está montado un proyecto de verdad, dónde se rompió y qué haría distinto la próxima vez.
Lo que al final me empujó fue darme cuenta de lo mucho que ha cambiado mi forma de construir. Ya no abro VSCode para construir o mejorar un sistema. Así que el tiempo es menos problema, y las ideas lo son más. Y si el código lo escribe un agente, compartir el código tampoco aporta mucho. Cualquier modelo frontier puede reconstruir algo muy parecido a partir de un buen prompt. Por eso cada post de aquí termina con ese prompt.
Lo que probé primero
| Opción | Por qué no encajaba |
|---|---|
| Medium | Ya no da tokens de API, así que no podía publicar automáticamente |
| Substack | No tiene API pública |
| Solo Hashnode | Buena API y buena audiencia, pero la web no sería mía |
¿Cómo lo construí?
Arquitectura
Fig. 1 · Línea continua = automático al hacer push a release. Discontinua = manual, después de publicar.
Lo único que corre en el servidor es nginx. Cuando hago push a la rama release, GitHub Actions construye una imagen Docker, la sube al registry y llama a la API application.deploy de Dokploy. El proxy de Dokploy se encarga del TLS delante del contenedor. Yo trabajo en main, así que no se publica nada a menos que haga push a release a propósito.
Stack y dependencias
| Pieza | Elección | Por qué |
|---|---|---|
| astro | ^7.3 | Markdown en git: publicar es un commit |
| @astrojs/mdx | ^8.0 | Componentes dentro de los artículos cuando hacen falta |
| @astrojs/rss | ^4.0 | Un feed para quien no vive en LinkedIn |
| nginx | alpine-slim | Sirve el dist/ estático en :3000 |
| Dokploy | en mi VPS | El mismo sitio que mis otros proyectos pequeños |
Decisiones clave y trade-offs
Quería que el original de cada post viviera en mi propia web. La URL canónica siempre apunta aquí. Después, npm run crosspost manda el mismo Markdown a dev.to y Hashnode con canonical_url, para que los buscadores sepan de dónde viene.
Cada artículo declara también los datos del proyecto en el frontmatter (estado, stack, herramientas de IA, tiempo de construcción). La franja de especificaciones de arriba de esta página se genera a partir de ahí:
project: z.object({
status: z.enum(['prototype', 'live', 'archived']),
timeSpent: z.string().optional(),
stack: z.array(z.string()).default([]),
aiTools: z.array(z.string()).default([]),
})
Cómo encajó la IA en el proceso
Yo decidí qué tenía que ser el blog y cómo tenía que verse. Los agentes hicieron la construcción.
- Design Canvas para el aspecto: el diseño tipo ficha técnica, la banda del about, la tabla de números. Luego los cambios que hice en el canvas se pasaron al código.
- Opus 5.5 en Claude Code para todo lo demás: la web en Astro, el esquema de contenido, la imagen Docker, el workflow de deploy y el script de publicación cruzada.
Pasé la mayor parte de la semana dándole vueltas a los detalles. El logo es un buen ejemplo. Claude dibujó hojas de opciones, cada una a tamaño real en la cabecera y como favicon de 16px, y yo iba eligiendo y pidiendo cambios hasta que llegué a lo que quería: > /AP, como un slash command, con un destello como cursor de la IA.
La parte que reutilizaría en otros proyectos es la skill /write-article. Primero lee el repo del proyecto (dependencias, infra, historial de git), rellena todo lo que el código puede responder y solo entonces me pregunta por el resto, en dos rondas como mucho. No puede inventarse anécdotas ni cifras. Si no tengo respuesta, deja un TODO.
| Skill / herramienta | Para qué |
|---|---|
/write-article |
Montar el esqueleto de cada post, entrevistarme y escribir el copy para redes |
| archify | Los diagramas de arquitectura |
remotion-motion-graphics / onetake |
Convertir grabaciones de pantalla en vídeos de demo |
Demo
La estás leyendo: andresprada.blog. Cada post es un fichero Markdown en el repo, y publico haciendo push a release. Todavía os debo una grabación de /write-article redactando un post.
Qué salió mal
- Lo más difícil no fue técnico. No paraba de darle vueltas a para quién son estos posts. La gente los lee por encima buscando la historia, pero los agentes van a leer la misma página e intentar reutilizarla. Escribir para los dos a la vez hizo que los primeros borradores fueran peores. Al final los separé: el artículo es para las personas, y el prompt de “Replícalo” del final es para sus agentes.
- Hay demasiadas skills de vídeo. Remotion, onetake y otras se solapan, y no estaba claro cuál encajaba para una demo corta de producto. Los primeros vídeos necesitaron varias rondas antes de que los hubiera publicado.
- El servidor de desarrollo de Astro seguía sirviendo estilos antiguos de los componentes después de editarlos, dos veces el mismo día. Un cambio que funcionaba parecía roto hasta que reiniciaba el servidor.
En cifras
| Tiempo | 6 días, del domingo 27 de septiembre al viernes 2 de octubre. El pipeline de deploy entró el primer día |
| Coste | €0 extra. El VPS ya ejecuta mis otros proyectos, y la construcción va con mi suscripción de Claude |
| Líneas de código | ~2.650 (Astro, TypeScript, CSS, scripts, CI), sin contar los posts |
| Contenido | 2 builds, una plantilla fija de build y una plantilla de ensayo |
| Usuarios | Tú |
Lecciones aprendidas
Escribir a la vez para personas y para agentes no funcionó. Cada párrafo salía peor. Un prompt claro al final le sirve más a un agente que un post entero escrito para él.
Monté el deploy antes que cualquier diseño, y me alegro. Estuvo en producción el primer día. A partir de ahí estaba cambiando algo real, que es mucho más fácil de juzgar que un mockup.
Algunas cosas solo tienen sentido a tamaño real. Los iconos, favicons y badges quedaban bien como dibujos grandes. Solo supe si funcionaban cuando los vi en la cabecera y en una pestaña del navegador.
La skill hace mejores preguntas después de leer el repo. Y hace muchas menos.
Replícalo
Pega esto en Claude Code (o en cualquier agente de programación) en una carpeta vacía. Cambia el dominio y el servidor por los tuyos.
Build me a personal build log: a blog where every post takes one project apart in the same structure, and that I can cross-post without losing the original URL.
1. Astro with Markdown in git, so publishing is a commit. Add MDX, RSS and a sitemap.
2. A typed content collection: `kind` (build or essay), `draft`, tags, and for builds a `project` block (status, time spent, stack, AI tools, repo, demo). Render a spec strip at the top of each build from that block.
3. A build template with fixed sections: TL;DR, What is this?, Why did I build it?, How did I build it? (architecture, stack, key decisions, how AI fit in), Demo, What went wrong, By the numbers, Lessons learned, Replicate it.
4. Ship it as a static nginx container. A push to a `release` branch builds the Docker image in GitHub Actions, pushes it to a registry and triggers a deploy on my Dokploy VPS through its API. Working on `main` must never publish.
5. A `npm run crosspost -- <slug>` script that publishes the same Markdown to dev.to and Hashnode with `canonical_url` pointing back to my site, rewrites root-relative image paths to absolute URLs, supports `--dry-run`, and writes the resulting URLs back into the frontmatter so re-runs are safe.
6. A Claude Code skill, `/write-article`, that interviews me about a project, fills the template, draws the architecture diagram, and writes the LinkedIn post and X thread.
Keep everything I publish under `public/` with root-relative paths, and confirm with me before anything public happens.