Next

Domina la descripción del puesto

Guía de Estudio

Cada requisito que un rol senior de Next.js / React comúnmente pide — explicado a profundidad senior. Lee el concepto, luego la línea de 'cómo decirlo' para que puedas entregar una respuesta precisa y concisa en voz alta.

Cómo usar esto Cada tema explica el concepto a la profundidad que una entrevista senior de Next.js / React espera, y luego da una línea de 'cómo decirla' — la oración concisa para entregar en voz alta. Lee para entender primero; ensaya las líneas de una sola línea al final.

01 · El modelo mental del App Router

Lo que quieren: entiendas que el App Router invierte el defecto. Cada componente bajo app/ es un Server Component a menos que opte por "use client". Esa es una inversión deliberada del Pages Router, donde todo se enviaba al navegador por defecto.

Por qué existe RSC: tres ventajas. Se envía menos JavaScript — la lógica de un Server Component y sus dependencias nunca llegan al bundle del cliente. Los secretos permanecen del lado del servidor — puedes leer una base de datos o una clave API directamente en el cuerpo de un componente sin riesgo de que se filtre a una etiqueta script. Y los datos se obtienen cerca de la fuente — sin cascada del lado del cliente de "montar, luego obtener", sin impuesto de spinner de carga para cada hoja que necesita datos.

El formato de transmisión es el Payload RSC: no es HTML, un árbol serializado compacto que describe la salida del Server Component renderizado, con marcadores para dónde van los Client Components más las props serializables que necesitan. En la primera carga, el servidor también renderiza ese árbol a HTML para una pintura rápida. La secuencia es: HTML llega y pinta inmediatamente → el payload RSC reconcilia el árbol (React lo empareja) → el JS del cliente hidrata solo las islas de Client Components, conectando manejadores de eventos y estado.

Cómo decirlo "Los Server Components son el defecto — se renderizan a un payload serializado con marcadores para Client Components, así que solo las islas interactivas envían JS e hidratan."

02 · Enrutamiento por sistema de archivos y estructura de proyecto

Núcleo: el directorio app/ es el router — las carpetas definen segmentos de URL, y archivos con nombres especiales dentro de una carpeta definen el comportamiento para ese segmento. page.tsx hace que un segmento sea públicamente alcanzable y renderiza la UI. layout.tsx envuelve un segmento y sus hijos, preserva estado entre navegaciones (no se remonta), y se anida automáticamente con layouts padres.

template.tsx parece un layout pero se remonta en cada navegación — útil para animaciones de entrada/salida o resetear estado por visita. loading.tsx envuelve automáticamente el segmento en un límite <Suspense> y se muestra mientras los datos del segmento se resuelven. error.tsx es un límite de errores de Client Component con alcance en ese segmento (debe ser un Client Component porque los límites de errores usan estado de componente). not-found.tsx se renderiza cuando se lanza notFound() o una ruta no puede coincidir. default.tsx es la UI de fallback para un slot de ruta paralela cuando Next no puede recuperar el estado activo en una navegación dura.

Organiza sin cambiar la URL: los grupos de rutas (marketing) envuelven segmentos en paréntesis para que el nombre de la carpeta se elimine del camino — útil para dar a una sección su propio layout sin agregar un segmento de URL. Las carpetas privadas _components (guión bajo al inicio) sacan una carpeta del enrutamiento por completo, para que puedas colocar ayudantes, pruebas y archivos de no-ruta junto a las rutas que los usan.

Cómo decirlo "El árbol de carpetas es el árbol de rutas — page.tsx es lo público, layout.tsx persiste entre navegaciones, y los grupos de rutas me permiten organizar o agregar un límite de layout sin tocar la URL."

03 · Server Components vs Client Components

Predetermina Server Components para cualquier cosa que obtenga datos, lea secretos, o no necesite interactividad — lo cual en la mayoría de aplicaciones es la mayoría del árbol. Recurre a un Client Component ("use client") solo para la hoja que realmente necesita useState, useEffect, manejadores de eventos, o APIs solo-navegador.

La directiva es un límite del grafo de módulos, no un flag por componente. "use client" al inicio de un archivo marca ese archivo como el punto de entrada al bundle del cliente — todo lo que ese archivo importa y renderiza directamente también se envía al cliente, incluso si esos módulos no tienen directiva propia. No significa "solo este componente es un Client Component"; significa "aquí es donde se divide servidor y cliente."

La intercalación es el truco que mantiene a los Server Components renderizados en servidor incluso dentro de un árbol de Client Components: pasa un Server Component como children u otra prop a un Client Component, en lugar de importarlo y renderizarlo directamente. <Modal><Cart/></Modal>Modal es un Client Component que provee la interactividad de abrir/cerrar, pero Cart se compone desde un padre Server Component y permanece renderizado en servidor; Modal solo recibe su output ya renderizado como un slot.

