Lanzamiento de Next.js 15 - Estabilizacion de Turbopack y Soporte para React 19

2025.12.02

Documentación Oficial

Vision General de Next.js 15

Next.js 15 es un lanzamiento mayor que logra el soporte oficial de React 19 y la estabilizacion de Turbopack. Se han realizado mejoras significativas tanto en la experiencia de desarrollo como en el rendimiento.

flowchart TB
    subgraph Next15["Aspectos Destacados de Next.js 15"]
        subgraph Turbo["Turbopack (Estable para Dev)"]
            T1["Inicio del servidor de desarrollo: 76% mas rapido"]
            T2["Fast Refresh: 96% mas rapido"]
            T3["Primera compilacion: 45% mas rapido"]
        end

        subgraph React19["Soporte React 19"]
            R1["Server Actions (Estable)"]
            R2["Hook use()"]
            R3["React Compiler (Experimental)"]
        end

        subgraph PPR["Partial Prerendering (Beta)"]
            P1["Shell estatico + streaming de contenido dinamico"]
        end

        subgraph Cache["Nuevos Valores por Defecto de Cache"]
            C1["fetch: no-store (por defecto)"]
            C2["Route Handlers: no-store (por defecto)"]
        end
    end

Estabilizacion de Turbopack

En Next.js 15, Turbopack para el servidor de desarrollo se ha convertido en version estable.

# Iniciar servidor de desarrollo con Turbopack
next dev --turbo

# El build aun usa Webpack (Turbopack en desarrollo)
next build

Comparacion de Rendimiento

Comparacion de rendimiento en proyecto grande (5000 modulos):

ElementoWebpackTurbopackMejora
Inicio del servidor de desarrollo8.2s2.0s76% mas rapido
Fast Refresh (1 archivo modificado)800ms30ms96% mas rapido
Primera compilacion de ruta600ms330ms45% mas rapido
Uso de memoria1.6GB800MB50% de reduccion

Soporte para React 19

Server Actions

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  // Validacion
  if (!title || title.length < 3) {
    return { error: 'El titulo debe tener al menos 3 caracteres' };
  }

  // Operacion de base de datos
  await db.posts.create({ title, content });

  // Revalidacion de cache
  revalidatePath('/posts');

  // Redireccion
  redirect('/posts');
}

// Accion con actualizacion optimista
export async function likePost(postId: string) {
  await db.posts.update({
    where: { id: postId },
    data: { likes: { increment: 1 } },
  });

  revalidatePath(`/posts/${postId}`);
}
// app/posts/new/page.tsx
import { createPost } from '../actions';

export default function NewPostPage() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Titulo" required />
      <textarea name="content" placeholder="Contenido" required />
      <button type="submit">Crear Post</button>
    </form>
  );
}

useActionState

'use client';

import { useActionState } from 'react';
import { createPost } from '../actions';

export function CreatePostForm() {
  const [state, formAction, isPending] = useActionState(createPost, null);

  return (
    <form action={formAction}>
      <input name="title" placeholder="Titulo" disabled={isPending} />
      <textarea name="content" placeholder="Contenido" disabled={isPending} />

      {state?.error && (
        <p className="text-red-500">{state.error}</p>
      )}

      <button type="submit" disabled={isPending}>
        {isPending ? 'Creando...' : 'Crear Post'}
      </button>
    </form>
  );
}

useOptimistic

'use client';

import { useOptimistic } from 'react';
import { likePost } from '../actions';

export function LikeButton({ postId, initialLikes }: Props) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    initialLikes,
    (state, _) => state + 1
  );

  async function handleLike() {
    addOptimisticLike(null);
    await likePost(postId);
  }

  return (
    <form action={handleLike}>
      <button type="submit">
        ❤️ {optimisticLikes}
      </button>
    </form>
  );
}

Partial Prerendering (PPR)

Una nueva estrategia de renderizado que combina shell estatico con contenido dinamico.

flowchart TB
    subgraph BuildTime["Tiempo de Build"]
        subgraph StaticShell["Shell Estatico"]
            Header["Header (estatico)"]
            subgraph Layout["Layout"]
                Content["Content (estatico)"]
                Suspense["Suspense Fallback<br/>(Skeleton)"]
            end
        end
    end

    subgraph RequestTime["Tiempo de Solicitud"]
        Step1["1. Devolver shell estatico inmediatamente (TTFB: pocos ms)"]
        Step2["2. Streaming de parte dinamica"]
        Step3["3. Hidratacion secuencial en limites Suspense"]
        Step1 --> Step2 --> Step3
    end

    BuildTime --> RequestTime
