Routing y Gestión de Datos
5. Regla sobre Routing (App Router)
Regla Principal
Regla: El enrutamiento en aplicaciones Next.js debe implementarse utilizando las convenciones del App Router.
- Rutas basadas en Directorios: Cada directorio dentro de
app/define un segmento de ruta. Un archivopage.tsxdentro de un directorio hace que ese segmento sea públicamente accesible.- Layouts Anidados: Usar
layout.tsxpara definir UI compartida entre múltiples páginas dentro de un segmento de ruta y sus descendientes. Los layouts anidados preservan el estado y evitan re-renders innecesarios.- Archivos Especiales: Utilizar los archivos con nombres reservados (
page.tsx,layout.tsx,loading.tsx,error.tsx,not-found.tsx,template.tsx,default.tsx,route.ts) según su propósito definido por Next.js.- Rutas Dinámicas: Usar corchetes (
[folderName]) para crear segmentos de ruta dinámicos. Usar doble corchete ([[...folderName]]) para rutas catch-all opcionales y corchetes con puntos suspensivos ([...folderName]) para rutas catch-all obligatorias.- Generación de Rutas Dinámicas (SSG): Para rutas dinámicas que se quieren generar estáticamente en build time, exportar una función
generateStaticParamsdesdepage.tsxolayout.tsx.- Navegación: Usar el componente
<Link>denext/linkpara la navegación del lado del cliente entre rutas. Usar el hookuseRouterdenext/navigationpara la navegación programática en Client Components.- Grupos de Rutas: Usar paréntesis
(folderName)para organizar rutas o crear layouts específicos sin afectar la URL final.- Parallel Routes y Intercepting Routes: Utilizar estas características avanzadas (
@folder,(.)folder,(..)folder, etc.) cuando se necesiten vistas múltiples independientes en la misma URL o para mostrar una ruta dentro de otra (ej. modales).
Contexto y Justificación
El App Router es el sistema de enrutamiento moderno de Next.js, diseñado para Server Components y React Server Components. Sus convenciones permiten crear layouts complejos, manejar estados de carga y error de forma granular, y optimizar la navegación y el renderizado. Seguir estas convenciones es esencial para el correcto funcionamiento y aprovechamiento del framework.
Ejemplos y Contraejemplos
- Correcto:
// --- app/products/[productId]/page.tsx ---
import { Metadata } from 'next';
type Props = {
params: { productId: string };
};
// Función para generar metadatos dinámicos
export async function generateMetadata({ params }: Props): Promise<Metadata> {
// const product = await fetchProduct(params.productId);
return { title: `Producto ${params.productId}` };
}
// Función para generar rutas estáticas en build time
export async function generateStaticParams() {
// const products = await fetchAllProductIds(); // [{ productId: '1' }, { productId: '2' }]
// return products.map((product) => ({ productId: product.id }));
return [{ productId: '1' }, { productId: '2' }]; // Ejemplo
}
export default async function ProductPage({ params }: Props) {
// const product = await fetchProduct(params.productId);
return <h1>Detalle del Producto {params.productId}</h1>;
}
// --- components/ui/Navigation.tsx (Client Component) ---
'use client';
import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
export function Navigation() {
const pathname = usePathname();
const router = useRouter();
const goToHome = () => router.push('/');
return (
<nav>
<Link href="/about" className={pathname === '/about' ? 'active' : ''}>
Acerca de
</Link>
<button onClick={goToHome}>Ir a Inicio</button>
</nav>
);
} - Incorrecto: Usar
<a>en lugar de<Link>para navegación interna. Intentar definir rutas fuera de la estructuraapp/. UsaruseRouterdenext/router(del Pages Router) en App Router (usarnext/navigation). Poner lógica de página compleja enlayout.tsx.
Cuándo Aplicar
Siempre al definir rutas y navegación en aplicaciones Next.js con App Router.
Cuándo Evitar o Flexibilizar
Las convenciones de nombrado de archivos y estructura de directorios del App Router son obligatorias. Las características avanzadas como Parallel o Intercepting Routes deben usarse cuando el caso de uso lo justifique.
6. Regla sobre Data Fetching
Regla Principal
Regla: La obtención de datos (data fetching) en Next.js App Router debe realizarse utilizando los mecanismos recomendados, principalmente
fetchextendido por Next.js en Server Components.
- Server Components: Realizar el data fetching directamente en Server Components (incluyendo
page.tsx,layout.tsx) usandoasync/awaitconfetch. Next.js extiendefetchpara permitir el cacheo automático y la revalidación.- Cacheo y Revalidación: Controlar el comportamiento del cache de
fetchusando el objetonexten las opciones:{ cache: 'force-cache' }(default, SSG),{ cache: 'no-store' }(SSR, siempre dinámico),{ next: { revalidate: 3600 } }(ISR, revalida cada hora).- Client Components: Para data fetching en Client Components (ej. datos que cambian frecuentemente por interacción del usuario), utilizar librerías como SWR (
useSWR) o React Query (useQuery). No hacer fetch directamente conuseEffectsin una librería de gestión de estado de servidor.- Route Handlers (API Routes): Definir endpoints API en
app/api/.../route.tspara que los Client Components (o clientes externos) puedan hacer fetch de datos o realizar mutaciones.- Funciones Server: Utilizar Server Actions (
'use server') para realizar mutaciones de datos directamente desde Client Components sin necesidad de crear API Routes explícitas para ello, especialmente para formularios.- Manejo de Errores: Implementar manejo de errores adecuado para las operaciones de fetch (ej.
try/catchen Server Components, manejo de estado de error enuseSWR/useQuery).- Loading UI: Utilizar
loading.tsxpara mostrar UI de carga instantánea mientras los datos se cargan en Server Components.
Contexto y Justificación
Next.js optimiza el data fetching en Server Components integrándolo con su sistema de renderizado y cacheo. Usar
fetchextendido permite un control granular sobre SSG, SSR e ISR. Para el cliente, librerías como SWR/React Query simplifican la gestión del estado de datos remotos (cache, revalidación, errores, carga). Server Actions simplifican las mutaciones desde el cliente. Un data fetching incorrecto puede llevar a mal rendimiento, datos obsoletos o falta de manejo de errores.
Ejemplos y Contraejemplos
- Correcto (Server Component con ISR, Client Component con SWR):
// --- app/posts/[slug]/page.tsx (Server Component con ISR) ---
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 60 }, // Revalida cada 60 segundos (ISR)
});
if (!res.ok) return undefined; // O lanzar error para not-found.tsx
return res.json();
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
if (!post) return <div>Post no encontrado</div>; // O usar notFound()
return <h1>{post.title}</h1>;
}
// --- components/features/RealTimeData.tsx (Client Component con SWR) ---
'use client';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then((res) => res.json());
export function RealTimeData() {
const { data, error, isLoading } = useSWR('/api/realtime', fetcher, {
refreshInterval: 5000, // Refresca cada 5 segundos
});
if (error) return <div>Error al cargar datos</div>;
if (isLoading) return <div>Cargando...</div>;
return <div>Dato en tiempo real: {data?.value}</div>;
}
// --- app/api/realtime/route.ts (Route Handler) ---
import { NextResponse } from 'next/server';
export async function GET() {
// Lógica para obtener el dato en tiempo real
const value = Math.random();
return NextResponse.json({ value });
} - Incorrecto: Usar
useEffect(() => { fetch(...) }, [])en un Client Component sin SWR/React Query. Hacer fetch de datos sensibles directamente en Client Components. No especificar opciones de cacheo/revalidación enfetchen Server Components cuando se requiere un comportamiento específico (SSR/ISR). No manejar estados de carga o error.
Cuándo Aplicar
Siempre que se necesite obtener datos de fuentes externas (APIs, bases de datos) para renderizar páginas o componentes.
Cuándo Evitar o Flexibilizar
La elección entre fetching en Server Component vs. Client Component (con SWR/RQ) depende de la naturaleza de los datos (estáticos vs. dinámicos, sensibles vs. públicos) y la experiencia de usuario deseada.
7. Regla sobre API Routes (Route Handlers)
Regla Principal
Regla: La creación de endpoints de backend dentro de la aplicación Next.js debe realizarse utilizando Route Handlers.
- Ubicación y Nomenclatura: Crear un archivo
route.tsdentro del directorioapp/correspondiente a la ruta deseada (ej.app/api/users/route.tspara/api/users).- Exportar Funciones HTTP: Exportar funciones nombradas según el método HTTP que manejan (
GET,POST,PUT,DELETE, etc.). Estas funciones reciben un objetoRequestcomo argumento.- Acceso al Backend: Los Route Handlers se ejecutan en el servidor y pueden acceder directamente a bases de datos, servicios externos y lógica de backend.
- Respuestas: Utilizar la clase
NextResponsedenext/serverpara construir y retornar las respuestas, permitiendo establecer el status code, headers y el body (usandoNextResponse.json()).- Tipado: Tipar los parámetros de la ruta (si es dinámica) y el cuerpo de la petición (si lo hay) usando interfaces/tipos.
- Seguridad: Asegurar que los Route Handlers validen entradas, manejen errores y apliquen autenticación/autorización si es necesario (ej. verificando tokens, usando
next-auth).- Alternativa (Server Actions): Para mutaciones de datos iniciadas desde formularios en Client Components, considerar el uso de Server Actions como alternativa más directa a la creación de API Routes específicas para POST/PUT/DELETE.
Contexto y Justificación
Los Route Handlers permiten construir un backend API directamente dentro del proyecto Next.js, simplificando el desarrollo full-stack. Son ideales para que los Client Components obtengan datos dinámicos o realicen mutaciones. Usar
NextResponseasegura la compatibilidad con el entorno Edge Runtime (si se usa) y facilita la construcción de respuestas HTTP correctas.
Ejemplos y Contraejemplos
- Correcto:
// --- app/api/users/[userId]/route.ts ---
import { NextResponse } from 'next/server';
import { db } from '@/lib/db'; // Asume un cliente de BD en lib
// GET /api/users/{userId}
export async function GET(
request: Request, // El objeto Request no es necesario aquí, pero se recibe
{ params }: { params: { userId: string } }
) {
try {
const userId = params.userId;
// Lógica para obtener usuario de la BD
const user = await db.user.findUnique({ where: { id: userId } });
if (!user) {
return new NextResponse(JSON.stringify({ message: 'Usuario no encontrado' }), {
status: 404,
headers: { 'Content-Type': 'application/json' },
});
}
// Excluir contraseña antes de devolver
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { passwordHash, ...userData } = user;
return NextResponse.json(userData);
} catch (error) {
console.error('Error fetching user:', error);
return new NextResponse(JSON.stringify({ message: 'Error interno del servidor' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
// --- app/api/users/route.ts ---
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { z } from 'zod'; // Usando Zod para validación
const userSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
});
// POST /api/users
export async function POST(request: Request) {
try {
const body = await request.json();
const validation = userSchema.safeParse(body);
if (!validation.success) {
return NextResponse.json({ message: 'Datos inválidos', errors: validation.error.errors }, { status: 400 });
}
// Lógica para crear usuario en BD
const newUser = await db.user.create({ data: validation.data });
// ... (excluir contraseña)
return NextResponse.json(newUser, { status: 201 });
} catch (error) {
// ... manejo de error 500 ...
return NextResponse.json({ message: 'Error interno' }, { status: 500 });
}
} - Incorrecto: Realizar lógica de renderizado de UI dentro de un Route Handler. Exponer secretos o realizar operaciones inseguras. No validar los datos del body en POST/PUT. No manejar errores adecuadamente. Usar el antiguo
pages/apijunto conapp/apide forma inconsistente.
Cuándo Aplicar
Cuando se necesita exponer endpoints API desde la aplicación Next.js para ser consumidos por Client Components, aplicaciones móviles u otros servicios.
Cuándo Evitar o Flexibilizar
Si la aplicación no necesita exponer una API (ej. sitio completamente estático) o si las mutaciones se manejan exclusivamente con Server Actions.
8. Regla sobre Componentes React (Server vs Client)
Regla Principal
Regla: La IA debe diferenciar claramente y utilizar apropiadamente Server Components y Client Components dentro del App Router.
- Server Components (Default):
- Son el tipo por defecto en
app/.- Deben usarse para: acceso directo a datos/backend, mantener lógica sensible fuera del cliente, reducir el JavaScript enviado al navegador.
- No pueden usar hooks como
useState,useEffect,useReducer,useContext.- No pueden usar manejadores de eventos interactivos como
onClick,onChange.- Client Components (
'use client'):
- Deben marcarse explícitamente con la directiva
'use client'al inicio del archivo.- Deben usarse para: añadir interactividad (event listeners), usar estado y efectos (
useState,useEffect), usar APIs exclusivas del navegador, usar React Context.- Minimizar su Uso: Mover los Client Components tan abajo en el árbol de componentes como sea posible ("leaves") para que la mayor parte de la UI se renderice en el servidor.
- Composición: Pasar Server Components como
childrena Client Components es un patrón válido y recomendado para mantener partes estáticas renderizadas en servidor dentro de un entorno interactivo.- Importaciones: Los Server Components pueden importar Client Components. Los Client Components no pueden importar directamente Server Components (pero pueden recibirlos como props, ej.
children).
Contexto y Justificación
La distinción entre Server y Client Components es fundamental en el App Router de Next.js. Permite optimizar el rendimiento enviando menos JavaScript al cliente y realizando el trabajo pesado (data fetching, renderizado inicial) en el servidor. Usar incorrectamente uno u otro tipo de componente puede llevar a errores, mal rendimiento o limitaciones funcionales.
Ejemplos y Contraejemplos
- Correcto (Composición):
// --- app/dashboard/page.tsx (Server Component) ---
import { ClientWrapper } from '@/components/features/ClientWrapper';
import { ServerInfo } from '@/components/features/ServerInfo';
async function getData() { /* ... fetch data ... */ return { value: 123 }; }
export default async function DashboardPage() {
const data = await getData();
return (
<ClientWrapper>
{/* ServerInfo se renderiza en servidor y se pasa como children */}
<ServerInfo serverData={data.value} />
</ClientWrapper>
);
}
// --- components/features/ServerInfo.tsx (Server Component) ---
// No necesita 'use client'
export function ServerInfo({ serverData }: { serverData: number }) {
// Puede tener lógica compleja o más data fetching si es necesario
return <p>Información del servidor: {serverData}</p>;
}
// --- components/features/ClientWrapper.tsx (Client Component) ---
'use client';
import { useState } from 'react';
export function ClientWrapper({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0);
return (
<div>
<h2>Wrapper Interactivo</h2>
{children} {/* Renderiza el Server Component recibido */}
<button onClick={() => setCount(c => c + 1)}>Clic (Cliente): {count}</button>
</div>
);
} - Incorrecto: Poner
'use client'en un componente que solo muestra datos estáticos. Intentar usaruseStateenapp/page.tsxsin'use client'. Importar un Server Component dentro de un Client Component.
Cuándo Aplicar
Siempre al crear o modificar componentes dentro del App Router.
Cuándo Evitar o Flexibilizar
El modelo Server/Client es la base del App Router. La decisión de dónde colocar
'use client'debe hacerse conscientemente para optimizar el rendimiento y la interactividad.
8.1. Regla sobre Server Actions para Formularios
Regla Principal
Regla: Para el manejo de formularios y mutaciones de datos, la IA debe priorizar el uso de Server Actions como mecanismo principal antes que crear API Routes específicas.
- Server Actions: Utilizar Server Actions (
'use server') para manejar envíos de formularios y mutaciones de datos directamente desde Client Components.- Directiva 'use server': Marcar funciones con
'use server'al inicio para indicar que se ejecutan en el servidor.- Validación de Datos: Implementar validación robusta de datos en Server Actions usando librerías como Zod.
- Manejo de Errores: Proporcionar manejo de errores apropiado y retornar estados de error claros.
- Revalidación: Utilizar
revalidatePathorevalidateTagpara actualizar datos cached después de mutaciones.- FormData Integration: Integrar naturalmente con HTML forms usando
FormDatacomo parámetro.
Contexto y Justificación
Los Server Actions simplifican significativamente el manejo de formularios eliminando la necesidad de crear API Routes específicas para mutaciones. Permiten una integración más directa entre Client Components y lógica del servidor, reduciendo el código boilerplate y mejorando la experiencia de desarrollo. Son especialmente útiles para operaciones CRUD simples y formularios.
Ejemplos y Contraejemplos
- Correcto:
// app/actions.ts
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
const createUserSchema = z.object({
name: z.string().min(1, 'Nombre es requerido'),
email: z.string().email('Email inválido'),
message: z.string().min(10, 'Mensaje debe tener al menos 10 caracteres'),
});
export async function createUser(formData: FormData) {
const rawData = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
};
// Validar datos
const validation = createUserSchema.safeParse(rawData);
if (!validation.success) {
return {
errors: validation.error.flatten().fieldErrors,
};
}
try {
// Lógica de creación de usuario
await saveUserToDatabase(validation.data);
// Revalidar cache
revalidatePath('/users');
return { success: true };
} catch (error) {
return {
errors: {
_form: ['Error al crear usuario. Inténtalo de nuevo.'],
},
};
}
}
export async function deleteUser(userId: string) {
try {
await deleteUserFromDatabase(userId);
revalidatePath('/users');
redirect('/users');
} catch (error) {
throw new Error('Error al eliminar usuario');
}
}
// components/UserForm.tsx
'use client';
import { useActionState } from 'react';
import { createUser } from '@/app/actions';
export function UserForm() {
const [state, formAction] = useActionState(createUser, { errors: {} });
return (
<form action={formAction} className="space-y-4">
<div>
<input
type="text"
name="name"
placeholder="Nombre"
className="w-full p-2 border rounded"
/>
{state.errors?.name && (
<p className="text-red-500 text-sm">{state.errors.name[0]}</p>
)}
</div>
<div>
<input
type="email"
name="email"
placeholder="Email"
className="w-full p-2 border rounded"
/>
{state.errors?.email && (
<p className="text-red-500 text-sm">{state.errors.email[0]}</p>
)}
</div>
<div>
<textarea
name="message"
placeholder="Mensaje"
className="w-full p-2 border rounded"
/>
{state.errors?.message && (
<p className="text-red-500 text-sm">{state.errors.message[0]}</p>
)}
</div>
{state.errors?._form && (
<p className="text-red-500 text-sm">{state.errors._form[0]}</p>
)}
<button
type="submit"
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Crear Usuario
</button>
</form>
);
} - Incorrecto:
// ❌ Crear API Route innecesario para formulario simple
// app/api/users/route.ts
export async function POST(request: Request) {
const body = await request.json();
// Lógica duplicada que podría estar en Server Action
return NextResponse.json({ success: true });
}
// ❌ Client Component haciendo fetch manual
'use client';
export function UserForm() {
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
const formData = new FormData(e.target);
// Código verboso y propenso a errores
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: formData.get('name'),
email: formData.get('email'),
}),
});
if (!response.ok) {
// Manejo de errores manual
}
};
return <form onSubmit={handleSubmit}>{/* formulario */}</form>;
}
// ❌ Server Action sin validación
'use server';
export async function createUser(formData: FormData) {
// Sin validación de datos
const name = formData.get('name') as string;
await saveUser({ name }); // Datos no validados
}
Cuándo Aplicar
Para formularios, operaciones CRUD simples, mutaciones de datos que no necesitan ser expuestas como API pública, y cuando se quiera simplificar la integración cliente-servidor.
Cuándo Evitar o Flexibilizar
Para APIs que necesitan ser consumidas por aplicaciones externas, operaciones complejas que requieren múltiples endpoints, o cuando se necesita control granular sobre la respuesta HTTP (headers específicos, códigos de estado personalizados).
9. Regla sobre Componentes React (Server vs Client)
Regla Principal
Regla: La IA debe diferenciar claramente y utilizar apropiadamente Server Components y Client Components dentro del App Router.
- Server Components (Default):
- Son el tipo por defecto en
app/.- Deben usarse para: acceso directo a datos/backend, mantener lógica sensible fuera del cliente, reducir el JavaScript enviado al navegador.
- No pueden usar hooks como
useState,useEffect,useReducer,useContext.- No pueden usar manejadores de eventos interactivos como
onClick,onChange.- Client Components (
'use client'):
- Deben marcarse explícitamente con la directiva
'use client'al inicio del archivo.- Deben usarse para: añadir interactividad (event listeners), usar estado y efectos (
useState,useEffect), usar APIs exclusivas del navegador, usar React Context.- Minimizar su Uso: Mover los Client Components tan abajo en el árbol de componentes como sea posible ("leaves") para que la mayor parte de la UI se renderice en el servidor.
- Composición: Pasar Server Components como
childrena Client Components es un patrón válido y recomendado para mantener partes estáticas renderizadas en servidor dentro de un entorno interactivo.- Importaciones: Los Server Components pueden importar Client Components. Los Client Components no pueden importar directamente Server Components (pero pueden recibirlos como props, ej.
children).
Contexto y Justificación
La distinción entre Server y Client Components es fundamental en el App Router de Next.js. Permite optimizar el rendimiento enviando menos JavaScript al cliente y realizando el trabajo pesado (data fetching, renderizado inicial) en el servidor. Usar incorrectamente uno u otro tipo de componente puede llevar a errores, mal rendimiento o limitaciones funcionales.
Ejemplos y Contraejemplos
- Correcto (Composición):
// --- app/dashboard/page.tsx (Server Component) ---
import { ClientWrapper } from '@/components/features/ClientWrapper';
import { ServerInfo } from '@/components/features/ServerInfo';
async function getData() { /* ... fetch data ... */ return { value: 123 }; }
export default async function DashboardPage() {
const data = await getData();
return (
<ClientWrapper>
{/* ServerInfo se renderiza en servidor y se pasa como children */}
<ServerInfo serverData={data.value} />
</ClientWrapper>
);
}
// --- components/features/ServerInfo.tsx (Server Component) ---
// No necesita 'use client'
export function ServerInfo({ serverData }: { serverData: number }) {
// Puede tener lógica compleja o más data fetching si es necesario
return <p>Información del servidor: {serverData}</p>;
}
// --- components/features/ClientWrapper.tsx (Client Component) ---
'use client';
import { useState } from 'react';
export function ClientWrapper({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0);
return (
<div>
<h2>Wrapper Interactivo</h2>
{children} {/* Renderiza el Server Component recibido */}
<button onClick={() => setCount(c => c + 1)}>Clic (Cliente): {count}</button>
</div>
);
} - Incorrecto: Poner
'use client'en un componente que solo muestra datos estáticos. Intentar usaruseStateenapp/page.tsxsin'use client'. Importar un Server Component dentro de un Client Component.
Cuándo Aplicar
Siempre al crear o modificar componentes dentro del App Router.
Cuándo Evitar o Flexibilizar
El modelo Server/Client es la base del App Router. La decisión de dónde colocar
'use client'debe hacerse conscientemente para optimizar el rendimiento y la interactividad.