React Context necesita un wrapper cliente porque el contexto depende de createContext/useContext, que requieren reactividad del lado del cliente — un Server Component no puede suscribirse a nada. La convención es un pequeño archivo de proveedor "use client" que envuelve children, para que todo lo que esté debajo aún pueda ser un Server Component por defecto.

server-only y client-only son paquetes de protección: importar server-only a un módulo lanza un error de compilación si ese módulo es alguna vez incluido en un bundle del cliente (y viceversa) — seguro barato contra un ayudante que lee secretos filtrándose accidentalmente al navegador.

Trampa Agregar "use client" a un layout o página de nivel superior "solo para arreglar un error" arrastra todo lo que importa al bundle del cliente. Empuja la directiva tan abajo como sea posible — a la hoja interactiva específica, no a sus ancestros.
Cómo decirlo "'use client' marca un límite de módulo, no un solo componente — así que lo empujo a la hoja más pequeña que necesita interactividad y paso Server Components como children para mantener el resto del árbol renderizado en servidor."

04 · Estrategias de renderizado

Núcleo: el App Router colapsa la vieja taxonomía SSG/ISR/SSR en dos ejes — renderizado estático y dinámico — decidido por ruta en tiempo de compilación/solicitud en lugar de declararse de antemano. El renderizado estático se ejecuta en tiempo de compilación (o una vez, luego se cachea), produciendo HTML que se reutiliza para cada visitante — esto es lo que SSG e ISR se convierten efectivamente. El renderizado dinámico se ejecuta en cada solicitud, porque la ruta necesita algo que solo se puede conocer en tiempo de solicitud.

Cómo decide el router: una ruta se vuelve dinámica cuando toca una API en tiempo de solicitud — leer cookies(), headers(), un searchParams sin caché, o llamar un fetch sin caché. En el modelo anterior (pre-Cache Components), esto se hacía explícito con la configuración de segmento de ruta export const dynamic = 'force-dynamic' | 'force-static', que fija el comportamiento en lugar de dejar que Next lo infiera del uso de API. ISR en este marco es solo renderizado estático con una ventana de revalidate — servir el HTML cacheado y regenerarlo en segundo plano después de que la ventana expire.

Cómo decirlo "Pienso en estático vs dinámico, no en SSG vs SSR — una ruta es dinámica en el momento que toca una API en tiempo de solicitud, e ISR es solo renderizado estático con una ventana de revalidate."

05 · Cache Components y Prerenderizado Parcial

Núcleo: Cache Components es el nuevo modelo de caché, habilitado con cacheComponents: true en next.config.ts. Reemplaza "¿es esta ruta estática o dinámica?" con una pregunta más granular: ¿qué partes de una ruta son cacheables, marcadas explícitamente en lugar de inferidas?

La directiva "use cache" marca una función, componente, o archivo completo como cacheable. Ponla al inicio de una función asíncrona y su valor de retorno se cachea; ponla al inicio de un archivo y cada función exportada en ese archivo es cacheable. La clave de caché se deriva automáticamente de los argumentos de la función y cualquier valor capturado de su clausura — no creas una clave manualmente.

cacheLife(profile) y cacheTag(tag), ambos importados de next/cache, ahora son estables — sin más prefijo unstable_. cacheLife establece cuánto tiempo una entrada de caché se considera fresca (perfiles incorporados como 'seconds', 'minutes', 'hours', 'days', u un objeto personalizado); cacheTag adjunta una etiqueta de invalidación que puedes apuntar después con revalidateTag/updateTag.

El Prerenderizado Parcial es el mecanismo de entrega: el shell estático de una ruta — todo lo que no está envuelto en acceso a datos dinámicos — se prerenderiza en tiempo de compilación, y un límite <Suspense> alrededor de una pieza dinámica crea un "hueco" en ese shell que se transmite en tiempo de solicitud. El shell estático se sirve instantáneamente desde el borde del CDN; el hueco dinámico se llena un latido después.

Bajo Cache Components, acceder a una API en tiempo de solicitud (como cookies()) o una fuente de datos sin caché fuera de un límite <Suspense> o una función "use cache" es un error de compilación/dev — el framework se rehusa a adivinar si esa pieza debería ser estática o dinámica y te fuerza a ser explícito. connection() es la ruta de escape para llamadas no determinísticas (Math.random(), Date.now()) que no son APIs en tiempo de solicitud pero aún no deberían hornearse en el shell estático — esperarla difiere el resto de la función al tiempo de solicitud.

Trampa Cache Components es opt-in y cambia las reglas — no asumas que está activo en cada proyecto de Next 16. Verifica next.config.ts para cacheComponents: true antes de razonar sobre el comportamiento de caché.
Cómo decirlo "Con Cache Components, el caché es explícito — 'use cache' marca lo que es cacheable con una clave derivada automáticamente, cacheLife/cacheTag controlan frescura e invalidación, y los límites de Suspense tallan los huecos dinámicos de un shell de lo demás estático y prerenderizado."

