Autenticación en Next.js con Auth.js v5: guía Completa
Implementa autenticación en tu app Next.js con Auth.js v5 (NextAuth). OAuth con Google y GitHub, sesiones, middleware para proteger rutas, y roles de usuario paso a paso.
Autenticación en Next.js con Auth.js v5: guía Completa
La autenticación en Next.js con Auth.js v5 es el estandar actual para implementar login en aplicaciones con App Router. Auth.js (antes NextAuth.js) maneja OAuth con proveedores como Google y GitHub, sesiones con JWT o base de datos, middleware para proteger rutas, y callbacks para personalizar el flujo completo. Todo esto sin depender de un servicio de pago.
Esta guía cubre la implementación completa: desde instalar el paquete hasta proteger rutas con roles de usuario. código funcional, sin atajos.
qué es Auth.js v5 y por qué importa
Auth.js v5 es la versión actual de lo que antes conocías como NextAuth.js. El renombramiento refleja que la librería ya no es exclusiva de Next.js -- soporta SvelteKit, SolidStart y otros frameworks. Pero para el ecosistema de Next.js, sigue siendo la solución de autenticación open-source por defecto.
Las diferencias principales con NextAuth v4:
- Disenado para App Router: funciona nativamente con Server Components, Server Actions y middleware
- Configuración centralizada: un solo archivo
auth.tsen la raiz del proyecto - Edge compatible: el middleware corre en el edge runtime sin problemas
- API simplificada: menos boilerplate, más convención
Si ya usaste NextAuth v4, el salto a v5 cambia la estructura del proyecto pero los conceptos son los mismos.
Instalación y configuración inicial
Empecemos instalando Auth.js en un proyecto Next.js existente con App Router.
$ versión correcta
El paquete sigue llamándose next-auth en npm, pero a partir de la versión 5 es oficialmente Auth.js. Asegúrate de instalar next-auth@5 y no la versión 4.
Crear el archivo de configuración
El corazon de Auth.js v5 es un archivo auth.ts en la raiz del proyecto. aquí defines proveedores, callbacks y opciones de sesión:
// auth.ts
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import GitHub from 'next-auth/providers/github'
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Google,
GitHub,
],
})Eso es todo para una configuración básica. Auth.js lee automáticamente las variables de entorno AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET, AUTH_GITHUB_ID y AUTH_GITHUB_SECRET.
Variables de entorno
Agrega estas variables a tu archivo .env.local:
# .env.local
# Secret para firmar tokens (genera uno con: npx auth secret)
AUTH_SECRET=tu_secret_generado_aqui
# Google OAuth
AUTH_GOOGLE_ID=tu_google_client_id
AUTH_GOOGLE_SECRET=tu_google_client_secret
# GitHub OAuth
AUTH_GITHUB_ID=tu_github_client_id
AUTH_GITHUB_SECRET=tu_github_client_secret$ Ese comando genera un AUTH_SECRET aleatorio y lo agrega a tu .env.local automáticamente. Si necesitas una guía más detallada sobre como manejar variables de entorno en tu proyecto, revisa la guía de variables de entorno en Next.js y Vercel.
Configurar proveedores OAuth
Obtener credenciales de Google
Para configurar Google como proveedor de login, necesitas crear un proyecto en Google Cloud Console:
- Ve a Google Cloud Console
- Crea un proyecto nuevo o selecciona uno existente
- Ve a APIs & Services > Credentials
- Click en Create Credentials > OAuth client ID
- Selecciona Web application como tipo
- En Authorized redirect URIs agrega:
http://localhost:3000/api/auth/callback/google - Copia el Client ID y Client Secret a tu
.env.local
URI de redirección en producción
Cuando hagas deploy, necesitas agregar también la URI de producción: https://tudominio.com/api/auth/callback/google. Si no la agregas, el login con Google va a fallar en producción con un error de redirect_uri mismatch.
Obtener credenciales de GitHub
La configuración de GitHub es más directa:
- Ve a GitHub Developer Settings
- Click en New OAuth App
- Homepage URL:
http://localhost:3000 - Authorization callback URL:
http://localhost:3000/api/auth/callback/github - Click en Register application
- Copia el Client ID y genera un Client Secret
Para producción, crea una OAuth App separada con la URL de tu dominio real.
Route Handler
Auth.js necesita un Route Handler que maneje todas las rutas de autenticación (/api/auth/*). Crealo con esta estructura:
app/
└── api/
└── auth/
└── [...nextauth]/
└── route.ts
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlersDos líneas. El objeto handlers que exportaste desde auth.ts contiene toda la lógica para sign in, sign out, callbacks y sesiones. El catch-all route ([...nextauth]) captura todas las rutas bajo /api/auth/.
Componentes de Sign In y Sign Out
Boton de Sign In (Server Component)
Auth.js v5 te permite usar Server Actions directamente para el login:
// components/sign-in.tsx
import { signIn } from '@/auth'
export function SignIn() {
return (
<div>
<form
action={async () => {
'use server'
await signIn('google')
}}
>
<button type="submit">Iniciar sesión con Google</button>
</form>
<form
action={async () => {
'use server'
await signIn('github')
}}
>
<button type="submit">Iniciar sesión con GitHub</button>
</form>
</div>
)
}Boton de Sign Out (Server Component)
// components/sign-out.tsx
import { signOut } from '@/auth'
export function SignOut() {
return (
<form
action={async () => {
'use server'
await signOut()
}}
>
<button type="submit">Cerrar sesión</button>
</form>
)
}Boton de Sign In (Client Component)
Si necesitas un boton con interactividad del lado del cliente (por ejemplo, mostrar un loading state):
// components/sign-in-button.tsx
'use client'
import { signIn } from 'next-auth/react'
import { useState } from 'react'
export function SignInButton() {
const [cargando, setCargando] = useState(false)
async function handleSignIn(proveedor: string) {
setCargando(true)
await signIn(proveedor, { callbackUrl: '/dashboard' })
}
return (
<div>
<button
onClick={() => handleSignIn('google')}
disabled={cargando}
>
{cargando ? 'Redirigiendo...' : 'Google'}
</button>
<button
onClick={() => handleSignIn('github')}
disabled={cargando}
>
{cargando ? 'Redirigiendo...' : 'GitHub'}
</button>
</div>
)
}Server vs Client
Nota la diferencia de imports: en Server Components importas signIn desde @/auth, en Client Components desde next-auth/react. Cada uno tiene su propio mecanismo. Si esta distinción te genera dudas, la guía de Server Components vs Client Components lo explica a fondo.
Obtener la sesión del usuario
Este es el punto más importante de toda la configuración. Necesitas acceder a la sesión en tres contextos diferentes: Server Components, Client Components y middleware.
En Server Components: auth()
// app/dashboard/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { SignOut } from '@/components/sign-out'
export default async function DashboardPage() {
const sesión = await auth()
// Si no hay sesión, redirigir al login
if (!sesión?.user) {
redirect('/api/auth/signin')
}
return (
<div>
<h1>Dashboard</h1>
<p>Bienvenido, {sesión.user.name}</p>
<p>Email: {sesión.user.email}</p>
{sesión.user.image && (
<img
src={sesión.user.image}
alt={sesión.user.name ?? 'Avatar'}
width={64}
height={64}
/>
)}
<SignOut />
</div>
)
}La función auth() retorna la sesión completa o null si el usuario no esta autenticado. Al ser una función async, funciona perfectamente en Server Components.
En Client Components: useSession()
Para Client Components, necesitas envolver tu aplicación con el SessionProvider y usar el hook useSession:
// app/layout.tsx
import { SessionProvider } from 'next-auth/react'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="es">
<body>
<SessionProvider>
{children}
</SessionProvider>
</body>
</html>
)
}// components/user-info.tsx
'use client'
import { useSession } from 'next-auth/react'
export function UserInfo() {
const { data: sesión, status } = useSession()
if (status === 'loading') {
return <p>Cargando...</p>
}
if (status === 'unauthenticated') {
return <p>No has iniciado sesión</p>
}
return (
<div>
<p>Hola, {sesión?.user?.name}</p>
<p>{sesión?.user?.email}</p>
</div>
)
}El hook useSession retorna tres estados posibles: loading, authenticated y unauthenticated. Siempre maneja los tres.
En middleware: auth como wrapper
Auth.js v5 exporta auth como un wrapper de middleware que te da acceso a la sesión:
// middleware.ts
import { auth } from '@/auth'
export default auth((req) => {
const estaAutenticado = !!req.auth
// lógica de protección de rutas (ver siguiente sección)
})
export const config = {
matcher: ['/dashboard/:path*', '/perfil/:path*', '/admin/:path*'],
}Proteger rutas con middleware
El middleware es la primera línea de defensa para rutas protegidas. Se ejecuta antes de que el Server Component cargue, así que el usuario nunca ve contenido que no debería ver.
// middleware.ts
import { auth } from '@/auth'
import { NextResponse } from 'next/server'
// Rutas que requieren autenticación
const RUTAS_PROTEGIDAS = ['/dashboard', '/perfil', '/configuración']
// Rutas solo para usuarios NO autenticados
const RUTAS_AUTH = ['/login', '/registro']
export default auth((req) => {
const { nextUrl } = req
const estaAutenticado = !!req.auth
const esRutaProtegida = RUTAS_PROTEGIDAS.some(ruta =>
nextUrl.pathname.startsWith(ruta)
)
const esRutaAuth = RUTAS_AUTH.some(ruta =>
nextUrl.pathname.startsWith(ruta)
)
// Sin sesión intentando acceder a ruta protegida
if (esRutaProtegida && !estaAutenticado) {
const loginUrl = new URL('/api/auth/signin', nextUrl.origin)
loginUrl.searchParams.set('callbackUrl', nextUrl.pathname)
return NextResponse.redirect(loginUrl)
}
// Con sesión intentando acceder a login/registro
if (esRutaAuth && estaAutenticado) {
return NextResponse.redirect(new URL('/dashboard', nextUrl.origin))
}
return NextResponse.next()
})
export const config = {
matcher: [
'/dashboard/:path*',
'/perfil/:path*',
'/configuración/:path*',
'/login',
'/registro',
],
}Matcher específico
Usa matcher para ejecutar el middleware solo en las rutas necesarias. No uses matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'] a menos que realmente necesites proteger todas las rutas. Un matcher específico es más eficiente y más fácil de debuggear.
Para una guía más completa sobre seguridad en rutas y otros patrones de protección, revisa la guía de seguridad en aplicaciones Next.js.
Sesiones con base de datos: Prisma Adapter
Por defecto, Auth.js usa JWT para las sesiones. El token se guarda en una cookie y no necesitas base de datos. Pero si necesitas más control -- revocar sesiones, ver sesiones activas, o guardar datos adicionales del usuario -- necesitas un adapter de base de datos.
Instalar dependencias
$ Schema de Prisma
Auth.js requiere tablas especificas para manejar usuarios, cuentas, sesiones y tokens de verificación:
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
role String @default("user")
accounts Account[]
sessions Session[]
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}Sincronizar la base de datos
$ Configurar el adapter
Actualiza tu archivo auth.ts para usar el adapter de Prisma:
// auth.ts
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import GitHub from 'next-auth/providers/github'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
Google,
GitHub,
],
session: {
strategy: 'database', // Cambiar de JWT a sesiones en DB
},
})JWT vs Database
Al agregar un adapter, Auth.js cambia automáticamente a sesiones en base de datos. Si quieres seguir usando JWT (por ejemplo, para compatibilidad con edge runtime en middleware), específica session: { strategy: 'jwt' } explicitamente.
Callbacks: personalizar el flujo de autenticación
Los callbacks son funciones que Auth.js ejecuta en momentos clave del flujo de autenticación. Los dos más importantes son jwt y session.
JWT Callback
Se ejecuta cada vez que se crea o actualiza un JWT. aquí puedes agregar datos personalizados al token:
// auth.ts
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import GitHub from 'next-auth/providers/github'
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [Google, GitHub],
callbacks: {
// Se ejecuta al crear/actualizar el JWT
jwt({ token, user }) {
// 'user' solo esta disponible en el primer sign in
if (user) {
token.role = user.role ?? 'user'
token.id = user.id
}
return token
},
// Se ejecuta al leer la sesión (auth(), useSession, etc.)
session({ session, token }) {
if (session.user) {
session.user.role = token.role as string
session.user.id = token.id as string
}
return session
},
},
})Extender los tipos de TypeScript
Para que TypeScript reconozca los campos personalizados que agregaste a la sesión y al token, necesitas extender los tipos:
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth'
declare module 'next-auth' {
interface Session {
user: {
id: string
role: string
} & DefaultSession['user']
}
interface User {
role?: string
}
}
declare module 'next-auth/jwt' {
interface JWT {
role?: string
id?: string
}
}Authorized Callback
El callback authorized se ejecuta en el middleware y determina si una petición debe continuar o redirigirse:
// auth.ts (agregando authorized callback)
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [Google, GitHub],
callbacks: {
authorized({ auth, request }) {
const estaAutenticado = !!auth?.user
const esRutaProtegida = request.nextUrl.pathname.startsWith('/dashboard')
if (esRutaProtegida && !estaAutenticado) {
return false // Redirige a la página de sign in
}
return true // Permite el acceso
},
jwt({ token, user }) {
if (user) {
token.role = user.role ?? 'user'
token.id = user.id
}
return token
},
session({ session, token }) {
if (session.user) {
session.user.role = token.role as string
session.user.id = token.id as string
}
return session
},
},
})Control de acceso por roles
Con los callbacks configurados, implementar roles es cuestión de leer el campo role de la sesión en tus componentes.
Middleware con roles
// middleware.ts
import { auth } from '@/auth'
import { NextResponse } from 'next/server'
const PERMISOS_RUTA: Record<string, string[]> = {
'/dashboard': ['user', 'editor', 'admin'],
'/editor': ['editor', 'admin'],
'/admin': ['admin'],
}
export default auth((req) => {
const { nextUrl } = req
const usuario = req.auth?.user
if (!usuario) {
return NextResponse.redirect(
new URL('/api/auth/signin', nextUrl.origin)
)
}
// Buscar si la ruta tiene restricción de roles
const permisoRequerido = Object.entries(PERMISOS_RUTA).find(
([ruta]) => nextUrl.pathname.startsWith(ruta)
)
if (permisoRequerido) {
const [, rolesPermitidos] = permisoRequerido
const rolUsuario = usuario.role ?? 'user'
if (!rolesPermitidos.includes(rolUsuario)) {
return NextResponse.redirect(
new URL('/sin-acceso', nextUrl.origin)
)
}
}
return NextResponse.next()
})
export const config = {
matcher: ['/dashboard/:path*', '/editor/:path*', '/admin/:path*'],
}Verificación de roles en Server Components
El middleware protege la ruta, pero también debes verificar en el Server Component para operaciones sensibles:
// app/admin/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
export default async function AdminPage() {
const sesión = await auth()
if (!sesión?.user || sesión.user.role !== 'admin') {
redirect('/sin-acceso')
}
return (
<div>
<h1>Panel de administración</h1>
<p>Solo usuarios con rol admin pueden ver esto.</p>
</div>
)
}Componente condicional por rol
// components/role-gate.tsx
import { auth } from '@/auth'
interface RoleGateProps {
rolesPermitidos: string[]
children: React.ReactNode
fallback?: React.ReactNode
}
export async function RoleGate({
rolesPermitidos,
children,
fallback = null,
}: RoleGateProps) {
const sesión = await auth()
const rolUsuario = sesión?.user?.role ?? 'user'
if (!rolesPermitidos.includes(rolUsuario)) {
return <>{fallback}</>
}
return <>{children}</>
}// Uso en cualquier page
import { RoleGate } from '@/components/role-gate'
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
{/* Visible para todos los autenticados */}
<p>Contenido general del dashboard</p>
{/* Solo visible para admins */}
<RoleGate rolesPermitidos={['admin']}>
<section>
<h2>Estadisticas del sistema</h2>
{/* contenido de admin */}
</section>
</RoleGate>
</div>
)
}Estructura final del proyecto
después de implementar todo, tu proyecto debería tener esta estructura:
mi-app/
├── auth.ts (configuración central de Auth.js)
├── middleware.ts (protección de rutas)
├── types/
│ └── next-auth.d.ts (tipos extendidos de sesión)
├── app/
│ ├── api/
│ │ └── auth/
│ │ └── [...nextauth]/
│ │ └── route.ts (route handler)
│ ├── dashboard/
│ │ └── page.tsx (ruta protegida)
│ ├── admin/
│ │ └── page.tsx (ruta protegida por rol)
│ └── layout.tsx (SessionProvider)
├── components/
│ ├── sign-in.tsx (boton de login)
│ ├── sign-out.tsx (boton de logout)
│ ├── sign-in-button.tsx (versión client component)
│ ├── user-info.tsx (info del usuario en client)
│ └── role-gate.tsx (acceso condicional por rol)
├── prisma/
│ └── schema.prisma (schema con tablas de Auth.js)
└── .env.local (secrets de OAuth y DB)
Seguridad: proteger tus secrets de OAuth
Los client secrets de Google y GitHub son credenciales sensibles. Si se filtran en tu repositorio, un atacante podría usarlos para suplantar tu aplicación y obtener acceso a las cuentas de tus usuarios.
Verifica que tu .gitignore incluya:
# .gitignore
.env
.env.local
.env.*.localSi tu proyecto vive en GitHub, datahogo puede escanear tu repositorio para detectar secrets de OAuth o API keys que se hayan commiteado accidentalmente. Si encuentra algo, genera un PR con el fix.
además de proteger las credenciales, Asegúrate de configurar correctamente las URIs de callback en tus proveedores OAuth. No uses wildcards ni patrones permisivos -- específica la URL exacta de tu dominio.
Auth.js vs Clerk vs Supabase Auth
Auth.js no es la única opción. Dependiendo de tu proyecto, otra solución puede ser mejor:
| Caracteristica | Auth.js v5 | Clerk | Supabase Auth |
|---|---|---|---|
| Precio | Gratis (open-source) | Gratis hasta 10k MAU, luego de pago | Gratis con Supabase |
| Setup | Manual (tu configuras todo) | mínimo (SDK con UI incluida) | Medio (parte del ecosistema Supabase) |
| UI de login | Tu la construyes | Pre-construida y personalizable | Componente opcional |
| OAuth providers | 80+ proveedores | 20+ proveedores | 15+ proveedores |
| Base de datos | Tu la eliges (Prisma, Drizzle, etc.) | Managed por Clerk | PostgreSQL de Supabase |
| Self-hosted | Si | No | Si |
| App Router | Nativo | Nativo | Con @supabase/ssr |
| Roles/permisos | Manual con callbacks | Incluido (Organizations) | Con RLS de PostgreSQL |
Cuando elegir cada uno
Auth.js v5 es la mejor opción cuando quieres control total, no quieres depender de un servicio externo, y no te importa escribir más código. Es gratis sin límites de usuarios.
Clerk es ideal cuándo necesitas ir rápido. Su SDK incluye componentes de UI para sign in, sign up, perfil de usuario y organizaciones. El programa de creadores de Clerk ofrece beneficios para proyectos open-source y creadores de contenido. Si tu prioridad es velocidad de desarrollo sobre control, vale la pena evaluarlo.
Supabase Auth tiene sentido si ya usas Supabase como tu base de datos. Viene integrado y los permisos se manejan con Row Level Security directamente en PostgreSQL. Si quieres explorar esta opción, revisa la guía de Supabase con Next.js.
Errores comunes y como solucionarlos
Error: "OAUTH_CALLBACK_ERROR"
[auth][error] OAuthCallbackError: ...Causa: La URI de callback registrada en el proveedor (Google/GitHub) no coincide con la URL de tu aplicación.
Solución: Verifica que la URI en la consola del proveedor sea exactamente https://tudominio.com/api/auth/callback/google (o /github). No olvides el protocolo https en producción.
Error: "SESSION_NOT_FOUND" con adapter de base de datos
Causa: Las tablas de Auth.js no existen en tu base de datos.
Solución:
$ Verifica que tu schema de Prisma tenga los modelos User, Account, Session y VerificationToken exactamente como los define la documentación de Auth.js.
El middleware no detecta la sesión
Causa común: Estas usando session: { strategy: 'database' } pero el middleware corre en edge runtime, que no tiene acceso a la base de datos.
Solución: Usa JWT para las sesiones si necesitas que el middleware funcione en el edge:
// auth.ts
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [Google, GitHub],
session: {
strategy: 'jwt', // JWT funciona en edge runtime
},
})Con esta configuración, el adapter se usa para guardar usuarios y cuentas, pero las sesiones se manejan con JWT.
useSession retorna undefined
Causa: Falta el SessionProvider en el layout.
Solución: Asegúrate de envolver tu aplicación con <SessionProvider> en el layout raiz, cómo se muestra en la sección de Client Components.
Resumen
Implementar autenticación en Next.js con Auth.js v5 se reduce a estos pasos:
- Instalar
next-auth@5y configurarauth.tscon tus proveedores - Obtener credenciales de OAuth en Google Cloud Console y GitHub Developer Settings
- Crear el Route Handler en
app/api/auth/[...nextauth]/route.ts - Componentes de login/logout con Server Actions o Client Components
- Leer la sesión con
auth()en el servidor,useSession()en el cliente - Proteger rutas con middleware y el callback
authorized - Agregar Prisma adapter si necesitas sesiones en base de datos
- Implementar roles con callbacks y verificación en componentes
La documentación oficial de Auth.js cubre escenarios más avanzados como email/password, magic links y proveedores personalizados. Lo que tienes aquí es una base solida para la mayoria de proyectos con Next.js y App Router.
Recursos adicionales
Preguntas frecuentes
¿Cuál es la diferencia entre Auth.js y NextAuth?
Auth.js es la evolución de NextAuth.js. A partir de la versión 5, el proyecto se renombró a Auth.js para soportar múltiples frameworks (no solo Next.js). Si usabas NextAuth v4, Auth.js v5 es su sucesor directo.
¿Auth.js es gratis?
Si, Auth.js es completamente open-source y gratuito. No hay plan de pago ni límites de usuarios. Los unicos costos son los de tu base de datos si usas sesiones en DB.
¿Debo usar JWT o sesiones en base de datos?
JWT es más simple y no requiere base de datos, ideal para apps sin backend propio. Sesiones en DB dan más control (puedes revocar sesiones, ver sesiones activas) y son mejor para apps con datos sensibles.
¿Puedo usar Auth.js con el App Router de Next.js?
Si, Auth.js v5 fue disenado especificamente para el App Router. Funciona con Server Components, Server Actions, middleware y Route Handlers nativamente.
¿Auth.js vs Clerk vs Supabase Auth, cuál es mejor?
Auth.js es gratis y open-source pero requiere más configuración. Clerk es un servicio managed con UI pre-construida, ideal si quieres velocidad de desarrollo. Supabase Auth viene incluido si ya usas Supabase. La elección depende de tu stack y presupuesto.
Articulos relacionados
Zod Avanzado: Discriminated Unions, Transforms y Pipes
Patrones avanzados de Zod: discriminated unions, transforms, pipes, preprocess, y como validar datos complejos en TypeScript con schemas reutilizables.
tRPC + Next.js: APIs Type-Safe sin REST
Implementa tRPC en Next.js para APIs 100% type-safe. Sin schemas de API, sin fetch manual, sin types duplicados. End-to-end type safety con TypeScript.
Webhooks en Next.js: Recibe y Procesa Eventos
Implementa webhooks en Next.js para recibir eventos de Stripe, GitHub, Clerk y otros servicios. Verificación de firmas, tipado y manejo de errores.