Si Tu Fetch Sigue en un 'useEffect', No Estás Usando Next.js. Estás Pagándolo
Tu página carga. Se muestra el skeleton. El cliente dispara un `useEffect`. Hace un fetch a tu propia API route. Espera la respuesta. Re-renderiza.
*Y el servidor ya tenía esos datos 200 milisegundos antes. *
Ese patrón — fetch en efecto, SWR o React Query para datos iniciales, mutate contra una API route — es exactamente lo que Next.js lleva tres releases intentando eliminar. Desde el App Router en la 13, el servidor es el lugar por defecto para fetch, render y mutar. El componente cliente es el opt-in explícito.
La mayoría de codebases en producción siguen usando el manual de 2020.
En este artículo no vas a encontrar otra lista de features. Vas a encontrar las 3 que importan de verdad, por qué la inversión del caché en Next 15 te va a morder, y un método de 5 pasos para dejar de luchar contra el framework.
El Problema: Estás Arquitectando un Framework Nuevo con Manual de 2020
El mundo client-first no fue un accidente. React pasó diez años enseñándote a renderizar en el cliente. Bootcamps que hacen fetch con `useEffect`. Documentación que recomienda React Query para todo. Un ecosistema entero construido alrededor de una idea: el navegador es el que tiene que pedir los datos.
Next.js 13 lo invirtió de la noche a la mañana. Los River Server Components pasaron a ser el modelo por defecto. `'use client'` dejó de ser opcional para convertirse en la *válvula de escape*. Pero nadie te avisó de que estabas pagando un impuesto doble por arquitectar como antes:
❌ Re-descargas datos que el servidor ya tenía renderizados
❌ Pierdes streaming e ISR en rutas donde importan
❌ Añades una ronda de red extra por cada recurso
❌ Riesgo de hydration mismatch que no existía antes
✅ Server component hace `await` directamente sobre los datos
✅ El HTML llega completo al navegador
✅ Cero round trips extra para el render inicial
✅ El cliente solo re-hidrata lo que es interactivo
El coste es real y cuantificable: cada página re-descarga lo que el servidor ya tenía, cada round trip suma latencia y cada cache miss se paga en el render inicial.
Las 3 Features de Next.js 16 que Importan de Verdad
1. Partial Prerendering (PPR): El Fin del Binario Estático vs Dinámico
Durante años, Next.js te obligaba a elegir: página estática o dinámica. SSG o SSR. El binario era simple, pero falso — la mayoría de tus rutas no están en ninguno de los extremos.
PPR rompe el binario. Sirves un shell estático al instante y streameas las partes dinámicas solo cuando el servidor las tiene listas. Una página de producto con reviews dinámicas: el producto se sirve instantáneo, las reviews llegan cuando la base de datos responde.
```tsx
// app/products/[id]/page.tsx
import { Suspense } from 'react'
import { ProductCard } from './product-card'
import { Reviews } from './reviews'
// Esto es estático. Se entrega instantáneo.
export default function Page({ params }) {
return (
<div>
<h1>Producto</h1>
{/ Esto se streamea cuando está listo /}
<Suspense fallback={<ReviewSkeleton />}>
<Reviews productId={params.id} />
</Suspense>
</div>
)
}
```
El modelo mental que necesitas es un espectro, no un toggle:
→ Estático completo → ISR con `revalidate` → PPR (shell estático + islas dinámicas) → Dinámico completo
La mayoría de tus rutas están en el medio. Elegir el punto medio es una decisión arquitectónica legítima, no un compromiso.
2. Async Request APIs: El Cambio que Rompe Middlewares y Server Actions
Next.js 16 hace asíncronas las APIs que pedían la request — `cookies()`, `headers()`, `searchParams`. Esto rompe middlewares y Server Actions existentes que usaban la forma síncrona.
El cambio es correcto. El acceso síncrono a la request bloqueaba streaming y PPR. Hacerlo asíncrono permite que el shell renderice sin esperar a que se resuelvan las cookies.
```tsx
// CÓDIGO ROTO en Next 16 (forma antigua)
export async function Page() {
const token = cookies().get('token') // ❌ SyntaxError en runtime
return <Dashboard token={token} />
}
// CÓDIGO CORRECTO en Next 16
export async function Page() {
const cookieStore = await cookies() // ✅ Await obligatorio
const token = cookieStore.get('token')
return <Dashboard token={token} />
}
```
El coste: dos semanas de migración para equipos grandes. El diagnóstico: casi todas tus páginas que tocan request APIs. La solución: un script de codemod y una auditoría manual de cada uso.
3. Server Components Más Estrictos: 'use client' es un Compromiso Consciente
En Next 16, los Server Components se vuelven más estrictos. ProtoJSON va a reemplazar a serializable como estándar de serialización. Eso significa que pasar clases, funciones o fechas mal serializadas va a petar en build, no en runtime.
Esto es bueno. El error se mueve del runtime — donde lo pagas en producción — al build, donde lo arreglas antes de desplegar.
La Inversión del Caché que Nadie Te Explicó
Next.js 15 invirtió el comportamiento por defecto del fetch: dinámico por defecto, estático por opt-in. Si tu código dependía del caché por defecto que traía la 14, tu build ahora es más lento y tus páginas no se cachan.
No fue un capricho de producto. Fue arreglar un bug de diseño: la 14 cachaba por defecto, y eso causaba datos obsoletos en apps con escrituras frecuentes. La solución fue forzarte a elegir explícitamente.
```tsx
// Dinámico por defecto (Next 15+)
const data = await fetch('https://api.example.com/products')
// Opt-in: estático (caché de larga duración)
const staticData = await fetch('https://api.example.com/products', {
cache: 'force-cache'
})
// Opt-in: ISR-style
const revalidated = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }
})
// Opt-in: invalidation por tag
const data = await fetch('https://api.example.com/products', {
next: { tags: ['products'] }
})
// En tu Server Action o ruta de revalidación:
// revalidateTag('products')
```
La lección: la semántica del caché es una decisión de producto, no un detalle de implementación. Migrar una app grande por este cambio requiere auditar cada `fetch`, no solo subir la versión.
El Método de 5 Pasos Server-First
Llamo a esto *El Método de los 5 Pasos Server-First*. Es la forma más rápida de sacar tu codebase de 2020 sin una reescritura big-bang.
Paso 1: Audita ruta por ruta y clasifica
Cada ruta cae en uno de cuatro buckets: estático, dinámico, ISR (con `revalidate`) o streaming. La mayoría de los equipos nunca decide esto explícitamente — lo heredan del framework. Dedica una sesión a clasificar TODO.
Paso 2: Mueve el fetch inicial a server components
Borra los `useEffect` + SWR/React Query del render inicial. Deja que el servidor haga `await` directamente.
```tsx
// app/dashboard/page.tsx
// ANTES: useEffect + SWR (2020)
// DESPUÉS: el servidor resuelve los datos
export default async function Dashboard() {
const stats = await getStats() // fetch directo en servidor
return <StatsGrid stats={stats} />
}
// Si necesitas interactividad, esa pieza se etiqueta:
// app/dashboard/stats-chart.tsx ('use client')
```
Paso 3: Convierte las API routes de escritura en Server Actions
El patrón POST endpoint → client fetch → optimistic update → refetch se elimina con una form action.
```tsx
// app/posts/actions.ts
'use server'
import { z } from 'zod'
import { revalidatePath } from 'next/cache'
const schema = z.object({
title: z.string().min(3),
body: z.string().min(10)
})
export async function createPost(formData: FormData) {
// Valida TODOS los campos. El overposting es un riesgo real.
const parsed = schema.safeParse({
title: formData.get('title'),
body: formData.get('body')
})
if (!parsed.success) return { error: 'Datos inválidos' }
await db.post.create(parsed.data)
revalidatePath('/') // Invalida el caché de la ruta
}
```
```tsx
// app/posts/new/page.tsx
'use client'
import { useActionState } from 'react'
import { createPost } from './actions'
export function NewPostForm() {
const [state, formAction, pending] = useActionState(createPost, null)
return (
<form action={formAction}>
<input name="title" required />
<textarea name="body" required />
<button disabled={pending}>Publicar</button>
{state?.error && <p>{state.error}</p>}
</form>
)
}
```
Sobre la seguridad: las Server Actions reciben datos crudos del formulario. La validación server-side es obligatoria, no opcional. Usa zod en la acción, verifica el header de Origin si necesitas, y rate-limita las acciones sensibles. *"El servidor lo hace por ti"* es una feature y una responsabilidad a la vez.
Paso 4: Añade streaming a las rutas lentas
Añade `loading.tsx` y límites de `<Suspense>` antes de tocar el caché. El rendimiento percibido va primero; el caché, después.
```tsx
// app/products/[id]/loading.tsx
export default function Loading() {
return <ProductSkeleton />
}
```
Paso 5: Añade una ruta de escape para self-hosting
La dependencia de Vercel es más conveniencia que código. Next.js es MIT y el modo `standalone` produce un servidor Node autocontenido que corre en cualquier plataforma.
```ts
// next.config.ts
const config = {
output: 'standalone',
turbopack: true // dev con Turbopack
}
export default config
```
```bash
Dev con Turbopack (compilación casi instantánea)
next dev --turbopack
Build standalone: lista para desplegar en cualquier contenedor
npm run build
```
Sé honesto contigo mismo: self-hostable no significa zero-effort. En Vercel, el `revalidate` y los deploy previews funcionan sin configurar nada. Fuera, montas la infraestructura tú. La decisión es operativa, no técnica.
"Next.js Cambia Demasiado Rápido" — la Objeción que se Merece una Solución, no un Ataque
Es cierto. Las releases han sido agresivas y la 15 rompió el comportamiento del caché sin avisar.
Pero la respuesta no es quedarte en 2020. Es migrar incrementalmente: el App Router coexiste con el Pages Router durante la transición, y los cambios de caché son opt-in por fetch. Puedes actualizar sin una reescritura big-bang.
La complejidad que te asusta del App Router reemplazó una complejidad oculta que ya estabas manteniendo: sincronización de estado cliente, capas de API, invalidación manual de caché, y el baile infinito de optimistic updates. Cambiaste un Frankenstein por un framework con reglas claras.
El Resumen que te Llevas
El server-first es el modelo por defecto desde la 13. Si tu fetch inicial vive en un `useEffect`, estás pagando un impuesto invisible en cada carga.
El caché es una decisión de producto. Audita qué debe ser estático, qué dinámico y qué ISR. No lo dejes en manos del azar.
Las Server Actions eliminan la razón principal por la que añadiste un backend. Con zod en la acción y `revalidatePath`, tu form action reemplaza el baile completo de POST + fetch + optimistic update.
PPR es el fin del binario estático/dinámico. Tu mentalidad debe ser un espectro, no un toggle.
Eres self-hostable, no zero-effort. La decisión es operativa, no técnica.
Next.js 16 no es una actualización más de React. Es la consolidación de un cambio de mentalidad que empezó en 2022. *El framework ya no pregunta si el servidor debe renderizar — da por hecho que debe. * Y la pregunta que decide tu arquitectura ya no es técnica: es si estás dispuesto a desaprender una década de hábitos client-first.
Los equipos que lo hagan van a entregar páginas más rápidas, con menos código y menos infraestructura. Los que no, van a seguir pagando el impuesto del `useEffect` en cada carga.
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