06 · El modelo de caché anterior

Núcleo: a menos que una ruta opte por Cache Components, se ejecuta en el modelo de caché original del App Router — y ese modelo es importante conocerlo porque aún es el defecto. Esto no es una capa debajo de Cache Components; son dos modelos mentales diferentes para el mismo problema, y mezclar su vocabulario en una respuesta de entrevita es una señal de que no realmente sabes cuál está ejecutando un proyecto dado.

En este modelo, fetch() es sin caché por defecto en el App Router (un cambio deliberado del Pages Router, donde la obtención de datos no tenía caché incorporado en absoluto). Optas por ello por llamada: fetch(url, { cache: 'force-cache' }) cachea indefinidamente hasta invalidarse manualmente, y fetch(url, { next: { revalidate: 60, tags: ['posts'] } }) cachea con una ventana de revalidación basada en tiempo y/o una etiqueta que puedes invalidar bajo demanda.

Para acceso a datos que no es una llamada fetch — una consulta ORM, un cliente de base de datos directo — unstable_cache() da el mismo comportamiento de caché (TTL, etiquetas) para una función asíncrona arbitraria. La configuración de segmento de ruta aún aplica aquí: export const dynamic, export const revalidate, y export const fetchCache al inicio de un page.tsx/layout.tsx fijan el comportamiento de caché de toda la ruta en lugar de decidirlo por fetch.

La función cache() de React es una herramienta completamente diferente — no persiste datos entre solicitudes, deduplica llamadas a la misma función dentro de una sola solicitud, así que si tres componentes en una página todos llaman getUser(id), el trabajo subyacente se ejecuta una vez.

Cómo decirlo "Sin Cache Components, fetch es sin caché por defecto y opto por ello con force-cache o una opción revalidate/tags; para datos no-fetch uso unstable_cache, y el cache() de React es solo dedup por solicitud, no caché persistente."

07 · Streaming y Suspense

Núcleo: envolver una pieza lenta de UI en <Suspense fallback={...}> permite que el resto del shell HTML de la ruta se transmita al navegador inmediatamente, con el fallback mostrado en lugar de la parte lenta, luego el contenido real se transmite y reemplaza el fallback una vez que se resuelve — sin necesidad de bloquear toda la página detrás de la dependencia de datos más lenta.

La sutileza que vale la pena decir en voz alta: estar envuelto en Suspense no es lo mismo que ser dinámico. Si el componente dentro del límite es síncrono y no toca ninguna API en tiempo de solicitud, aún se resuelve completamente en tiempo de compilación — el límite de Suspense solo está presente estructuralmente en el JSX, pero no hay nada en lo que realmente se suspenda, así que no contribuye a un hueco dinámico bajo Prerenderizado Parcial. Suspense solo crea comportamiento de streaming útil cuando algo dentro de él es realmente asíncrono y lento (un fetch real, una API real en tiempo de solicitud).

Trampa No asumas que envolver algo en Suspense hace que una ruta sea dinámica o mejore su comportamiento en tiempo de compilación — verifica si el componente envuelto realmente espera algo dependiente de la solicitud, o el límite es peso muerto.
Cómo decirlo "Suspense transmite el shell primero y llena la parte lenta después — pero solo si lo que está dentro realmente se suspende en algo asíncrono; un componente síncrono en un límite de Suspense aún se resuelve en tiempo de compilación."

08 · Patrones de obtención de datos

Núcleo: los Server Components obtienen datos directamente en el cuerpo del componente con await — sin useEffect, sin boilerplate de estado de carga para el caso común. El patrón que eliges controla si las obtenciones independientes se ejecutan en paralelo o se serializan accidentalmente.

Paralelo: inicia múltiples llamadas fetch/consulta sin esperar cada una inmediatamente, luego espéralas juntas — ya sea esperando dos promises juntas o explícitamente con Promise.all([getUser(id), getPosts(id)]). Ambas solicitudes se disparan al mismo tiempo en lugar de que una espere a la otra.

Secuencial (una cascada): a veces inevitable y a veces intencional — necesitas el resultado de una obtención (el id del equipo de un usuario) antes de poder hacer la siguiente (los miembros de ese equipo). La habilidad no es "nunca cascada", es reconocer qué dependencias son reales y cuáles son accidentales (por ejemplo, dos componentes esperando independientemente dentro de JSX anidado cuando podrían haber juntos más arriba en el árbol).