// next.config.js
module.exports = {
  experimental: {
    ppr: true,
  },
};
// app/products/[id]/page.tsx
import { Suspense } from 'react';

// Parte generada estaticamente
export default function ProductPage({ params }: Props) {
  return (
    <div>
      {/* Contenido estatico */}
      <ProductDetails id={params.id} />

      {/* Contenido dinamico (streaming) */}
      <Suspense fallback={<RecommendationsSkeleton />}>
        <PersonalizedRecommendations userId={getUserId()} />
      </Suspense>

      <Suspense fallback={<ReviewsSkeleton />}>
        <RecentReviews productId={params.id} />
      </Suspense>
    </div>
  );
}

// Componente que usa datos dinamicos
async function PersonalizedRecommendations({ userId }: { userId: string }) {
  // Esta parte se ejecuta en tiempo de solicitud
  const recommendations = await getRecommendations(userId);
  return <RecommendationList items={recommendations} />;
}

Nueva Semantica de Cache

En Next.js 15, el comportamiento por defecto del cache ha cambiado.

// Next.js 14 y anteriores: cache por defecto
// Next.js 15: sin cache por defecto

// Cambio en el valor por defecto de fetch API
async function getData() {
  // Next.js 14: cache: 'force-cache' era el defecto
  // Next.js 15: cache: 'no-store' es el defecto
  const res = await fetch('https://api.example.com/data');
  return res.json();
}

// Para habilitar cache, especificar explicitamente
async function getCachedData() {
  const res = await fetch('https://api.example.com/data', {
    cache: 'force-cache',
    next: { revalidate: 3600 }, // 1 hora
  });
  return res.json();
}

Cache de Route Handlers

// app/api/data/route.ts

// Next.js 14: GET se cachea estaticamente
// Next.js 15: sin cache por defecto

// Para habilitar cache
export const dynamic = 'force-static';
// o
export const revalidate = 3600;

export async function GET() {
  const data = await fetchData();
  return Response.json(data);
}

Mejoras de Seguridad

Seguridad de Server Actions

// Proteccion de endpoints de Server Actions
// next.config.js
module.exports = {
  experimental: {
    serverActions: {
      // Especificar origenes permitidos
      allowedOrigins: ['my-app.com', 'staging.my-app.com'],
    },
  },
};
// app/actions.ts
'use server';

import { headers } from 'next/headers';

export async function sensitiveAction() {
  // Verificacion de referer
  const referer = headers().get('referer');
  if (!referer?.startsWith('https://my-app.com')) {
    throw new Error('Origen invalido');
  }

  // La proteccion CSRF se realiza automaticamente
  // ...
}

Mejoras en Exportacion Estatica

// next.config.js
module.exports = {
  output: 'export',

  // Optimizacion de imagenes en exportacion estatica
  images: {
    unoptimized: true,
    // O usar un loader externo
    loader: 'custom',
    loaderFile: './image-loader.js',
  },
};
// image-loader.js
export default function customLoader({ src, width, quality }) {
  return `https://cdn.example.com/${src}?w=${width}&q=${quality || 75}`;
}

instrumentation.ts

Permite ejecutar codigo durante el bootstrap de la aplicacion.

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    // Ejecutar solo en runtime Node.js
    await import('./monitoring/sentry');
  }

  if (process.env.NEXT_RUNTIME === 'edge') {
    // Ejecutar solo en runtime Edge
    await import('./monitoring/edge-logger');
  }
}

export function onRequestError(error: Error, request: Request) {
  // Envio de logs de error
  console.error('Error de solicitud:', error);
}

Migracion

# Actualizacion
npm install next@15 react@19 react-dom@19

# Codemod automatico
npx @next/codemod@latest upgrade latest

Principales Cambios Disruptivos

CambioNext.js 14Next.js 15
fetch por defectocache: ‘force-cache’cache: ‘no-store’
Route HandlerCache estaticoSin cache
Version de ReactReact 18React 19
Requisito minimo de Node.js18.17.018.18.0

Enlaces de Referencia

← Volver a la lista