50 Reglas de React y Next.js
Reglas esenciales para crear aplicaciones de producción con React 19 y Next.js. Cubre arquitectura, rendimiento, obtención de datos, seguridad y prácticas recomendadas del ecosistema.
Fundamentos y Arquitectura (Reglas 1-10)
1. Por defecto, utiliza Server Components. En Next.js App Router (y ecosistemas de React 19), los componentes son Server Components por defecto. Solo agrega "use client" cuando necesites interactividad, state o APIs del navegador. Esto minimiza el JavaScript del lado del cliente y mejora los tiempos de carga.
2. Usa el App Router exclusivamente en nuevos proyectos. El Pages Router es heredado. El App Router proporciona layouts anidados, streaming, mejor obtención de datos y soporte completo de React Server Components.
3. Domina los límites cliente/servidor. Los Server Components pueden renderizar Client Components (y pasar props serializables). Los Client Components no pueden importar Server Components. Nunca pases valores no serializables (funciones, clases) a través del límite.
- Serializables: strings, números, booleanos, arrays, objetos planos,
Date,Map,Set - No serializables: funciones, instancias de clases, nodos del DOM, elementos React con closures
4. Coloca la lógica junto con los componentes. Mantén la obtención de datos, estilos y utilidades relacionadas cerca de los componentes que los usan. Haz que los efectos secundarios sean predecibles y del lado del servidor cuando sea posible.
app/
dashboard/
page.tsx # Ruta
dashboard-chart.tsx # Componente usado solo aquí
actions.ts # Server Actions para esta ruta
loading.tsx # UI de carga
5. Prefiere la composición sobre herencia o state complejo. Construye componentes pequeños, enfocados y reutilizables. Eleva el state solo cuando sea necesario. Favorece el state local y los datos gestionados por el servidor sobre stores globales.
// Bien: composición con children
<Card>
<CardHeader>Title</CardHeader>
<CardBody>{content}</CardBody>
</Card>
// Evita: herencia o mega-componentes
class SpecialCard extends Card { ... }6. Adopta TypeScript por defecto. Usa tipado estricto, genéricos para componentes y hooks, y la transformación JSX moderna. TypeScript detecta errores temprano y mejora la mantenibilidad en aplicaciones grandes.
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}7. Piensa en Servidor + Cliente, no solo en "componentes". Diseña con un modelo mental de renderización del servidor primero, luego agrega interactividad del cliente solo cuando sea necesario. El rendimiento es una restricción de diseño, no un pensamiento tardío.
| Capa | Maneja | Ejemplos |
|---|---|---|
| Server Components | Obtención de datos, contenido estático, SEO | Páginas de productos, dashboards, artículos |
| Client Components | Interactividad, state, APIs del navegador | Formularios, modales, dropdowns, animaciones |
8. Usa los nuevos hooks de React 19 sabiamente. Aprovecha las nuevas APIs para patrones comunes:
| Hook | Propósito |
|---|---|
useActionState | Estado de acción de formulario con pending, error, data |
useOptimistic | Actualizaciones de UI optimistas durante acciones async |
useFormStatus | Accede al estado pending del formulario padre |
use() | Desenvuelve promesas y contexto condicionalmente |
9. Adopta React Server Components (RSCs) como base. Los RSCs permiten acceso directo al backend, cero JS de cliente para partes estáticas, y streaming mejor. Evita forzar todo al lado del cliente.
Beneficios de los RSCs:
- Acceso directo a base de datos/API sin una capa de API
- Cero JavaScript enviado para componentes solo de servidor
- Separación de código automática a nivel de componente
- Streaming y renderización progresiva
10. Sigue una lista de verificación de producción antes de desplegar. Ejecuta next build, prueba con next start, optimiza imágenes y fuentes, y verifica Core Web Vitals.
# Lista de verificación pre-despliegue
next build # Busca errores de compilación
next start # Prueba el modo de producción localmente
npx lighthouse # Comprueba métricas de rendimientoRendimiento y Optimización (Reglas 11-25)
11. Elimina cascadas en la obtención de datos. Usa fetches paralelos, límites de Suspense y carga de datos del lado del servidor para evitar solicitudes bloqueantes secuenciales.
// Mal: secuencial (cascada)
const user = await getUser(id);
const posts = await getPosts(user.id);
// Bien: paralelo
const [user, posts] = await Promise.all([
getUser(id),
getPosts(id),
]);12. Confía en el Compilador de React (React Forget). En React 19, el compilador memoiza automáticamente donde es seguro. Escribe código más simple sin exceso de useMemo y useCallback a menos que el profiling muestre problemas.
// Antes: memoización manual en todas partes
const filtered = useMemo(() => items.filter(predicate), [items, predicate]);
const handleClick = useCallback(() => doThing(id), [id]);
// Después (con Compilador de React): solo escribe
const filtered = items.filter(predicate);
const handleClick = () => doThing(id);13. Usa Suspense para obtención de datos declarativa y estados de carga. Envuelve secciones dinámicas con Suspense. Combina con streaming para renderización progresiva y mejor UX.
<Suspense fallback={<Skeleton />}>
<ProductDetails id={id} />
</Suspense>14. Aprovecha Partial Prerendering (PPR). En Next.js 15+, renderiza estáticamente la estructura de una página mientras transmite las partes dinámicas. Este es lo mejor de ambos mundos para muchas aplicaciones.
// La estructura estática se renderiza instantáneamente
// Las partes dinámicas se transmiten a medida que se resuelven
export default function Page() {
return (
<div>
<Header /> {/* Estático */}
<Suspense fallback={<Skeleton />}>
<LiveFeed /> {/* Dinámico, transmitido */}
</Suspense>
<Footer /> {/* Estático */}
</div>
);
}15. Elige la estrategia de renderización adecuada.
| Estrategia | Mejor para | Compensación |
|---|---|---|
| Estático (SSG) | Contenido público, páginas de marketing | Obsoleto hasta reconstruir |
| ISR | Publicaciones de blog, páginas de productos | Obsoleto dentro de la ventana de revalidación |
| SSR | Datos específicos del usuario, datos en tiempo real | TTFB más lento, más carga en servidor |
| CSR | Herramientas internas, dashboards protegidos por auth | Sin SEO, carga inicial más lenta |
16. Optimiza imágenes agresivamente. Usa el componente Image de Next.js con sizes, formatos modernos (WebP/AVIF) y lazy loading. Comprime assets y evita cascadas de fuentes/CDN de terceros.
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
sizes="(max-width: 768px) 100vw, 1200px"
priority // Por encima de la línea de flotación? Usa priority
/>17. Implementa caché y revalidación adecuadas. Usa opciones de caché fetch, revalidatePath/revalidateTag, o la directiva use cache. Entiende las capas de caché de Next.js:
| Capa de Caché | Qué | Duración |
|---|---|---|
| Memoización de Solicitudes | Desduplica fetches idénticos en un render | Una solicitud |
| Caché de Datos | Cachea respuestas fetch | Hasta revalidar |
| Caché de Ruta Completa | Cachea páginas renderizadas completas | Hasta revalidar |
| Caché de Enrutador | Caché de ruta del lado del cliente | Basado en sesión |
18. Minimiza el tamaño del bundle del cliente. Mantén los Client Components pequeños y enfocados. Usa importaciones dinámicas para código del cliente pesado o condicional.
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("./chart"), {
loading: () => <Skeleton className="h-64" />,
});19. Evita re-renderizados innecesarios. Con el Compilador esto es menos manual, pero aún así haz profiling con React DevTools. Usa keys correctamente en listas. No uses el índice de array como key para listas dinámicas.
20. Transmite e hidrata selectivamente. Usa límites de Suspense para habilitar hidratación selectiva y mejor rendimiento percibido. Los usuarios pueden interactuar con partes hidratadas mientras otras secciones siguen cargando.
21. Optimiza para Core Web Vitals.
| Métrica | Objetivo | Cómo |
|---|---|---|
| LCP (Largest Contentful Paint) | Menos de 2.5s | Imágenes con priority, precarga de fuentes, SSR |
| CLS (Cumulative Layout Shift) | Menos de 0.1 | Establece dimensiones de imagen, evita layout shifts |
| INP (Interaction to Next Paint) | Menos de 200ms | Bundles del cliente pequeños, useTransition |
22. Usa Turbopack en desarrollo. Ahora es estable y proporciona HMR y compilaciones significativamente más rápidas en Next.js 15+.
next dev --turbopack23. Comprime y carga de forma perezosa assets no críticos. Usa importaciones dinámicas para rutas y componentes, formatos de imagen modernos, y evita el overhead de CSS-in-JS en tiempo de ejecución donde sea posible (favorece Tailwind o CSS Modules).
24. Monitorea y haz profiling en entornos similar a producción. Usa métricas integradas de Next.js, Lighthouse y herramientas como Vercel Analytics para detectar problemas de rendimiento de usuarios reales.
25. Reduce JavaScript enviado al cliente. Apunta a bundles más pequeños a través de RSCs, separación de código y tree-shaking. Los Server Components pueden reducir JS del cliente en 30-50% en muchos casos.
Obtención de Datos, State y Formularios (Reglas 26-35)
26. Obtén datos en el servidor por defecto. Usa Server Components o Route Handlers. Evita la obtención de datos del lado del cliente para datos iniciales cuando sea posible.
// Server Component: acceso directo a datos
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await db.product.findUnique({ where: { id } });
return <ProductDetails product={product} />;
}27. Usa Server Actions para mutaciones. Simplifican formularios, manejan transiciones async, estados pending y errores automáticamente en React 19 y Next.js. Prefierelas sobre rutas de API tradicionales para muchos casos de uso.
"use server";
export async function createPost(formData: FormData) {
const title = formData.get("title") as string;
await db.post.create({ data: { title } });
revalidatePath("/posts");
}28. Prefiere TanStack Query (o SWR) para state del servidor del lado del cliente. Combina con obtención de datos del servidor para caché óptimo y sincronización. Usa obtención del cliente solo para datos que cambian frecuentemente después de la carga inicial.
29. Maneja formularios con Actions y actualizaciones optimistas. Usa useOptimistic y useActionState para UX suave sin boilerplate manual de carga y errores.
const [optimisticItems, addOptimistic] = useOptimistic(
items,
(state, newItem: Item) => [...state, { ...newItem, pending: true }]
);30. Mantén el state local a menos que sea compartido. Evita usar excesivamente gestores de state globales. Eleva el state solo cuando sea realmente necesario.
| Tipo de State | Solución |
|---|---|
| UI state (un componente) | useState |
| State local complejo | useReducer |
| Compartido entre hermanos | Eleva el state al padre |
| Compartido en toda la app | Zustand o Context |
| Datos del servidor | Server Components, SWR, TanStack Query |
| State de URL | useSearchParams |
31. Desduplica solicitudes automáticamente. Next.js y React 19 manejan muchos casos a través de memoización de solicitudes. Asegura claves de caché consistentes.
32. Usa async/await naturalmente en Server Components. La obtención de datos es síncrona durante el renderizado en el servidor. Los Server Components async son de primera clase en React 19.
33. Valida entradas en el servidor. Nunca confíes en datos del cliente. Usa esquemas (Zod, etc.) en Server Actions o Route Handlers.
"use server";
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
});
export async function createUser(formData: FormData) {
const result = schema.safeParse(Object.fromEntries(formData));
if (!result.success) return { error: result.error.flatten() };
// ... crear usuario
}34. Maneja límites async con Suspense y límites de error. Proporciona fallbacks elegantes para carga y errores.
<ErrorBoundary fallback={<ErrorMessage />}>
<Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>35. Evita acceso directo a APIs del navegador en código compartido. Verifica "use client" y el entorno cuando sea necesario. Usa guardias typeof window !== "undefined" raramente, y prefiere dividir en módulos de servidor y cliente.
Seguridad y Prácticas Recomendadas (Reglas 36-45)
36. Nunca expongas secretos al cliente. Usa variables de entorno solo para servidor (sin prefijo NEXT_PUBLIC_ para datos sensibles). Proxya llamadas a API a través de Server Actions o Route Handlers.
# .env
STRIPE_SECRET_KEY=sk_live_... # Solo servidor
NEXT_PUBLIC_STRIPE_KEY=pk_live_... # Seguro para cliente37. Asegura Server Actions. Protege contra llamadas no autorizadas con verificaciones de auth adecuadas. Mantén dependencias actualizadas. Las Server Actions son puntos finales HTTP públicos, así que siempre verifica el usuario.
"use server";
import { auth } from "@/lib/auth";
export async function deletePost(id: string) {
const session = await auth();
if (!session) throw new Error("Unauthorized");
// ... eliminar post
}38. Implementa autenticación y autorización adecuadas. Usa cookies httpOnly, Secure, SameSite para sesiones. Valida en el servidor. Nunca confíes solo en verificaciones del lado del cliente.
39. Desinfecta y valida todas las entradas. Prevén XSS, CSRF (las Server Actions tienen protección CSRF integrada a través de verificaciones de origen), y ataques de inyección.
40. Sigue una lista de verificación de seguridad de producción:
- Actualiza Next.js y React a las versiones parchadas más recientes
- Habilita encabezados de seguridad (CSP, X-Frame-Options, etc.)
- Audita dependencias con
npm audit - Nunca registres datos sensibles
- Usa HTTPS en todas partes
41. Usa carpetas privadas (_folder) en App Router. Mantén archivos sin ruta (utils, componentes) fuera del sistema de enrutamiento.
app/
dashboard/
_components/ # No es una ruta
_lib/ # No es una ruta
page.tsx # Ruta
42. Organiza la estructura del proyecto de manera escalable.
src/
app/ # Rutas y layouts
components/ # Componentes de UI compartidos
ui/ # Primitivos de shadcn/ui
lib/ # Utilidades, constantes, tipos
hooks/ # Custom hooks
actions/ # Server Actions compartidas
43. Escribe componentes pequeños con una única responsabilidad. Más fáciles de probar, optimizar y mantener. Un componente debe hacer una cosa bien. Si estás desplazándote para entenderlo, divídelo.
44. Prueba exhaustivamente. Pruebas unitarias de hooks y componentes, pruebas de integración de flujos de datos, y pruebas end-to-end de rutas críticas. Incluye escenarios de hidratación y streaming.
| Tipo de Prueba | Herramienta | Qué Probar |
|---|---|---|
| Unit | Vitest | Hooks, utilidades, Server Actions |
| Component | React Testing Library | Interacciones del usuario, renderizado |
| E2E | Playwright | Flujos críticos del usuario, pagos |
45. Documenta y revisa límites. Marca claramente "use client", Server Actions y lógica de obtención de datos en revisiones de código. Haz la división cliente/servidor obvia.
Avanzado y Ecosistema (Reglas 46-50)
46. Integra características de IA de manera reflexiva. Proxya llamadas a través de tu servidor (nunca expongas claves de API del lado del cliente). Usa Server-Sent Events o el SDK de Vercel AI para streaming. Cachea agresivamente y rastrea costos.
// Server Action con AI SDK
"use server";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function chat(messages: Message[]) {
const result = await streamText({
model: openai("gpt-4o"),
messages,
});
return result.toDataStreamResponse();
}47. Aprovecha metadata y herramientas de SEO. Usa la API metadata de Next.js, genera sitemaps y robots.txt dinámicamente, y asegura contenido renderizado en servidor para rastreadores.
export const metadata: Metadata = {
title: "Product Name",
description: "Product description for SEO",
openGraph: { images: ["/og-image.png"] },
};48. Mantente actualizado pero estable. Fija versiones mayores donde sea necesario. Prueba el Compilador de React y nuevos comportamientos de caché incrementalmente. Lee los changelogs antes de actualizar.
49. Prioriza accesibilidad y diseño inclusivo. Usa HTML semántico, ARIA donde sea necesario, y prueba con lectores de pantalla. Presta especial atención al contenido dinámico transmitido que puede no anunciarse correctamente.
Verificaciones clave de accesibilidad:
- Todas las imágenes tienen texto
altsignificativo - Los formularios tienen labels asociadas
- El contraste de color cumple WCAG AA (4.5:1 para texto)
- La navegación por teclado funciona para todos los elementos interactivos
- Gestión de foco en cambios de ruta
50. Trata el rendimiento y la mantenibilidad como disciplinas continuas. Haz profiling regularmente, refactoriza hacia patrones del lado del servidor, y usa recursos de la comunidad. El rendimiento no es una tarea única. Es una práctica continua.
Preguntas Frecuentes
¿Cuándo debes agregar "use client" a un componente?
- Solo cuando el componente necesita interactividad, state (
useState,useReducer), o APIs del navegador - Los Server Components son el predeterminado en App Router -- no agregues
"use client"de manera preventiva - Si un componente solo renderiza datos o contenido estático, mantenlo como un Server Component
¿Qué valores puedes pasar a través del límite servidor/cliente?
- Serializables (OK): strings, números, booleanos, arrays, objetos planos,
Date,Map,Set - No serializables (generarán error): funciones, instancias de clases, nodos del DOM, elementos React con closures
¿Cómo eliminas cascadas de obtención de datos?
Usa Promise.all para ejecutar fetches independientes en paralelo:
const [user, posts] = await Promise.all([
getUser(id),
getPosts(id),
]);¿Qué cambia el Compilador de React (React Forget) sobre la memoización?
- El compilador memoiza automáticamente donde es seguro en React 19
- Ya no necesitas
useMemoyuseCallbackmanual en la mayoría de casos - Solo agrega memoización manual si el profiling revela un problema de rendimiento específico
Gotcha: ¿Qué sucede si usas un índice de array como key en una lista dinámica?
- React no puede distinguir elementos correctamente cuando se agregan, quitan o reordenan elementos
- Esto lleva a re-renderizados incorrectos, state obsoleto y bugs sutiles
- Siempre usa un identificador estable y único (p. ej., ID de base de datos) como key
¿Qué es Partial Prerendering (PPR) y cuándo deberías usarlo?
- PPR renderiza estáticamente la estructura de la página mientras transmite partes dinámicas vía Suspense
- Disponible en Next.js 15+
- Mejor para páginas con contenido tanto estático como dinámico (p. ej., un dashboard con header estático y datos en vivo)
¿Cómo eliges entre SSG, ISR, SSR y CSR?
- SSG: contenido público, que raramente cambia (páginas de marketing)
- ISR: contenido que cambia periódicamente (publicaciones de blog, páginas de productos)
- SSR: datos específicos del usuario o datos en tiempo real
- CSR: herramientas internas protegidas por auth donde el SEO no es necesario
¿Cómo tipas un Server Component que recibe params en Next.js?
type Props = {
params: Promise<{ id: string }>;
};
export default async function Page({ params }: Props) {
const { id } = await params;
// ...
}¿Cómo debes tipar el valor de retorno de useActionState en TypeScript?
Define un tipo de state explícito para el valor de retorno de la acción:
type FormState = {
error?: string;
data?: { id: string };
};
const [state, action, isPending] = useActionState<FormState, FormData>(
submitAction,
{ error: undefined, data: undefined }
);Gotcha: ¿Por qué nunca debes exponer variables prefijadas con NEXT_PUBLIC_ para secretos?
- Cualquier variable de entorno prefijada con
NEXT_PUBLIC_se integra en el bundle del cliente en tiempo de compilación - Es visible para cualquiera que inspeccione la fuente de la página o solicitudes de red
- Las claves de API, URLs de base de datos y tokens deben usar variables de entorno solo para servidor (sin prefijo)
¿Cuáles son las cuatro capas de caché en Next.js y qué cachea cada una?
- Memoización de Solicitudes: desduplica fetches idénticos dentro de un paso de renderizado
- Caché de Datos: cachea respuestas fetch hasta revalidar
- Caché de Ruta Completa: cachea páginas renderizadas completas hasta revalidar
- Caché de Enrutador: caché de ruta del lado del cliente que dura la sesión
¿Cómo validas entradas del lado del servidor con Zod en una Server Action?
"use server";
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
});
export async function createUser(formData: FormData) {
const result = schema.safeParse(Object.fromEntries(formData));
if (!result.success) return { error: result.error.flatten() };
}¿Cuál es el propósito de useOptimistic y cuándo deberías usarlo?
useOptimisticproporciona retroalimentación instantánea de UI mientras una acción async (p. ej., Server Action) está en progreso- Si la acción falla, React automáticamente revierte al state anterior
- Úsalo para likes, saves, deletes y cualquier mutación donde la velocidad percibida importa
Tarjeta de Referencia Rápida
| Categoría | Idea Clave |
|---|---|
| Arquitectura | Server Components primero, Client Components solo para interactividad |
| Rendimiento | Fetching paralelo, streaming, PPR, Turbopack |
| Datos | Server Actions para mutaciones, Suspense para carga |
| State | State local por defecto, Zustand para compartir, URL para compartible |
| Formularios | useActionState + validación Zod + useOptimistic |
| Seguridad | Nunca expongas secretos, valida en servidor, auth en cada acción |
| Pruebas | Unit + component + E2E cubriendo rutas críticas |
| SEO | API Metadata, renderizado del servidor, datos estructurados |