Saltar al contenido principal

Next.js 15: Todas las Novedades que Cambian tu Forma de Programar

Guía completa de Next.js 15: React 19, caché por defecto desactivado, turbopack estable, after(), mejoras en Server Actions y todo lo que necesitas saber para migrar.

Fran Cobos 8 min de lectura 1505 palabras

Tabla de contenidos

Next.js 15 salió con cambios que rompen muchas apps y otros que hacen la vida mucho mejor. Voy a explicar cada uno con código real para que sepas exactamente qué te afecta.

Los cambios más importantes de un vistazo

  1. Caché desactivado por defecto — el cambio que más rompe cosas
  2. React 19 integradouse(), Server Actions mejorados, optimistic updates
  3. Turbopack estable en desarrollo — 76% más rápido en dev
  4. after() — ejecutar código después de enviar la respuesta
  5. forbidden() y unauthorized() — manejo nativo de 401/403
  6. instrumentation.js estable — observabilidad en el servidor
  7. Mejoras en Forms<Form> component nativo

1. El caché ya no está activo por defecto

Este es el cambio que más confunde a la gente que migra desde v14.

¿Cómo era en v14?

// Next.js 14 — esto se CACHEABA por defecto (force-cache)
const res = await fetch('https://api.example.com/productos')
const data = await res.json()
// ⚠️ Solo hacía la petición UNA VEZ y devolvía el resultado cacheado el resto

¿Cómo es en v15?

// Next.js 15 — NO se cachea por defecto (no-store)
const res = await fetch('https://api.example.com/productos')
const data = await res.json()
// ✅ Hace la petición CADA VEZ que se renderiza el componente

Ahora el comportamiento por defecto es el que esperas de cualquier fetch normal. Si quieres caché, tienes que pedirla explícitamente:

// Caché estático: igual que v14 por defecto
const res = await fetch('https://api.example.com/productos', {
  cache: 'force-cache'
})

// Revalidar cada hora
const res = await fetch('https://api.example.com/productos', {
  next: { revalidate: 3600 }
})

// Sin caché (ahora el default)
const res = await fetch('https://api.example.com/productos', {
  cache: 'no-store'
})

Route Handlers también cambian

// v14 — GET Route Handler se CACHEABA
// v15 — GET Route Handler NO se cachea por defecto

// app/api/productos/route.ts
export async function GET() {
  const productos = await db.query('SELECT * FROM productos')
  return Response.json(productos)
  // En v15: siempre fresh. En v14: podía quedar stale.
}

// Para recuperar el comportamiento de v14 en v15:
export const dynamic = 'force-static'
export async function GET() { ... }

Client Router Cache

// v14 — los layouts se cacheaban 5 minutos en el cliente
// v15 — el Client Router Cache no cachea por defecto

// Recuperar comportamiento v14 en next.config.ts:
const config = {
  experimental: {
    staleTimes: {
      dynamic: 30,  // segundos
      static: 180,
    }
  }
}

2. React 19 — Las novedades que más usarás

El hook use() — leer Promises y Context

// Antes (React 18) — suspense con async component
async function Productos() {
  const productos = await fetchProductos() // Solo funciona en Server Components
  return <Lista items={productos} />
}

// React 19 — use() funciona en Client Components también
import { use } from 'react'

function Productos({ productosPromise }: { productosPromise: Promise<Producto[]> }) {
  const productos = use(productosPromise) // Suspende automáticamente
  return <Lista items={productos} />
}

// Uso:
const promesa = fetchProductos()
<Suspense fallback={<Skeleton />}>
  <Productos productosPromise={promesa} />
</Suspense>

También funciona con Context (reemplaza useContext):

const tema = use(TemaContext) // equivalente a useContext(TemaContext) pero más flexible

Server Actions mejorados con useActionState

// React 19 + Next.js 15
import { useActionState } from 'react'

async function crearProducto(estado: Estado, formData: FormData) {
  'use server'
  
  const nombre = formData.get('nombre') as string
  
  try {
    await db.insert({ nombre })
    return { exito: true, mensaje: 'Producto creado' }
  } catch (e) {
    return { exito: false, error: 'Error al crear' }
  }
}

function FormularioProducto() {
  const [estado, accion, isPending] = useActionState(crearProducto, null)
  
  return (
    <form action={accion}>
      <input name="nombre" type="text" required />
      <button disabled={isPending}>
        {isPending ? 'Guardando...' : 'Crear'}
      </button>
      {estado?.error && <p className="text-red-500">{estado.error}</p>}
      {estado?.exito && <p className="text-green-500">{estado.mensaje}</p>}
    </form>
  )
}

useOptimistic — updates optimistas limpios

import { useOptimistic } from 'react'

function ListaTareas({ tareas }: { tareas: Tarea[] }) {
  const [tareasOptimistas, addTareaOptimista] = useOptimistic(
    tareas,
    (estado, nuevaTarea: Tarea) => [...estado, nuevaTarea]
  )

  async function añadirTarea(formData: FormData) {
    const texto = formData.get('texto') as string
    const tareaTemp = { id: Date.now(), texto, completada: false }
    
    addTareaOptimista(tareaTemp) // UI actualiza inmediatamente
    await crearTarea(texto)     // Luego hace la llamada real
  }

  return (
    <div>
      {tareasOptimistas.map(t => <TareaItem key={t.id} tarea={t} />)}
      <form action={añadirTarea}>
        <input name="texto" />
        <button>Añadir</button>
      </form>
    </div>
  )
}

