Introducción: ¿Por qué construir desde cero?
En un mundo donde existen decenas de plataformas de blogging listas para usar (Medium, Dev.to, Ghost, WordPress), ¿por qué invertir tiempo en construir tu propio blog?
La respuesta es simple: el viaje es tan importante como el destino.
Como ingenieros de software, a veces caemos en la trampa de usar herramientas sin entender cómo funcionan por dentro. Este blog nace con dos propósitos:
- Aprender haciendo: Dominar tecnologías modernas como Astro, Docker y Directus
- Documentar el proceso: Compartir los desafíos técnicos y las soluciones encontradas
En este artículo, te cuento exactamente cómo construí Se2Code, las decisiones arquitectónicas que tomé y los problemas que resolví en el camino.
🏗️ La Arquitectura: Un Stack Moderno y Minimalista
El Diagrama Mental
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Astro │────▶│ Directus │────▶│ PostgreSQL │
│ (Frontend) │ │ (Backend) │ │ (Database) │
└─────────────┘ └──────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Docker │
│ (Orquestador│
└─────────────┘
¿Por qué estas tecnologías?
Astro:
- Rendimiento: Genera HTML estático por defecto, con hidratación parcial solo cuando es necesario
- Flexibilidad: Permite usar componentes de React, Vue o Svelte si los necesitas
- Developer Experience: Sintaxis familiar basada en HTML + JavaScript
Directus:
- Headless CMS: Separa el contenido de la presentación
- API Automática: Genera endpoints REST y GraphQL instantáneamente
- Panel de Administración: Interfaz intuitiva para gestionar contenido sin tocar código
Docker:
- Consistencia: El mismo entorno en desarrollo y producción
- Aislamiento: Cada servicio (Astro, Directus, PostgreSQL) corre en su propio contenedor
- Escalabilidad: Fácil de migrar a Kubernetes cuando sea necesario
El Diseño: Identidad DevOps
Quería que el sitio transmitiera profesionalismo técnico sin ser aburrido. El resultado: un tema oscuro inspirado en terminales y editores de código.
Paleta de Colores
css
--primary: #00ff88; /* Verde neón tipo terminal /
--secondary: #61dafb; / Azul React/Astro /
--bg: #0a0a0a; / Negro casi puro /
--bg-card: #1f1f23; / Gris oscuro para tarjetas /
--text: #e4e4e7; / Blanco suave */
Elementos Clave
Favicon Animado: Un SVG que simula una terminal con cursor parpadeante
Header Terminal: En móvil, un menú que se despliega como una ventana de terminal real (con los 3 puntos de colores de macOS)
Footer Consistente: Blindado contra conflictos de CSS para que se vea idéntico en todas las páginas
🚧 Los Desafíos Técnicos (y Cómo los Resolví)
- El Footer que No Se Veía Igual en Todas Partes
Problema: El footer se veía perfecto en el home, pero en los artículos aparecía sin márgenes y con una fuente diferente.
Diagnóstico:
- Las variables CSS (--bg-alt, --primary) no estaban disponibles en la página del artículo
- La clase .container del artículo (con max-width: 800px) estaba "secuestrando" al footer
Solución: - Crear un Layout.astro global que importe global.css una sola vez
- Usar process.env en lugar de import.meta.env para variables de entorno en SSR
- Renombrar .container a .article-container en los artículos para evitar conflictos
// Antes (no funcionaba)
const smtpHost = import.meta.env.SMTP_HOST;
// Después (funciona)
const smtpHost = process.env.SMTP_HOST;
- El Formulario de Contacto que No Enviaba Emails
Problema: El formulario guardaba los datos en Directus, pero los emails nunca llegaban.
Causa Raíz:
Astro en modo SSR (Server-Side Rendering) no puede acceder a import.meta.env en tiempo de ejecución.
Las variables de entorno solo están disponibles en build time.
Solución:Usar process.env para leer variables en runtime
Configurar Nodemailer con SMTP (sin depender de servicios de terceros)
Escapar caracteres peligrosos en el mensaje para evitar inyección HTML
const safeMessage = message
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
- URLs Sucias que Afectaban el SEO
Problema: El menú de terminal (diseñado para móvil) se mostraba también en pantallas grandes.
Solución:
- Aplicar display: none por defecto al menú móvil
- Usar media queries precisas (@media (max-width: 768px))
- Implementar visibility: hidden + opacity: 0 para transiciones suaves
.terminal-menu {
display: none; /* Oculto por defecto */
}
@media (max-width: 768px) {
.terminal-toggle {
display: block; /* Mostrar botón solo en móvil */
}
.terminal-menu.active {
display: flex; /* Mostrar solo cuando está activo */
}
}
🔐 Seguridad y SEO
Protección Contra Spam
Implementé Cloudflare Turnstile (una alternativa gratuita a reCAPTCHA) que:
- Verifica que el usuario es humano sin interacción visible
- No requiere que el usuario resuelva acertijos de "selecciona los semáforos"
- Es gratuito hasta 100,000 solicitudes/mes
Control de Indexación
Agregué una propiedad noindex al Layout para controlar qué páginas ve Google:
<Layout title="Contacto" noindex={true}>
<!-- Esta página no aparecerá en Google -->
</Layout>
Esto es crucial para páginas como /contact que no deben indexarse.
📧 Emails con Diseño Profesional
Los emails no son solo texto plano. Usé tablas HTML con estilos inline (la única forma confiable de hacer emails que se vean bien en clientes como Gmail, Outlook, etc.):
- Fondo oscuro:
#0a0a0a - Bordes: Verdes neón
- Tipografía: Monoespaciada
- Efecto visual: "Terminal de transmisión entrante"
📊 Resultados y Métricas
Lo que Logramos
- ✅ Performance: Lighthouse score de 95+ en performance y accesibilidad
- ✅ SEO: URLs limpias, meta tags dinámicos y sitemap automático
- ✅ UX: Formulario funcional con validación en tiempo real
- ✅ Mantenibilidad: Código modular, componentes reutilizables
- ✅ Escalabilidad: Arquitectura lista para migrar a Kubernetes
💡 Lecciones Aprendidas
- Las variables de entorno en SSR son diferentes: Entender la diferencia entre
process.envvsimport.meta.enves vital. - Los emails son un infierno: Siempre usa tablas y estilos inline para máxima compatibilidad.
- Docker Compose es tu amigo: Pero el flag
--force-recreatees tu mejor amigo. - El naming importa: Un simple
nombrevsnamepuede romper toda tu API.
🚀 Próximos Pasos
Este blog es un proyecto vivo. Actualmente en desarrollo:
- Migración a Kubernetes: Traducir el
docker-compose.ymla manifiestos de K8s. - Búsqueda Full-Text: Implementar búsqueda de artículos con PostgreSQL.
- Analytics Self-Hosted: Usar Plausible o Umami en lugar de Google Analytics para cuidar la privacidad.
- CI/CD: Configurar GitHub Actions para deployments automáticos.
🏁 Conclusión
Construir este blog desde cero no fue solo sobre escribir código; fue sobre tomar decisiones arquitectónicas, resolver problemas complejos y documentar el proceso para que otros puedan aprender.
Si llegaste hasta aquí, ¡gracias por leer! Si tienes preguntas o sugerencias, usa el formulario de contacto —¡te aseguro que realmente funciona!
¿Qué construirás tú hoy?