Precarga: un idioma común es una utilidad preload() — una llamada no esperada a una función de datos envuelta en cache(), disparada temprano (frecuentemente al inicio de un padre) para que la solicitud ya esté en vuelo cuando un componente hijo realmente la espere más profundo en el árbol. Porque cache() deduplica por argumentos dentro de la solicitud, el await getUser(id) posterior del hijo solo resuelve el promise ya iniciado en lugar de disparar una segunda solicitud.

use() es la API para la dirección opuesta — canalizar un promise de un Server Component a un Client Component y leerlo allí. El Server Component inicia la obtención y pasa el promise mismo (no el valor esperado) como prop; el Client Component llama use(promise) dentro de un límite <Suspense> para suspender hasta que se resuelva, lo que permite que el shell renderizado por servidor se transmita inmediatamente mientras ese valor específico se transmite después.

Cómo decirlo "Inicio obtenciones independientes juntas para que se ejecuten en paralelo, uso un preload() respaldado por cache() para iniciar solicitudes temprano, y use() cuando necesito transmitir un promise de un Server Component a un Client Component sin bloquear el shell."

09 · Server Functions y Server Actions

Núcleo: la directiva "use server" marca una función como Server Function — código que se define dondequiera que lo escribas pero siempre se ejecuta en el servidor, invocable desde Client Components como si fuera una función asíncrona local. Ponla al inicio del cuerpo de la función para una ocasional, o al inicio de un archivo para marcar cada exportación en ese archivo. Cuando una Server Function se pasa a un formulario, convencionalmente se llama Server Action.

La invocación idiomática es <form action={myServerFunction}>, o formAction en un botón de envío individual cuando un formulario necesita soportar múltiples acciones. Internamente, Next genera una referencia estable a la función y convierte el envío del formulario en un POST.

El hecho crítico de seguridad: cada Server Function es siempre un endpoint POST y siempre directamente alcanzable desde fuera de tu UI — cualquiera que tenga la firma de solicitud puede llamarla con curl, evadiendo tu formulario, el estado deshabilitado de tu botón, tus verificaciones del lado del cliente completamente. Esto significa que debes verificar autenticación y autorización dentro de la función misma, cada vez, nunca confiando en "el botón solo se renderiza para administradores" como un límite de seguridad. El control del lado del cliente es UX, no defensa.

Múltiples llamadas a Server Functions despachadas desde el cliente se envían secuencialmente, una a la vez — no en paralelo — lo cual importa si estás disparando varias acciones en respuesta a una interacción y esperas que compitan.

useActionState conecta una Server Function a estado pendiente/resultado para un formulario (reemplazando el viejo patrón de rastrear manualmente un booleano de carga y un resultado). useOptimistic te permite renderizar el resultado esperado de una acción inmediatamente, antes de que el servidor lo confirme, luego reconcilia una vez que la respuesta real regresa.

Trampa "Solo se llama desde un formulario que controlo" no es un argumento de seguridad — las Server Functions son alcanzables por POST independientemente de tu UI. Verifica auth dentro de la acción, no solo alrededor del disparador.
Cómo decirlo "Las Server Functions son directamente alcanzables por POST independientemente de la UI que las dispara, así que verifico auth dentro de cada acción — y uso useActionState para pendiente/resultado y useOptimistic para la versión de retroalimentación instantánea."

10 · Mutaciones y revalidación

Núcleo: después de una mutación, algo tiene que decirle al caché que sus datos están obsoletos. revalidatePath(path) invalida todo lo cacheado para una ruta específica — grueso pero simple. revalidateTag(tag, profile) invalida cada entrada de caché que carry esa etiqueta, dondequiera que viva en la aplicación — más granular, ya que una etiqueta puede abarcar muchas rutas.

APIComportamiento
revalidatePathInvalidar por ruta
revalidateTag(tag, profile)Invalidar por etiqueta — estilo SWR: el próximo visitante puede aún recibir datos obsoletos brevemente mientras se regenera
updateTag(tag)Invalidar por etiqueta, solo para Server Actions — read-your-writes, refresca inmediatamente, sin ventana obsoleta
refresh()Solo para Server Actions — refresca UI sin caché/dinámica en pantalla sin tocar el caché en absoluto

Cambio de ruptura en Next 16: revalidateTag ahora toma un segundo argumento requerido de perfil cacheLife, porque se comporta con semántica stale-while-revalidate — el perfil le dice cómo tratar la ventana de transición. Esto es diferente de updateTag (nuevo en 16, solo para Server Actions), que te da frescura inmediata, read-your-writes, sin ventana obsoleta, y refresh() (también nuevo en 16, también solo para Server Actions), que refresca solo las partes sin caché de la UI actual sin invalidar nada en el caché compartido.

redirect() funciona lanzando una excepción de control de flujo especial que Next atrapa internamente — esto significa que el código después de una llamada redirect() nunca se ejecuta, y cualquier revalidación que necesites debe ocurrir antes de que lo llames, no después.

