
Cuando decides construir algo desde cero, las primeras decisiones son las que más impacto tienen. El framework, la base de datos, el ORM y la estrategia de despliegue definen el techo de lo que puedes hacer y el suelo de lo que vas a sufrir.
En nuestro caso, el stack quedó así. Next.js como framework, SQLite como base de datos, Drizzle como ORM, TypeScript en todas partes y Docker con Dokploy para el despliegue. Cada elección tiene una historia detrás.
Next.js no es perfecto. Tiene una curva de complejidad que crece rápido cuando mezclas Server Components, Server Actions y rutas dinámicas. Pero tiene algo que ningún otro framework nos ofrecía en el mismo paquete. Un router maduro con App Router, soporte nativo para ISR (Incremental Static Regeneration), API routes integradas y un ecosistema enorme de librerías compatibles.
Astro era tentador por su rendimiento estático, pero nuestro blog necesita un panel de administración completo con autenticación, editor WYSIWYG, subida de archivos y operaciones CRUD. Eso es una aplicación web, no un sitio estático. Astro puede hacerlo con islands de React, pero a ese nivel estás luchando contra el framework en vez de usarlo.
Remix y SvelteKit son excelentes, pero la comunidad y el ecosistema de componentes (shadcn/ui, Tiptap, Shiki) están mucho más maduros en React y Next.js. Para un proyecto donde cada semana queremos añadir funcionalidades sin tener que escribir todo desde cero, eso importa mucho.
Esta fue probablemente la decisión más contraintuitiva. "¿SQLite para producción? ¿En serio?" Sí, en serio. Y funciona mejor de lo que la mayoría espera.
Un blog personal no es una aplicación con miles de escrituras concurrentes. Es una aplicación donde un usuario (el administrador) escribe contenido y miles de lectores lo leen. El patrón de lectura masiva con escritura ocasional es exactamente donde SQLite brilla gracias al modo WAL (Write-Ahead Logging).
Las ventajas son enormes en simplificación operativa.
Los pragmas de configuración que usamos optimizan SQLite para nuestro caso. WAL para concurrencia, busy_timeout de 30 segundos para evitar bloqueos, cache de 20 MB en memoria y memory-mapped I/O de 64 MB para que las lecturas frecuentes no toquen disco.
Probamos Prisma primero. Es el ORM más popular del ecosistema TypeScript y por algo será. Pero Prisma genera un cliente propio, necesita un paso de generate después de cada cambio de schema, y su soporte para SQLite tiene limitaciones que nos dieron problemas con las migraciones.
Drizzle es diferente. El schema se define en TypeScript puro, las queries son type-safe hasta el nivel de columna y las migraciones se generan comparando el schema actual con el anterior. Sin binarios intermedios, sin generación de código. Solo TypeScript. Hablamos de esto con más detalle en las migraciones con Drizzle ORM.
El flujo es simple. Editas el schema en drizzle/schema.ts, ejecutas pnpm db:generate para crear el SQL de migración, revisas el SQL generado y aplicas con pnpm db:migrate. Si algo no cuadra, el SQL está ahí para que lo leas y entiendas exactamente qué va a pasar.
Vercel es la opción obvia para Next.js. Es del mismo equipo, el despliegue es instantáneo y el free tier es generoso. Pero Vercel tiene limitaciones que nos incomodaban.
La principal es que Vercel ejecuta cada ruta como una función serverless. Eso significa que SQLite no funciona porque no hay un sistema de archivos persistente entre invocaciones. Necesitaríamos migrar a PostgreSQL con Neon o Turso, añadiendo complejidad y latencia de red que habíamos evitado deliberadamente.
La alternativa fue autoalojar con Docker en un VPS. El Dockerfile usa un build multi-stage con Alpine Linux que produce una imagen final de apenas unos megabytes. El entrypoint ejecuta automáticamente el WAL checkpoint, las migraciones pendientes, el seed del usuario admin y arranca el servidor. Desde la perspectiva de seguridad, en la seguridad en la cadena de suministro de Docker profundizamos en este aspecto.
Para la orquestación usamos Dokploy, un PaaS open source que se instala en cualquier VPS y gestiona el ciclo de vida de las aplicaciones. Cada push a main dispara un build y despliegue automático. Traefik se encarga del proxy reverso y los certificados SSL. El resultado es una experiencia similar a Vercel pero en infraestructura propia, sin límites artificiales y con un coste predecible. Si quieres profundizar, en la infraestructura con Dokploy lo cubrimos en detalle.
El blog es TypeScript de arriba a abajo. Desde el schema de la base de datos hasta los componentes de React pasando por las Server Actions y las API routes. Esto no es casualidad.
Cuando el schema de Drizzle define que un campo es text("status").$type<"draft" | "published" | "scheduled">(), TypeScript se asegura de que nunca pases un valor inválido en ninguna parte del código. Si renombras un campo, el compilador te avisa de todos los sitios que hay que actualizar. Esa red de seguridad es invaluable cuando estás moviendo rápido y añadiendo features cada semana.
Después de meses usando este stack en producción, las lecciones son claras.
"El mejor stack no es el más popular. Es el que te permite moverte rápido sin tropezar con tus propias decisiones."

Autoalojar un blog significa que la seguridad, el SEO y el rendimiento son tu responsabilidad. Cabeceras CSP, rate limiting, imágenes OG generadas, ISR y las lecciones que aprendimos configurándolo todo.

Teníamos WordPress, Ghost y decenas de plataformas a un clic de distancia. Pero ninguna nos daba lo que necesitábamos sin compromisos. Esta es la historia de por qué acabamos escribiendo nuestro propio motor de blog.

El flujo completo de migraciones en proyectos reales con Drizzle ORM, desde el esquema en TypeScript hasta producción. Con comparativa frente a Prisma y lecciones aprendidas.