El stack tecnológico perfecto en 2025: lo que elegiría si arrancara un proyecto hoy
Son las 2 de la mañana. Tengo tres pestañas con documentación de frameworks que existían hace seis meses y ya tienen un sucesor. Hay un hilo de 200 respuestas en X donde la gente se pelea por si usar Bun o Node. Y yo, con el mate frío al lado, tomé una decisión: me bajo del carrusel del hype y te cuento qué elegiría hoy si tuviera que arrancar un proyecto desde cero.
No es un tutorial. Es una opinión. Fuerte, con fundamentos, y con los errores propios que la sostienen.
Por qué importa elegir bien el stack tecnológico en 2025
Elegir un stack tecnológico en 2025 no es lo mismo que elegir zapatillas. Una mala elección de stack te persigue durante años. Te lo digo por experiencia: en 2021 arranqué un proyecto con Create React App porque "era lo que conocía" y en 2023 estaba migrando a Vite con la misma energía con que uno muda un departamento después de una ruptura — doloroso, inevitable, y con cosas que directamente terminan en la basura.
El ecosistema de hoy tiene una trampa sutil: hay demasiadas opciones buenas. Y eso paraliza. Entonces lo que hago acá es simple: te digo qué elegiría yo, por qué, y qué descarté con argumentos concretos.
El stack: la decisión
Voy directo:
- Frontend: Next.js 15 con App Router
- Lenguaje: TypeScript en todo
- Estilos: Tailwind CSS v4
- Base de datos: PostgreSQL
- ORM: Drizzle ORM
- Auth: Auth.js (NextAuth v5)
- Deploy: Vercel para frontend, Railway o Fly.io para backend/DB
- Containerización: Docker para desarrollo local
- Testing: Vitest + Playwright
Eso es todo. Sin microservicios desde el día uno, sin Kubernetes hasta que de verdad lo necesites, sin event sourcing para una app que tiene doce usuarios.
Next.js 15: el centro de todo
Next.js es el framework que más veces me hizo decir "esto está buenísimo" y "esto me quiero matar" en el mismo día. Pero después de trabajar con él en serio, con el App Router desde que salió en versión estable, llegué a la conclusión de que nada más se le acerca para proyectos full-stack en 2025.
El App Router cambió todo. El modelo mental de Server Components vs Client Components al principio parece una abstracción innecesaria, pero cuando lo entendés, es como cuando aprendiste a andar en bicicleta: no podés creer que alguna vez hayas pensado diferente.
// app/productos/[id]/page.tsx
// Este componente corre en el servidor. Zero JS al cliente.
import { db } from '@/lib/db'
import { productos } from '@/lib/schema'
import { eq } from 'drizzle-orm'
interface Props {
params: { id: string }
}
export default async function ProductoPage({ params }: Props) {
const producto = await db
.select()
.from(productos)
.where(eq(productos.id, parseInt(params.id)))
.limit(1)
if (!producto.length) return <div>No encontrado</div>
return (
<article>
<h1>{producto[0].nombre}</h1>
<p>{producto[0].descripcion}</p>
</article>
)
}
Eso es una query directa a la base de datos desde un componente de React. Sin API route, sin fetch, sin loading states innecesarios. El HTML llega renderizado al browser. Esto en 2020 requería un backend separado, un endpoint REST, manejo de estados de carga... Era una locura.
TypeScript: no es opcional en 2025
En 2022 todavía discutía con gente si TypeScript valía la pena. En 2025, esa discusión me parece arqueológica. TypeScript no es "más trabajo" — es descubrir los bugs antes de que los descubra el cliente.
El momento en que me convertí fue en un proyecto de e-commerce donde teníamos una función que recibía el objeto de un pedido. Sin tipos, nadie sabía qué campos existían. Todos mirábamos la base de datos o el código anterior para saber qué tenía ese objeto. Con TypeScript:
interface Pedido {
id: string
userId: string
items: Array<{
productoId: string
cantidad: number
precioUnitario: number
}>
estado: 'pendiente' | 'pagado' | 'enviado' | 'cancelado'
creadoEn: Date
}
function calcularTotal(pedido: Pedido): number {
return pedido.items.reduce(
(acc, item) => acc + item.cantidad * item.precioUnitario,
0
)
}
Ahora el IDE te dice exactamente qué podés hacer con ese objeto. Si alguien agrega un campo nuevo a la interfaz, TypeScript te avisa en todos los lugares donde eso importa. Eso es productividad real.
Drizzle ORM: el ORM que no te esconde SQL
Acá es donde me voy a ganar algunos enemigos: Prisma está sobrevalorado.
Prisma es fantástico para empezar, la documentación es excelente, y el developer experience inicial es impecable. Pero cuando empezás a necesitar queries complejas, te encontrás luchando contra el ORM en lugar de trabajar con él. El cliente de Prisma es una capa de abstracción que a veces hace magia negra con el SQL generado y cuando algo falla, debuggear es una pesadilla.
Drizzle es diferente. Drizzle es "SQL pero con tipos". La API está diseñada para que sepas exactamente qué query se está ejecutando:
// lib/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'
export const usuarios = pgTable('usuarios', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
nombre: text('nombre').notNull(),
creadoEn: timestamp('creado_en').defaultNow()
})
export const pedidos = pgTable('pedidos', {
id: serial('id').primaryKey(),
usuarioId: integer('usuario_id').references(() => usuarios.id),
total: integer('total').notNull(),
estado: text('estado').notNull().default('pendiente')
})
// Uso en cualquier Server Component o Server Action
const usuariosConPedidos = await db
.select({
usuario: usuarios,
cantidadPedidos: count(pedidos.id)
})
.from(usuarios)
.leftJoin(pedidos, eq(pedidos.usuarioId, usuarios.id))
.groupBy(usuarios.id)
.where(gt(count(pedidos.id), 0))
Sabés qué SQL se ejecuta. Tenés autocompletado completo. Si el esquema cambia, TypeScript te rompe donde hay inconsistencias. Es la combinación perfecta entre control y ergonomía.
PostgreSQL: aburrido y perfecto
No, no voy a usar MongoDB. Ya lo usé. Ya migré de MongoDB a PostgreSQL en un proyecto que creció. Fue horrible.
PostgreSQL en 2025 hace todo: JSON nativo si necesitás flexibilidad, full-text search, extensiones como pgvector para embeddings de IA, transacciones ACID reales. Es la base de datos que escala desde tu laptop hasta millones de usuarios sin que tengas que reaprender nada.
El único argumento real a favor de MongoDB hoy es "mi equipo lo conoce mejor" — y ese argumento tiene fecha de vencimiento.
Lo que descarté y por qué
Remix: Me encanta el modelo mental de Remix. Las loaders y actions son elegantes. Pero el ecosistema es más chico, la integración con el mundo React es más friccionosa, y Vercel está invirtiendo en Next.js de una manera que hace que sea difícil competir en velocidad de features. Si Shopify sigue apostando fuerte a Remix, lo reevalúo.
SvelteKit: Svelte es un placer de escribir. En serio. Pero el mercado laboral y la cantidad de librerías disponibles para React no tienen comparación. Si estoy solo en un proyecto, quizás. Con un equipo, no puedo pedirle a todos que aprendan Svelte.
tRPC: Lo usé, me gustó, pero con Next.js App Router y Server Actions, la fricción de montar tRPC se justifica cada vez menos. Las Server Actions con TypeScript te dan type safety end-to-end sin la infraestructura extra:
// app/actions/pedidos.ts
'use server'
import { db } from '@/lib/db'
import { pedidos } from '@/lib/schema'
export async function crearPedido(data: {
usuarioId: number
items: Array<{ productoId: number; cantidad: number }>
}) {
// Validación, lógica de negocio, escritura a DB
const [nuevoPedido] = await db
.insert(pedidos)
.values({ usuarioId: data.usuarioId, estado: 'pendiente', total: 0 })
.returning()
return nuevoPedido
}
Eso se llama desde un Client Component con await crearPedido(data) y TypeScript garantiza que los tipos sean correctos de punta a punta. tRPC resuelto.
Bun como runtime en producción: Bun es increíblemente rápido. Lo uso para correr tests y scripts localmente y es un placer. Pero para producción en 2025, todavía me quedo con Node. El ecosistema, la estabilidad comprobada, y la cantidad de artículos de troubleshooting disponibles cuando algo sale mal a las 3am siguen siendo argumentos sólidos.
El entorno de desarrollo: Docker sí, pero con criterio
Docker para desarrollo local es indispensable. No instales PostgreSQL directamente en tu máquina. No le pidas a tu equipo que instale Redis de forma nativa. Un docker-compose.yml sencillo resuelve todo:
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: miapp
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
postgres_data:
docker compose up -d y tenés tu entorno listo. Cualquier persona del equipo clona el repo, corre ese comando, y está. No hay "en mi máquina funciona".
El elefante en la habitación: ¿y la IA?
Todo stack en 2025 tiene que tener una respuesta para IA generativa. La mía es: empieza simple.
Vercel AI SDK con cualquier modelo de OpenAI o Anthropic es suficiente para el 90% de los casos de uso. pgvector en PostgreSQL para embeddings. No necesitás Pinecone, no necesitás una base de datos vectorial especializada hasta que tengas un problema de escala real que justifique la complejidad.
La conclusión que nadie quiere escuchar
El mejor stack tecnológico en 2025 no es el más nuevo, no es el más performante en benchmarks sintéticos, y no es el que tiene más estrellas en GitHub esta semana. Es el que te permite entregar — con calidad, con mantenibilidad, con un equipo que puede sumarse rápido.
Next.js + TypeScript + PostgreSQL + Drizzle es mi respuesta a esa pregunta hoy. El año que viene puede cambiar. Pero si cambia, va a ser porque algo fundamentalmente mejor apareció — no porque me convencieron con un thread de Twitter con gráficos bonitos.
Arrancá a construir. Los benchmarks son para las charlas de conferencia. El código que llega a producción es el que importa.
Comentarios (0)
Deja un comentario
Artículos Relacionados
Metí un LLM chico adentro de una app Next.js y esto fue lo que aprendí
Reproducí el experimento del LLM tiny que explotó en Show HN: Gemma corriendo en el browser, sin API keys, desde mi stack habitual. Acá está todo lo que salió mal — y lo poco que salió bien.
De DOS a Cloud: mi viaje de 33 años con la tecnología — desde una Amiga en 1994 hasta deployar en Railway con Next.js
Empecé tocando una Amiga 500 a los 3 años sin entender nada. Hoy hago deploy en segundos desde una terminal. En el medio: cyber cafés, servidores Linux a las 3am, y un pivot de carrera que cambió todo. Esta es mi historia.
De 3 segundos a 300ms: cómo optimicé el performance de una app Next.js en producción
Diagnóstico brutal, cambios concretos y métricas reales. Así pasé una app Next.js de ser un desastre lento a cargar en 300ms — sin magia, sin excusas, con trabajo.