Cómo decirlo "revalidatePath/revalidateTag son estilo SWR — revalidateTag ahora necesita un perfil cacheLife en Next 16 — mientras updateTag y refresh son las nuevas primitivas solo para Server Actions para frescura inmediata, read-your-writes. Y siempre revalide antes de redirigir, porque redirect lanza una excepción."

11 · Enrutamiento dinámico

Núcleo: los corchetes en un nombre de carpeta crean un segmento dinámico. [slug] coincide exactamente con un segmento de ruta (/blog/hello-worldslug: 'hello-world'). [...catchAll] coincide con uno o más segmentos y los captura como un array (/docs/a/b/c['a','b','c']) — pero /docs solo no coincidirá. [[...optionalCatchAll]] es lo mismo, pero además coincide con la ruta base con cero segmentos, haciendo que todo el array sea opcional.

generateStaticParams es una función asíncrona exportada desde el page.tsx de una ruta dinámica que retorna la lista de valores de parámetros para prerenderizar en tiempo de compilación — el reemplazo del App Router para getStaticPaths. Los parámetros no retornados por ella dan 404 o se renderizan dinámicamente en la primera solicitud, dependiendo de la configuración.

params (y searchParams) son Promises que deben esperarse (await) antes de usarse — esto aplica a cada segmento dinámico y cada página/layout que los lea.

Cómo decirlo "[slug] es un segmento, [...catchAll] es uno-o-más capturado como un array, y la versión de doble corchete hace que ese array sea opcional — y generateStaticParams es cómo le digo a la compilación qué valores prerenderizar."

12 · Grupos de rutas, rutas paralelas y rutas interceptadas

Grupos de rutas (marketing) organizan archivos y pueden definir múltiples layouts raíz — pon dos grupos de rutas directamente bajo app/, cada uno con su propio layout.tsx que contenga <html>/<body>, y obtienes, digamos, un shell completamente diferente para un sitio de marketing versus un dashboard, sin compartir nada en la raíz.

Rutas paralelas usan una carpeta @slot para renderizar más de una página independiente en el mismo layout simultáneamente — un dashboard con un slot @analytics y un slot @team renderizados lado a lado, cada uno con su propio estado de carga/error y su propia sub-navegación. El layout recibe cada slot como una prop y lo coloca en JSX como cualquier otro hijo.

Cambio de ruptura en Next 16 Cada slot de ruta paralela ahora requiere un default.js explícito — anteriormente Next caía gracefully en algunos casos, pero ahora un slot sin uno causa un fallo de compilación. Agrega un default.tsx a cada carpeta @slot, aunque solo renderice null.

Rutas interceptadas permiten que una ruta se renderice en el contexto del layout actual mientras aún actualiza la URL — el caso de uso clásico es una cuadrícula de fotos donde hacer clic en una miniatura la abre como modal (interceptada), pero una actualización dura o enlace directo a esa URL renderiza la página completa independiente en su lugar. La convención son prefijos de estilo de ruta relativa en el nombre de la carpeta: (.) intercepta un segmento en el mismo nivel, (..) un nivel arriba, (..)(..) dos niveles arriba, y (...) desde la raíz.

Cómo decirlo "Las rutas paralelas con @slots renderizan páginas independientes en un layout — y cada slot necesita su propio default.js ahora en Next 16 — mientras las rutas interceptadas con (.)/(..)/(...) me dan URLs reales para modales que aún caen a la página completa en actualización."

13 · proxy.ts (anteriormente middleware.ts)

Núcleo: Next 16 renombró el archivo y la exportación — middleware.ts es ahora proxy.ts, y la función exportada middleware ahora se llama proxy. El renombrado es intencional: "middleware" implicaba que podía sentarse en cualquier parte del ciclo de solicitud/respuesta haciendo trabajo arbitrario, cuando en realidad esta capa es específicamente un límite de red que se ejecuta antes de que una solicitud llegue a tus rutas — proxy nombra eso con precisión.

El cambio funcional junto al renombrado: proxy.ts se ejecuta solo en el runtime de Node.js ahora — no hay opción de edge runtime para él. La ruta obsoleta middleware.ts aún existe y aún soporta edge, mantenido para proyectos que específicamente necesitan ejecución en edge, pero el código nuevo debería usar proxy.ts y aceptar el runtime de Node.js.

Usos comunes: proteger rutas con auth (verificando una cookie de sesión antes de permitir que una solicitud pase y redirigiendo a /login si falta), pruebas A/B (reescribiendo una solicitud a una ruta variante basada en una cookie o encabezado), y reescritura/inyección de encabezados (agregando un id de solicitud, un nonce CSP, o normalizando un encabezado antes de que llegue a un route handler).

Cómo decirlo "Ahora es proxy.ts y export function proxy, no middleware — el renombrado hace explícito que es un límite de red, y en Next 16 es solo Node.js, sin más opción de edge para código nuevo."

