Data fetching en Next.js: dónde pido los datos y por qué importa dónde
Hay un error de rendimiento que no se ve en el código y solo se ve en una pestaña de red del navegador: pedidos de datos que deberían dispararse todos juntos, disparándose uno atrás del otro porque cada uno espera a que termine el anterior.
Se llama cascada de requests, y en Next.js App Router es sorprendentemente fácil de crear sin darse cuenta.
Cómo se forma una cascada sin querer
async function Page() {
const user = await getUser(); // espera esto
return <Profile userId={user.id} />;
}
async function Profile({ userId }) {
const posts = await getPosts(userId); // recién ahora arranca esto
return <PostList posts={posts} />;
}
getPosts no puede empezar hasta que getUser termine, porque Profile necesita el userId que devuelve el primero. Eso es correcto — hay una dependencia real entre los dos datos. El problema aparece cuando esa dependencia no existe y el código igual queda escrito en cascada porque fue la forma más natural de escribirlo.
El caso sin dependencia real
async function Page() {
const user = await getUser();
const settings = await getSettings(); // no depende de `user` para nada
return <Dashboard user={user} settings={settings} />;
}
getSettings no usa nada de user. Podría haber arrancado al mismo tiempo. Pero como está escrito con dos await seguidos, se ejecuta uno después del otro, sumando sus tiempos en vez de superponerse.
La forma correcta:
async function Page() {
const [user, settings] = await Promise.all([getUser(), getSettings()]);
return <Dashboard user={user} settings={settings} />;
}
Ahora los dos arrancan en el mismo instante. Si cada uno tarda 200ms, la versión en cascada tarda 400ms y esta tarda 200ms. La diferencia crece con cada pedido adicional que se agregue en cascada sin necesidad.
El criterio que uso para decidir en qué nivel pedir cada dato
Si dos datos no dependen entre sí, se piden en el componente más alto posible, en paralelo. Esto evita la cascada y además evita que cada componente hijo tenga que volver a pedir el mismo dato que ya pidió su padre.
Si un dato es específico de una sola sección de la página, se pide en el componente de esa sección, no arriba de todo. Pedirlo arriba "por las dudas" retrasa el render de toda la página esperando un dato que solo una parte necesita. En este mismo portafolio, WhyMe pide sus propias traducciones en vez de recibirlas del componente padre — no hay razón para que el Hero espere a que ese dato esté listo si el Hero no lo usa.
Si un dato realmente depende de otro, la cascada es correcta y no hay que forzarla a paralelo. Forzar un Promise.all entre datos que sí dependen entre sí no acelera nada, porque igual hay que esperar al primero para poder pedir el segundo — solo agrega complejidad sin beneficio real.
Cache: la otra mitad del problema
Pedir el mismo dato en dos componentes distintos del mismo request no debería duplicar la consulta a la base. En este sitio, cada función de lectura de contenido pasa por cached(), que envuelve la consulta con unstable_cache — así que si Header y Footer piden el mismo dato de configuración del sitio, la segunda llamada devuelve el resultado cacheado en vez de golpear la base de nuevo.
Esto es lo que hace seguro pedir un dato "más cerca de donde se usa" sin miedo a duplicar el costo: la duplicación del pedido en el código no es duplicación del trabajo real, si el cache está bien puesto.
Cómo lo detecto cuando ya pasó
Abro la pestaña de red del navegador y miro la forma de las barras de tiempo. Pedidos en paralelo se ven como una fila de barras que empiezan en el mismo punto. Una cascada se ve como una escalera: cada barra empieza donde termina la anterior.
Si veo una escalera donde esperaba una fila, reviso si hay una dependencia real entre esos dos pedidos. La mitad de las veces, no la hay — solo estaba escrito en el orden en que se me ocurrió escribirlo, no en el orden que el rendimiento necesitaba.
Seguí leyendo
- Server Components vs Client Components en Next.js: cuándo uso cada unoLa pregunta no es cuál es mejor. Es qué necesita interactividad y qué no, y esa distinción cambia cuánto JavaScript le mandás al navegador de cada visitante.
- Qué es un design system (y cuándo NO necesitás uno)Construir un design system para un proyecto de una sola pantalla es gastar dos semanas en flexibilidad que nadie va a usar. Cuándo vale la pena y cuándo es sobre-ingeniería con nombre elegante.