3. Turbopack estable en desarrollo

# Next.js 15 — activar Turbopack en desarrollo
next dev --turbopack

O en package.json:

{
  "scripts": {
    "dev": "next dev --turbopack"
  }
}

Los números que publica Vercel en proyectos grandes:

  • Arranque inicial del servidor: 76% más rápido
  • Hot Module Replacement (HMR): hasta 96% más rápido
  • Compilación de rutas: significativamente más rápido

Para proyectos medianos/grandes, el cambio en experiencia de desarrollo es muy notable. Para proyectos pequeños la diferencia es menos perceptible.

Compatibilidad: La mayoría de loaders de webpack tienen equivalente en Turbopack, pero algunos plugins personalizados aún no son compatibles. Revisa tu next.config.ts antes de activarlo.


4. after() — Código después de la respuesta

Una función muy útil que no existía antes de forma oficial:

import { after } from 'next/server'

export async function GET() {
  const datos = await fetchDatos()
  
  // Envía la respuesta al usuario
  const respuesta = Response.json(datos)
  
  // Este código se ejecuta DESPUÉS de enviar la respuesta
  // El usuario ya tiene su respuesta, no espera esto
  after(async () => {
    await analytics.track('api_called')
    await cache.actualizar('datos')
    await logger.guardar({ timestamp: new Date(), ruta: '/api/datos' })
  })
  
  return respuesta
}

Perfecto para:

  • Analytics y logging sin penalizar la latencia
  • Invalidar cachés
  • Enviar notificaciones
  • Tareas de mantenimiento

Funciona en Route Handlers, Server Components y Server Actions.


5. forbidden() y unauthorized() — Errores 401/403 nativos

import { forbidden, unauthorized } from 'next/navigation'

// En un Server Component o Route Handler
export default async function PaginaAdmin() {
  const sesion = await getSession()
  
  if (!sesion) {
    unauthorized() // Lanza un 401, renderiza unauthorized.tsx
  }
  
  if (sesion.rol !== 'admin') {
    forbidden() // Lanza un 403, renderiza forbidden.tsx
  }
  
  return <AdminPanel />
}

Crea los archivos de error en app/:

app/
  unauthorized.tsx   ← se renderiza con 401
  forbidden.tsx      ← se renderiza con 403
  not-found.tsx      ← 404 (existía antes)
// app/unauthorized.tsx
export default function NoAutorizado() {
  return (
    <div>
      <h1>Inicia sesión para continuar</h1>
      <a href="/login">Iniciar sesión</a>
    </div>
  )
}

6. El componente <Form> de Next.js

Next.js 15 añade un componente <Form> que extiende el <form> de HTML:

import Form from 'next/form'

function BuscadorProductos() {
  return (
    <Form action="/buscar">
      <input name="q" placeholder="Buscar..." />
      <button type="submit">Buscar</button>
    </Form>
  )
}

Lo que hace diferente:

  • Prefetch automático de la página /buscar cuando el formulario es visible
  • Navegación del lado cliente (sin recarga) usando el App Router
  • Loading UI automático (usa loading.tsx)
  • URL actualizada con los params del form para poder compartir el resultado

Cómo migrar de Next.js 14 a 15

Usa el codemod automático

npx @next/codemod@canary upgrade latest

Esto actualiza las dependencias y aplica los cambios de API automáticamente.

Cambios manuales que el codemod no hace

1. Revisar los fetch con caché implícita:

# Busca en tu proyecto todos los fetch sin cache explícito
grep -rn "await fetch(" src/ --include="*.tsx" --include="*.ts"

Para cada fetch en un Server Component, decide si quieres caché o no y añádelo explícitamente.

2. cookies(), headers(), params ahora son async:

// v14
import { cookies } from 'next/headers'
const cookieStore = cookies()
const token = cookieStore.get('token')

// v15
import { cookies } from 'next/headers'
const cookieStore = await cookies()  // ← await obligatorio
const token = cookieStore.get('token')

El codemod lo migra automáticamente, pero si tienes abstracciones propias encima, revísalas.

3. params en Page components ahora es una Promise:

// v14
export default function Pagina({ params }: { params: { slug: string } }) {
  const { slug } = params
}

// v15
export default async function Pagina({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params  // ← await
}

Resumen de lo que afecta a tu código del día a día

FeatureImpacto en migraciónLo que tienes que hacer
Caché desactivadoAltoRevisar todos los fetch y añadir cache explícito
cookies() asyncAltoEl codemod lo migra, revisar abstracciones
params asyncMedioEl codemod lo migra
React 19BajoSin cambios breaking para la mayoría
TurbopackBajoOpt-in, añadir --turbopack en dev
after()NingunoNueva API, úsala cuando quieras
forbidden()/unauthorized()NingunoReemplaza tu manejo manual de errores

Para proyectos nuevos, Next.js 15 con React 19 es la combinación a usar en 2026. Para migrar proyectos existentes: ejecuta el codemod, revisa los fetch y reserva 2-4 horas para los casos edge.

Fran Cobos

Fran Cobos

Desarrollador Full Stack especializado en IA aplicada, automatización y desarrollo web. Escribo sobre herramientas, tutoriales y casos reales para programadores.

¿Necesitas desarrollo a medida?

Apps web, IA, módulos ERP — cuéntame tu proyecto.