14 · Route Handlers

Núcleo: un archivo route.ts dentro de app/ exporta funciones nombradas por verbo HTTP — GET, POST, PUT, DELETE, etc. — convirtiendo ese segmento en un endpoint API en lugar de una página. Una carpeta puede tener page.tsx o route.ts para un segmento dado, no ambos.

Cuándo usar cuál: una Server Action / Server Function es la herramienta correcta cuando el llamador es tu propia UI y estás realizando una mutación desencadenada por interacción del usuario — te da ergonomía de llamada a función directa sin cableado manual de fetch/JSON. Un Route Handler es la herramienta correcta cuando necesitas un endpoint HTTP real — un objetivo de webhook para un servicio de terceros, una API consumida por un cliente no-Next (una aplicación móvil, otro servicio), o cuando necesitas control explícito sobre encabezados/códigos de estado/respuestas de streaming. Una simple llamada fetch/datos de Server Component es correcta cuando solo estás leyendo datos para renderizar — sin handler necesario en absoluto, ya que el componente mismo puede hablar con la base de datos o un servicio interno directamente.

Caché para handlers GET bajo Cache Components: el output de un handler GET no se cachea implícitamente como podría haberlo estado bajo algunas convenciones en el viejo modelo — optas por ello explícitamente, de la misma manera que lo harías dentro de cualquier otra función: envuelve la lógica que produce datos en "use cache" (con cacheLife/cacheTag según sea necesario) si quieres que la respuesta se cachee, en lugar de depender del caché de configuración de ruta a nivel de framework.

Cómo decirlo "Server Actions para mutaciones desencadenadas desde mi propia UI, Route Handlers cuando necesito un endpoint HTTP real para un webhook o un cliente externo, y simples fetches de Server Component cuando solo estoy leyendo datos para renderizar."

15 · Metadatos y SEO

Núcleo: exporta un objeto metadata estático desde un page.tsx/layout.tsx cuando los títulos/descripciones/etiquetas OG no dependen de datos — Next fusiona metadatos hacia abajo en el árbol de layouts, con segmentos hijos sobrescribiendo campos padres. Cuando los metadatos dependen de datos obtenidos (el título de una publicación de blog, el precio de un producto en la descripción), exporta una función asíncrona generateMetadata() en su lugar, que recibe los params de la ruta y puede esperar lo que necesite.

next/font auto-hospeda archivos de fuente en tiempo de compilación — Google Fonts o archivos locales se descargan una vez durante la compilación y se sirven desde tu propio dominio, así que no hay solicitud de red externa a una CDN de fuentes en tiempo de ejecución (mejor privacidad, una ida y vuelta DNS/TLS menos) y no hay cambio de diseño por una fuente que se cambia tarde, ya que las métricas de la fuente se conocen y reservan de antemano.

Las imágenes de Open Graph y Twitter card pueden ser archivos estáticos (opengraph-image.png en una carpeta de ruta) o generarse dinámicamente con un archivo opengraph-image.tsx usando ImageResponse. sitemap.ts y robots.ts son archivos especiales que generan /sitemap.xml y /robots.txt programáticamente, para que puedan derivarse de tus datos de ruta reales (por ejemplo, cada publicación de blog) en lugar de mantenerse manualmente.

Cómo decirlo "Exportaciones de metadatos estáticos para contenido fijo, generateMetadata cuando depende de datos obtenidos, y next/font para que no haya cambio de diseño ni solicitud externa para tipografía."

16 · Optimización de imágenes y fuentes

Núcleo: next/image reemplaza un <img> simple con un componente que genera un srcset responsivo automáticamente (para que el navegador descargue un tamaño apropiado para el viewport, no siempre la versión más grande), carga diferidamente imágenes fuera de pantalla por defecto, y previene cambios de diseño requiriendo width/height (o fill) de antemano para que el navegador reserve espacio antes de que la imagen cargue.

Las imágenes remotas deben estar en la lista de permitidos vía images.remotePatterns en next.config.ts — un patrón estructurado (protocolo, hostname, puerto, pathname) en lugar del viejo images.domains más grueso (obsoleto), que solo ponía en la lista de permitidos un hostname entero sin granularidad de ruta/protocolo.

Cambios de defecto de Next 16 que vale la pena conocer de memoria: minimumCacheTTL (cuánto tiempo se cachea una variante de imagen optimizada) pasó de 60 segundos a 4 horas — un defecto mucho más sensato para imágenes que rara vez cambian. Y la opción qualities se redujo a solo [75] por defecto, reduciendo el número de variantes de calidad distintas generadas (y cacheadas) por imagen a menos que configures explícitamente más.

