April 19, 2026
Cómo publico rápido en 2026: mi stack moderno de Next.js

Este artículo explica el stack que uso como base en casi todos mis proyectos, aunque no en todos. Es un enfoque opinionado que recomiendo usar en 2026. Además, ofrece una visión general de cada parte del stack y te guiaré a través de su desarrollo.
Este stack está basado en el T3 Stack, que recomiendo encarecidamente, ya que fue mi opción habitual en muchos proyectos. Sin embargo, he desarrollado una configuración personalizada que refleja el estado de la web este año, incluyendo algunas adiciones modernas clave que considero esenciales.
Si ya has leído este artículo y quieres empezar a usar este stack, puedes ir directamente a este repositorio.
Qué cubre esta guía
- Razones para elegir este stack
- Configuración de Next.js
- Paginación y grupos de layout
- Validación de variables de entorno
- Estilos
- Shadcn
- Animaciones y transiciones
- Base de datos y ORM
- Autenticación
- Despliegue
- Conclusión
Razones para elegir este stack
Los principios de este stack son: AI-first, UI moderna, propiedad total y listo para producción. Con esto en mente, vamos a usar Next.js y TypeScript, que proporcionan una base sólida y un desarrollo rápido. Vale la pena destacar que la IA funciona mejor con contratos, lo que subraya la importancia de usar TypeScript.
Además, he elegido tecnologías bien documentadas, ampliamente usadas y amigables con la IA. Los agentes de IA son más precisos cuando el contexto es más accesible. Así, por ejemplo, usando TailwindCSS, el agente de IA no tiene que buscar archivos .css fuera del componente, ya que los estilos están en línea. Otro ejemplo es Prisma, que requiere definir los modelos en un único archivo, por lo que el agente de IA solo tiene que inspeccionar un archivo para obtener el contexto de toda la base de datos.
En cuanto al editor que uso, recientemente he empezado a usar Cursor en lugar de Visual Studio Code, porque está construido sobre él y me ayuda con los agentes de IA. No obstante, también puedes usar Claude Code con VS Code o lo que prefieras.
En 2026, no solo es importante ganar confianza con tus habilidades de código y ampliar tu stack, sino también aprender a delegar ciertas tareas a la IA y ser capaz de revisar y corregir los resultados.
Empecemos configurando nuestro proyecto.
Configuración de Next.js
Primero, crea una app de Next.js con el nombre modern-nextjs-stack. Después, entra en esta nueva carpeta y ábrela con tu editor de código.
pnpm create next-app@latest modern-nextjs-stack --yes
cd modern-nextjs-stackNext.js ya habrá configurado la mayoría de las tecnologías necesarias.
Routing y grupos de layout
Estamos usando el App Router de Next.js, y aquí la estructura de carpetas es importante. Recomiendo que la carpeta ./app sea exclusivamente para routing, layouts, grupos, API routes, loadings y errors. El resto de carpetas deben estar fuera de ./app. Un ejemplo de esta organización es el siguiente:
/app <-- Solo para routing y API routes
├── api
├── route.ts
├── auth
├── layout.ts <-- Layout de grupo
├── page.ts
├── (root)
├── layout.ts <-- Layout de grupo
├── page.ts <-- Página de inicio
├── layout.ts <-- Layout principal
/components <-- Componentes reutilizables
/actions <-- Server Actions
/lib <-- Clientes de BD (Prisma), utilidades, env.ts
/services <-- Lógica de negocioComo habrás notado, hay varios layouts dentro de la carpeta app. Este enfoque te permite tener estilos diferentes por grupo de páginas. El layout principal incluye las fuentes, los estilos globales y los providers. Además, he creado un grupo llamado (root), que aplica nuevos estilos a las páginas dentro de ese grupo, como la página de inicio, la de about o la de contacto.
Más adelante explicaré con más detalle el resto de carpetas.
Validación de variables de entorno
Dentro del proyecto, hay que instalar los siguientes paquetes:
pnpm install @t3-oss/env-nextjs zodEl paquete @t3-oss/env-nextjs está preconfigurado para Next.js y te permite crear un cliente que separa tus variables de entorno entre el lado del servidor y el del cliente. En este caso, uso zod para validar estas variables.
La configuración de este cliente de variables de entorno es la siguiente:
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
/**
* Define aquí el esquema de tus variables de entorno del servidor.
* De este modo puedes asegurarte de que la app no se compila con variables inválidas.
*/
server: {
BETTER_AUTH_SECRET:
process.env.NODE_ENV === "production"
? z.string()
: z.string().optional(),
BETTER_AUTH_URL: z.string(),
DATABASE_URL: z.string().url(),
NODE_ENV: z
.enum(["development", "test", "production"])
.default("development"),
},
/**
* Define aquí el esquema de tus variables de entorno del cliente.
* Para exponerlas al cliente, añade el prefijo `NEXT_PUBLIC_`.
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string(),
},
/**
* No se puede desestructurar `process.env` como un objeto normal en los edge runtimes
* de Next.js (p. ej. middlewares) ni en el cliente, por lo que hay que hacerlo manualmente.
*/
runtimeEnv: {
BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET,
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL,
DATABASE_URL: process.env.DATABASE_URL,
NODE_ENV: process.env.NODE_ENV,
},
/**
* Ejecuta `build` o `dev` con `SKIP_ENV_VALIDATION` para omitir la validación de variables.
* Especialmente útil para builds con Docker.
*/
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
/**
* Hace que las cadenas vacías se traten como undefined.
* `SOME_VAR: z.string()` con `SOME_VAR=''` lanzará un error.
*/
emptyStringAsUndefined: true,
});Estilos
En este proyecto usamos TailwindCSS como framework CSS de utilidades. Como sabes, el orden de las utilidades es relevante; es necesario que primero instales la extensión Prettier - Code formatter de Esben Petersen para VS Code y la configures como tu formateador por defecto. Esta configuración se realiza con el siguiente archivo settings.json:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"prettier.documentSelectors": ["**/*.{js,jsx,ts,tsx,html,css,scss}"]
}Después, hay que añadir Prettier y el plugin para TailwindCSS al proyecto:
pnpm add prettier prettier-plugin-tailwindcssAhora solo necesitamos añadir la configuración de Prettier:
/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */
export default {
plugins: ["prettier-plugin-tailwindcss"],
};Shadcn
Shadcn no es solo open source, sino también open code. Lo veo como un conjunto moderno de reglas y una base para construir tu propia librería de componentes.
Por debajo, Shadcn usa una librería de primitivos a tu elección, como Radix UI, para encargarse del trabajo pesado. Proporciona fundaciones accesibles y sin estilos junto con su funcionalidad principal, lo que permite a Shadcn centrarse en la capa de diseño.
Cada vez que necesitas un componente, Shadcn te entrega uno con un diseño excelente de serie. Estos componentes son abiertos a modificación, así que si necesitas cambiar su comportamiento o estilo, solo tienes que sobreescribir o extender el código del componente.
Por último, los principios en los que se basa Shadcn permiten a la IA ayudarte a editar o crear componentes, porque la IA tiene acceso al código del componente, pero también porque son componibles, predecibles y tienen un estilo coherente.
Para empezar a usar Shadcn, ejecuta el siguiente comando dentro del proyecto:
pnpm dlx shadcn@latest init --template nextTendrás que elegir una librería de componentes y un preset de tu preferencia. En mi caso, seleccioné Radix y Nova, respectivamente.
El último paso del estilo es configurar correctamente las fuentes. Next.js ya las tiene configuradas en el layout raíz. Sin embargo, hay que ajustar el nombre de la variable al que se usa en el archivo global.css, por ejemplo así:
const geistSans = Geist({
variable: "--font-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});Animaciones y transiciones
Aunque no sepas usar animaciones, te animo a instalar 2 paquetes para este proyecto. Así podrás especificar los paquetes que quieres usar cuando le pidas a un agente de IA que anime tus componentes.
El primero es Motion (anteriormente Framer Motion), que ofrece una API de animaciones muy sencilla de usar. Un sitio web sin animaciones puede resultar aburrido, así que te recomiendo usarlas.
El segundo es a tu elección. Next Transition Router tiene una API simple para crear transiciones animadas entre páginas.
Por último, recomendaría Next Themes si quieres ofrecer un modo oscuro en tu sitio web.
Base de datos y ORM
Para este stack usaré PostgreSQL. Este proyecto incluye un archivo ./start-database.sh que ejecuta una base de datos PostgreSQL dentro de un contenedor Docker. Debes configurar la siguiente variable de entorno:
DATABASE_URL="postgresql://postgres:password@localhost:5432/modern-nextjs-stack"Una vez que tengas tu base de datos en marcha, puedes configurar tu ORM favorito. En este caso, usaré Prisma. Para empezar con Prisma, hay que instalar las siguientes dependencias:
pnpm add prisma @types/pg --save-devpnpm add @prisma/client @prisma/adapter-pg dotenv pgDespués podemos inicializar Prisma en el proyecto:
pnpm dlx prisma initEste último comando genera el archivo prisma.config.ts y la carpeta ./prisma. Esta carpeta contiene el archivo schema.prisma, donde tienes que definir los modelos de la base de datos. Ahora podemos generar el cliente de Prisma:
pnpm dlx prisma generateEste comando generará la carpeta ./lib/generated/prisma. Contiene tanto el cliente de Prisma como los modelos, tipos, etc. Por eso tienes que ejecutar este comando cada vez que hagas cambios en tu base de datos, especialmente al ejecutar migraciones.
Ahora es el momento de configurar el cliente de Prisma para el proyecto:
import "dotenv/config";
import { PrismaClient } from "./generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
export const pgAdapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
});
const createPrismaClient = () =>
new PrismaClient({
adapter: pgAdapter,
log:
process.env.NODE_ENV === "development"
? ["query", "error", "warn"]
: ["error"],
});
const globalForPrisma = globalThis as unknown as {
prisma: ReturnType<typeof createPrismaClient> | undefined;
};
export const db = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;A partir de ahora puedes añadir tus modelos de base de datos al archivo schema.prisma. Después, solo necesitas ejecutar una migración y generar el cliente de Prisma así:
pnpm dlx prisma migrate dev --name nombre-de-la-migracion
pnpm dlx prisma generateVale la pena mencionar que Prisma ya está conectado a tu base de datos que corre en Docker.
Autenticación
Para gestionar la autenticación, prefiero usar Better-Auth. Esta librería es intrínsecamente type-safe, incluye un conjunto completo de funcionalidades de serie y ofrece plugins que te ahorran días de desarrollo. Además, se ejecuta en tu propia infraestructura y tienes control total sobre el sistema de autenticación.
No te preocupes por la configuración ni el mantenimiento, porque es muy sencillo. Better-Auth se integra de forma increíble con Prisma, y su CLI puede generar directamente en tu schema de Prisma los modelos básicos que necesitas para la autenticación. Además, si en el futuro necesitas un panel para gestionar tus usuarios, puedes tenerlo en la propia infraestructura de Better-Auth.
Empecemos añadiendo Better-Auth al proyecto:
pnpm add better-authAhora hay que añadir las siguientes variables de entorno a tu archivo .env:
# Better Auth
# Secreto usado por Better Auth. Puedes generar uno nuevo con el siguiente comando:
# openssl rand -base64 32
BETTER_AUTH_SECRET=""
# URL base de tu app
BETTER_AUTH_URL="http://localhost:3000"Configuremos Better-Auth creando el siguiente archivo:
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { db } from "./db";
export const auth = betterAuth({
baseURL: {
allowedHosts: ["localhost:3000", "*.vercel.app"],
protocol: process.env.NODE_ENV === "development" ? "http" : "https",
},
database: prismaAdapter(db, {
provider: "postgresql", // o "sqlite" o "mysql"
}),
emailAndPassword: {
enabled: true,
},
});
export type Session = typeof auth.$Infer.Session;Además, crea un auth client:
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient();
export type Session = typeof authClient.$Infer.Session;Así, en el lado del cliente puedes usar el authClient directamente. En el lado del servidor, recomiendo crear la siguiente función, que incluye los headers:
import { auth } from "./auth";
import { headers } from "next/headers";
export const getSession = async () =>
auth.api.getSession({ headers: await headers() });Better Auth requiere un endpoint de API para gestionar las peticiones de autenticación como sign-in, sign-up y sign-out. Por eso necesitamos crear el siguiente archivo:
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);Después de esta configuración, es necesario añadir los modelos de autenticación (User, Session, Account y Verification) a tu archivo schema.prisma. Hagámoslo usando la CLI de Better-Auth:
pnpm dlx auth generatePor último, solo necesitamos generar la primera migración de la base de datos:
pnpm dlx prisma migrate dev --name init
pnpm dlx prisma generateEn este punto, las tablas relacionadas quedan creadas en tu base de datos.
Este proyecto incluirá ejemplos para las páginas de sign-up y sign-in, así que te sugiero revisar el repositorio del proyecto.
Despliegue
Hay muchas opciones para desplegar tu app web. Una de mis favoritas es Vercel, porque solo tienes que conectar tu cuenta de GitHub, seleccionar el repositorio correcto y añadir las variables de entorno de producción. Después, Vercel gestionará automáticamente el CI/CD, así que no tendrás que preocuparte por ello.
Sin embargo, hay algo más importante que resolver. Cuando generas una nueva migración en local, el cambio no se reflejará directamente en tu base de datos de producción. Para ello debes crear, por ejemplo, una Github Action. Por eso he añadido un workflow de GitHub que se ejecutará cada vez que hagas push de una migración a tu rama main. Este workflow no se ejecutará hasta que configures el secreto DATABASE_URL con la URL de la base de datos de producción en el repositorio de GitHub.
name: Prisma Migrate on Push
on:
push:
paths:
- prisma/migrations/**
branches:
- main
jobs:
migrate:
if: "${{ secrets.DATABASE_URL != '' }}"
runs-on: ubuntu-latest
steps:
- name: Checkout del repositorio
uses: actions/checkout@v4
- name: Configurar Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Instalar pnpm
run: npm install -g pnpm
- name: Instalar dependencias
run: pnpm install --frozen-lockfile
- name: Aplicar migraciones de Prisma
run: pnpm dlx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NODE_ENV: productionConclusión
Este año nos ha demostrado que tenemos nuevos miembros en el equipo: los agentes de IA. Necesitamos evaluar no solo las capacidades del equipo para decidir las tecnologías y la arquitectura, sino también entender cómo funciona mejor un agente de IA para sacarle el máximo partido.
Este proyecto trata de recopilar las mejores prácticas e integrar las tecnologías más relevantes en 2026. Recuerda que es un stack opinionado, así que cada decisión depende de tus requisitos o casos de uso. Por ejemplo, soy un gran fan de TRPC. Sin embargo, no lo he considerado para dar forma a este stack porque resulta útil cuando las peticiones se hacen tanto desde el cliente como desde el servidor. Actualmente, es posible crear una app web completa usando únicamente server actions, así que tú decides si integrarlo.
En un próximo artículo, crearé una app web usando este stack, aprovechando las server actions que pueden mejorar drásticamente el rendimiento de tus apps.
Este stack está disponible como plantilla que puedes usar directamente desde mi repositorio de GitHub. Mira la imagen para encontrar el botón «Use this template».
