Mejores Prácticas de Datos en Next.js
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de cada página en esta sección.
- Obtén Datos en Componentes Server Asincronos: Pon
await fetch(...)directamente en el cuerpo del Componente Server en lugar de usaruseEffect— sin estado de carga, sin cascada del cliente, y los datos se envían como HTML. - Espera a params y searchParams: En Next.js 15+, ambos son Promises, por lo que desestructurar sin
awaitproduce un objeto Promise (a menudo renderizado como[object Promise]); siempre hazconst { slug } = await params. - Paraleliza Lecturas Independientes con Promise.all: Envuelve lecturas independientes —
const [user, posts] = await Promise.all([getUser(id), getPosts(id)])— para que la latencia total sea igual a la llamada más lenta en lugar de la suma, y deja que TypeScript infiera la tupla de resultado de forma gratuita. - Evita Cascadas Accidentales: Nunca encadenes
const a = await getA(); const b = await getB();para datos independientes — las promises comienzan en la creación, pero cadaawaitsigue bloqueando la siguiente línea, produciendo latencia secuencial. - Usa Promise.allSettled para Datos Opcionales: Cuando una fuente no es crítica (recomendaciones, análisis),
Promise.allSettledimpide que un único rechazo descarte los otros resultados exitosos y te permite renderizar una página parcial. - Envuelve Llamadas Sin fetch con React.cache: La memorización de solicitudes solo cubre la API
fetch, por lo que envuelve funciones de base de datos y ORM —const getUser = cache((id: string) => db.user.findUnique({ where: { id } }))— para obtener deduplicación por renderizado en layouts y páginas. - Aísla Obtenciones no-store Detrás de Suspense: Un único
cache: "no-store"en cualquier lugar de una ruta opta toda la ruta al renderizado dinámico; muévelo a un componente separado envuelto en<Suspense>para que el resto pueda permanecer estático. - Sé Explícito Sobre las Opciones de Caché de fetch: El valor predeterminado de Next.js 15 es
cache: "auto"(noforce-cache), por lo que establececacheonext.revalidateen cada fetch en lugar de confiar en lo que el framework infiera. - Nombra Etiquetas de Revalidación Específicamente: Las etiquetas son globales —
fetch(url, { next: { tags: ["product-abc123"] } })— usa cadenas con espacio de nombres en lugar de genéricas como"data"para querevalidateTag("product-abc123")no elimine silenciosamente cachés no relacionadas. - Entiende Stale-While-Revalidate:
revalidatePath/revalidateTagmarcan el caché como obsoleto para la siguiente solicitud — la respuesta actual sigue devolviendo datos obsoletos, por lo que usacache: "no-store"cuando necesitas datos frescos ahora mismo. - Usa "layout" para Actualizar Páginas Anidadas:
revalidatePath("/")actualiza solo la página raíz; pasa"layout"—revalidatePath("/dashboard", "layout")— para invalidar el layout más cada página secundaria bajo él. - Usa unstable_cache con Claves Únicas: Para fuentes sin fetch que necesitan almacenamiento en caché persistente,
unstable_cache(fn, keyParts, { tags, revalidate })es la herramienta — pero asigna a cada llamadakeyPartsdistintos y descriptivos o devolverás datos corruptos por colisiones de claves. - Llama a router.refresh Después de Mutaciones del Cliente: Después de que una Server Action actualice datos, el Router Cache del lado del cliente puede seguir sirviendo páginas obsoletas al retroceder/avanzar, por lo que activa
router.refresh()(o ajustaexperimental.staleTimes) para borrarlo. - Valida Entradas de Server Action en el Servidor: Las Server Actions son puntos finales POST alcanzables cuyos argumentos pueden ser valores cerrados enviados al cliente, por lo que valida cada campo dentro de la acción y nunca confíes en cierres para ocultar secretos.
- Llama a redirect Fuera de try/catch:
redirect()lanza un error centinela que Next.js captura para realizar la navegación; envolverlo entry/catchsuprime la redirección, por lo que colócalo después de tu mutación y fuera de cualquier controlador de errores. - Devuelve Errores a través de useActionState: Cuando un formulario necesita retroalimentación de validación, tipifica la acción como
(prevState, formData) => Promise<State>y conéctala a través deuseActionStatepara que el estado pendiente y los mensajes de error fluyan a través de React de forma limpia. - Empareja useOptimistic con useTransition: Para interfaces de usuario receptivas, llama a
addOptimisticdentro destartTransition—startTransition(async () => { addOptimistic(newItem); await saveItem(newItem) })— para que los usuarios vean el cambio al instante y la bandera pendiente siga controlando estados deshabilitados. - Usa Límites de Suspense Granulares: Envuelve cada fuente de datos independiente en su propio
<Suspense>para que los widgets se transmitan a medida que se resuelvan; un límite de nivel superior bloquea toda la página en la consulta más lenta y anula la transmisión. - Ajusta Esqueletos para que Coincidan con el Contenido Final: Los puntos de reserva deben compartir la relación de aspecto o dimensiones fijas del contenido real, de lo contrario el layout salta cuando llegan los datos y tomas un golpe CLS evitable.
- Empareja Suspense con error.tsx: Si algún hijo asincrónico lanza un error, la excepción burbujea más allá de Suspense; cada límite de Suspense debe tener un límite de error coincidente (
error.tsxo<ErrorBoundary>) al mismo nivel. - Envuelve useSearchParams en Suspense: En el cliente,
useSearchParams()lanza un error durante la prerrenderización estática, por lo que el componente que lo lee debe estar dentro de un límite<Suspense>o la compilación fallará. - Prefiere router.replace para Actualizaciones de Filtros: Los filtros, ordenamiento y búsqueda crean entradas de historial ruidosas con
router.push; usarouter.replacepara que el botón atrás regrese a una página anterior significativa y también reiniciapage=1cada vez que la consulta cambia. - Espera a cookies() y headers(): Ambos son asincronos en Next.js 15+ —
const token = (await cookies()).get("session")?.value— y tocar cualquiera hace que la ruta sea dinámica; léelos en un componente hoja pequeño bajo<Suspense>cuando quieras que el resto de la ruta permanezca estático. - Establece Cookies Solo en Actions o Middleware:
cookies()es de solo lectura en Componentes Server, por lo que las escrituras deben ocurrir en una Server Action, Route Handler, o Middleware —(await cookies()).set("session", token, { httpOnly: true, secure: true, sameSite: "lax" }). - Transmite Grandes Conjuntos de Datos con Generadores Asincronos: Para APIs paginadas, exportaciones CSV o puntos finales SSE, usa
async function*másfor await...ofpara mantener solo un lote en memoria, componer con ayudantes map/filter/take, y recuerda que los generadores son solo del servidor y de uso único.