next/font (cubierto en la sección de metadatos) aplica aquí también — es la mitad de fuentes de la misma filosofía "optimizar assets estáticos en tiempo de compilación, servir desde tu propio dominio" que next/image aplica a imágenes.

Trampa Olvidar images.remotePatterns para un nuevo host de imagen externo es uno de los errores más comunes de "¿por qué mi imagen está rota en producción" — el servidor de desarrollo a veces lo enmascara dependiendo de la configuración, pero una compilación de producción estricta se rehusará a optimizar un host no listado.
Cómo decirlo "next/image me da srcset responsivo, carga diferida, y cero cambio de diseño gratis, y pongo en la lista de permitidos hosts remotos con remotePatterns en lugar de la lista obsoleta de domains."

17 · APIs Asíncronas de Solicitud

Núcleo: cookies(), headers(), draftMode(), y los params y searchParams de cada ruta son todos Promises — esto solía ser una migración gradual (el acceso síncrono fue obsoleto con una advertencia por un tiempo), pero a partir de Next 16 el acceso síncrono ha sido completamente removido. Cada uno de estos debe esperarse (await) antes de poder leerse, tanto en Server Components como en Route Handlers.

export default async function Page({ params, searchParams, }: PageProps<'/blog/[slug]'>) { const { slug } = await params; const { q } = await searchParams; const store = await cookies(); }

El razonamiento se conecta con Cache Components: hacer que estas APIs sean asíncronas, en lugar de sincronizarlas desde algún objeto de solicitud ambiental, es lo que le permite al framework razonar sobre un componente como "esto espera algo dependiente de la solicitud" versus "esto no" — que es exactamente la señal que el Prerenderizado Parcial necesita para decidir qué pertenece al shell estático versus un hueco dinámico.

next typegen genera los tipos auxiliares para las rutas reales del proyecto — PageProps, LayoutProps, y RouteContext — parametrizados por el patrón de ruta literal (como PageProps<'/blog/[slug]'> arriba), para que params/searchParams regresen tipados correctamente para esa ruta específica en lugar de un genérico Record<string,string>.

Trampa Código copiado de un ejemplo anterior de Next.js que lee params.slug síncronamente lanzará un error o se comportará silenciosamente mal en Next 16 — siempre await params primero.
Cómo decirlo "cookies, headers, draftMode, params, y searchParams son todos asíncronos ahora — completamente asíncronos, sin fallback síncrono en Next 16 — y uso los PageProps/LayoutProps de next typegen para que regresen tipados correctamente por ruta."

18 · Patrones de autenticación y autorización

Núcleo: la forma común es basada en sesión o cookie — al hacer login, establece una cookie HTTP-only (un id de sesión o un token firmado/cifrado) que el servidor lee en solicitudes subsiguientes para identificar al usuario. HTTP-only lo mantiene fuera del alcance del JavaScript del lado del cliente, lo cual importa para resistencia a XSS.

Defensa en profundidad en dos capas: verificar la sesión en proxy.ts se trata de UX y enrutamiento — redirigir a un visitante no autenticado a /login antes de que siquiera vea el shell de una página protegida, o reescribir basado en rol. Pero verificar auth otra vez dentro del Server Component real, Route Handler, o Server Action es lo que provee seguridad real — porque, por la sección de Server Functions, esos son directa e independientemente alcanzables independientemente de cualquier lógica de proxy que esté frente a la UI. Trata las verificaciones a nivel de proxy.ts como una redirección agradable de tener, nunca como la única puerta.

En la práctica, la mayoría de aplicaciones de producción no crean gestión de sesiones manualmente — recurren a Auth.js (anteriormente NextAuth.js) para integración de proveedores OAuth, manejo de sesión/JWT, y persistencia basada en adaptadores, porque obtener rotación de tokens, protección CSRF, y particularidades de proveedores bien desde cero es mucho área superficial para poseer.

Cómo decirlo "Las verificaciones de auth a nivel de proxy son para UX y redirecciones; la verificación de seguridad real ocurre otra vez dentro de cada Server Component, acción, y route handler, porque esos son independientemente alcanzables — y en la práctica uso Auth.js en lugar de crear sesiones manualmente."

19 · Rendimiento y Turbopack

Núcleo: Turbopack ahora es estable y el empaquetador por defecto tanto para next dev como next build en Next 16 — sin flag necesario para optar por ello. Es un empaquetador desde cero, basado en Rust, construido para computación incremental, y los números principales son aproximadamente 2–5x compilaciones de producción más rápidas y hasta 10x Fast Refresh más rápido comparado con webpack en aplicaciones grandes. Aún puedes optar por salir a webpack con --webpack si un proyecto depende de un plugin específico de webpack que no ha sido portado.

