Next.js 15 en producción: Turbopack estable, caché granular y React 19 sin fricciones

Next.js 15 se publicó en octubre de 2024 y a principios de 2026 ya es la versión que usan la mayoría de proyectos nuevos en producción. No porque sea la última, sino porque resolvió tres problemas que llevaban años siendo puntos de fricción: la lentitud del compilador en proyectos grandes, un sistema de caché difícil de razonar, y la integración con React 19 que llegó con incompatibilidades molestas. A día de hoy, esas tres cosas están resueltas.
Este artículo es una guía de cómo usar Next.js 15 bien en producción: cuándo activar Turbopack y cuándo no, cómo funciona el nuevo sistema de caché y cómo aprovechar React 19 con Server Components y Server Actions sin caer en los errores más comunes.
Turbopack estable: qué esperar y qué no
Turbopack reemplaza a Webpack como compilador por defecto en desarrollo. En proyectos medianos y grandes, la diferencia en arranque y hot reloads es real: arranques de 3-4 segundos frente a los 30-40 de Webpack en proyectos con muchas dependencias.
# Turbopack es el default en Next.js 15
next dev
# Si necesitas Webpack explícitamente
next dev --no-turbopackSin embargo, hay casos donde Turbopack todavía no es la mejor opción: loaders de Webpack personalizados sin equivalente en Turbopack y plugins PostCSS muy específicos. Comprueba la lista de loaders soportados en la documentación oficial antes de migrar.
Para builds de producción, Turbopack sigue en beta:
next build --experimental-turbopackÚsalo solo en proyectos donde hayas validado que los outputs son idénticos y los tests E2E pasan correctamente.
El nuevo sistema de caché: nada se cachea por defecto
El sistema de caché de Next.js 14 era potente pero difícil de razonar: fetch cacheaba por defecto y las reglas de invalidación eran complejas. Next.js 15 cambia la filosofía: nada se cachea por defecto, tú decides explícitamente qué cachear.
// Next.js 14 — fetch cacheaba por defecto
const data = await fetch('https://api.ejemplo.com/posts');
// Había que desactivar explícitamente:
const data = await fetch('https://api.ejemplo.com/posts', { cache: 'no-store' });
// Next.js 15 — no cachea por defecto
const data = await fetch('https://api.ejemplo.com/posts');
// Para cachear, especifícalo:
const data = await fetch('https://api.ejemplo.com/posts', {
next: { revalidate: 3600 }, // Revalidar cada hora
});unstable_cache para datos sin fetch
Para cachear resultados de queries a Supabase, ORMs u otras fuentes que no usan fetch:
import { unstable_cache } from 'next/cache';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
);
export const getPostsPublicados = unstable_cache(
async (limite = 6) => {
const { data, error } = await supabase
.from('posts')
.select('id, slug, title, excerpt, thumbnail_url, published_at, author_name')
.eq('status', 'published')
.order('published_at', { ascending: false })
.limit(limite);
if (error) throw new Error(error.message);
return data;
},
['posts-publicados'],
{ revalidate: 300 } // 5 minutos
);Invalidación granular con revalidateTag
import { revalidateTag } from 'next/cache';
export async function publicarPost(id: number) {
await supabase
.from('posts')
.update({ status: 'published', published_at: new Date().toISOString() })
.eq('id', id);
revalidateTag('posts-publicados'); // Solo invalida posts, no todo el sitio
}React 19 con Server Components: el stack completo
El hook use() para datos asíncronos
import { use } from 'react';
import { Suspense } from 'react';
function ContenidoPost({ postPromise }: { postPromise: Promise<Post> }) {
const post = use(postPromise); // React suspende hasta que resuelve
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
export default function PaginaPost({ params }: { params: { slug: string } }) {
const postPromise = getPostBySlug(params.slug);
return (
<Suspense fallback={<div>Cargando...</div>}>
<ContenidoPost postPromise={postPromise} />
</Suspense>
);
}Server Actions con useActionState
'use client';
import { useActionState } from 'react';
type State = { error?: string; success?: boolean };
async function enviarComentario(_prev: State, formData: FormData): Promise<State> {
'use server';
const contenido = formData.get('contenido') as string;
if (!contenido || contenido.trim().length < 10) {
return { error: 'El comentario debe tener al menos 10 caracteres' };
}
const { error } = await supabase
.from('comentarios')
.insert({ contenido, creado_en: new Date().toISOString() });
if (error) return { error: 'Error al guardar el comentario' };
return { success: true };
}
export function FormularioComentario() {
const [state, action, pending] = useActionState(enviarComentario, {});
return (
<form action={action}>
<textarea name="contenido" required minLength={10} />
{state.error && <p role="alert">{state.error}</p>}
{state.success && <p>¡Comentario enviado!</p>}
<button type="submit" disabled={pending}>
{pending ? 'Enviando...' : 'Enviar'}
</button>
</form>
);
}Errores comunes en proyectos Next.js 15
No revisar los fetches al migrar desde Next.js 14. Es el cambio más crítico. Revisa todos los fetch de tu aplicación y añade next: { revalidate: N } donde antes confiabas en el cacheo por defecto. Sin esto, perderás rendimiento sin entender por qué.
`use client` innecesario. Añadir use client a un componente sin necesidad arrastra al cliente todo el árbol de dependencias. Revisa el bundle analyzer antes de deploy.
Server Actions sin validación. Las Server Actions son endpoints HTTP implícitos. Valida siempre el input con Zod o similar antes de operaciones de escritura.
`unstable_cache` en datos que cambian frecuentemente. Si cacheas datos con TTL de una hora que cambian cada minuto, la experiencia se degrada. Usa revalidateTag para invalidación explícita.
Conclusión
Next.js 15 es la versión más madura del framework. Turbopack en desarrollo acelera el ciclo de feedback de forma real. El nuevo sistema de caché, aunque requiere más configuración explícita, es mucho más predecible. Y React 19 completo con use(), useActionState y Server Actions bien integradas abre patrones de código más limpios.
El riesgo principal de la migración desde Next.js 14 está en la caché. Dedica tiempo a revisar todos tus fetches antes que a cualquier otra cosa y la migración será directa y sin sorpresas.