React Compiler es estable y opt-in vía reactCompiler: true en next.config.ts. Analiza estáticamente el código del componente en tiempo de compilación e inserta memorización automáticamente — el useMemo/useCallback/React.memo que habrías escrito manualmente para evitar re-renderizados innecesarios se generan por ti, lo que tanto reduce boilerplate como captura oportunidades de memorización que un desarrollador probablemente omitiría o haría sutilmente mal.

Core Web Vitals — LCP (carga), INP (interactividad, reemplazó FID), CLS (estabilidad visual) — permanecen como el marco estándar para rendimiento de usuario real, y la mayoría de lo que se cubre en otra parte de esta guía (Prerenderizado Parcial para shells rápidos, next/image para CLS, streaming para tiempo de carga percibido) está a su servicio.

Next 16 eliminó la métrica "First Load JS" del output de compilación — era un proxy aproximado para el tamaño del bundle que no contaba bien para streaming, matices de división de código, o Cache Components. El reemplazo recomendado es medir lo que los usuarios realmente experimentan: Lighthouse para datos de laboratorio, Vercel Analytics (u otra herramienta RUM) para datos de campo.

Dos mecanismos adicionales de rendimiento de navegación que vale la pena nombrar: la deduplicación de layouts significa que un layout compartido no se re-obtiene/re-renderiza en cada navegación entre rutas hermanas — el router reconoce que no ha cambiado y lo reutiliza. La precarga incremental significa que el router precarga solo lo que se necesita para renderizar la próxima navegación probable (no todo el árbol de páginas ansiosamente), manteniendo el tráfico de precarga proporcional a lo que el usuario probablemente hará clic.

Cómo decirlo "Turbopack es el defecto ahora — estable, varias veces más rápidas las compilaciones y Fast Refresh — React Compiler automatiza la memorización si opto por ello, y como Next 16 eliminó First Load JS del output de compilación mido rendimiento real con Lighthouse y datos de campo en lugar de un proxy de compilación."

20 · Testing y despliegue

Núcleo: los Client Components se testean como cualquier componente React — Jest o Vitest con React Testing Library, renderizando el componente, interactuando con él, y asertando sobre el DOM. Son unidades aisladas sin dependencia del servidor, así que esta capa es rápida y barata.

Los Server Components no pueden testearse unitariamente de la misma manera — no hay una historia simple de "renderirlo en jsdom" para un componente que está destinado a ejecutarse en un entorno servidor, esperar datos, y producir un payload RSC. La respuesta práctica es empujar la cobertura de Server Components un nivel más arriba, hacia pruebas de integración o extremo a extremo (Playwright es la elección común) que realmente ejecutan la aplicación — enrutamiento real, obtención de datos real (contra una base de datos de prueba o capa de red mockeada) — y asertar sobre la página renderizada, en lugar de intentar montar un Server Component de forma aislada.

La forma de despliegue es una elección de arquitectura real, no una idea posterior: output: "export" produce una exportación completamente estática — HTML/CSS/JS plano sin servidor Node.js requerido, desplegable a cualquier host estático o CDN — pero renuncia a cualquier cosa que necesite un servidor vivo: Server Actions, Route Handlers que hacen trabajo de servidor real, ISR/revalidación bajo demanda, y las APIs de solicitud asíncronas que dependen de una solicitud real. La alternativa es desplegar a una plataforma capaz de Node.js o Edge (Vercel, o un servidor Node auto-hospedado) que pueda ejecutar el conjunto completo de características.

Variables de entorno: cualquier cosa que necesite llegar al navegador debe tener el prefijo NEXT_PUBLIC_ — Next las inlinea en tiempo de compilación al bundle del cliente. Todo lo demás permanece solo-servidor por defecto y nunca se expone al cliente. El viejo mecanismo serverRuntimeConfig/publicRuntimeConfig de la era del Pages Router ya no existe — las variables de entorno son el único mecanismo soportado ahora.

Fundamentos de seguridad: Next realiza una verificación de encabezado Origin en solicitudes de Server Actions como protección CSRF — una solicitud cuyo Origin no coincide con el origen esperado del despliegue se rechaza antes de que tu código de acción siquiera se ejecute. El paquete server-only (de la sección de Server/Client Components) protege contra un módulo solo-servidor — uno que lee secretos o habla con una base de datos — que termine accidentalmente en un bundle del cliente. Y la disciplina general es: nunca pases un secreto como prop a un Client Component, nunca registres un secreto donde herramientas visibles por el cliente puedan capturarlo, y trata NEXT_PUBLIC_ como una puerta de un solo sentido — cualquier cosa con ese prefijo debe tratarse como pública para siempre.

Cómo decirlo "Los Client Components obtienen pruebas unitarias rápidas con RTL; los Server Components obtienen cobertura de integración/e2e con Playwright ya que no hay una manera significativa de renderizarlos unitariamente; y elijo exportación estática versus un servidor Node vivo basado en si la aplicación realmente necesita Server Actions o revalidación bajo demanda."