Saltar al contenido principal

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.

  1. Rutas basadas en Directorios: Cada directorio dentro de app/ define un segmento de ruta. Un archivo page.tsx dentro de un directorio hace que ese segmento sea públicamente accesible.
  2. Layouts Anidados: Usar layout.tsx para 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.
  3. 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.
  4. 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.
  5. Generación de Rutas Dinámicas (SSG): Para rutas dinámicas que se quieren generar estáticamente en build time, exportar una función generateStaticParams desde page.tsx o layout.tsx.
  6. Navegación: Usar el componente <Link> de next/link para la navegación del lado del cliente entre rutas. Usar el hook useRouter de next/navigation para la navegación programática en Client Components.
  7. Grupos de Rutas: Usar paréntesis (folderName) para organizar rutas o crear layouts específicos sin afectar la URL final.
  8. 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 estructura app/. Usar useRouter de next/router (del Pages Router) en App Router (usar next/navigation). Poner lógica de página compleja en layout.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 fetch extendido por Next.js en Server Components.

  1. Server Components: Realizar el data fetching directamente en Server Components (incluyendo page.tsx, layout.tsx) usando async/await con fetch. Next.js extiende fetch para permitir el cacheo automático y la revalidación.
  2. Cacheo y Revalidación: Controlar el comportamiento del cache de fetch usando el objeto next en las opciones: { cache: 'force-cache' } (default, SSG), { cache: 'no-store' } (SSR, siempre dinámico), { next: { revalidate: 3600 } } (ISR, revalida cada hora).
  3. 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 con useEffect sin una librería de gestión de estado de servidor.
  4. Route Handlers (API Routes): Definir endpoints API en app/api/.../route.ts para que los Client Components (o clientes externos) puedan hacer fetch de datos o realizar mutaciones.
  5. 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.
  6. Manejo de Errores: Implementar manejo de errores adecuado para las operaciones de fetch (ej. try/catch en Server Components, manejo de estado de error en useSWR/useQuery).
  7. Loading UI: Utilizar loading.tsx para 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 fetch extendido 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 en fetch en 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.

  1. Ubicación y Nomenclatura: Crear un archivo route.ts dentro del directorio app/ correspondiente a la ruta deseada (ej. app/api/users/route.ts para /api/users).
  2. Exportar Funciones HTTP: Exportar funciones nombradas según el método HTTP que manejan (GET, POST, PUT, DELETE, etc.). Estas funciones reciben un objeto Request como argumento.
  3. 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.
  4. Respuestas: Utilizar la clase NextResponse de next/server para construir y retornar las respuestas, permitiendo establecer el status code, headers y el body (usando NextResponse.json()).
  5. 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.
  6. 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).
  7. 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 NextResponse asegura 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/api junto con app/api de 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.

  1. 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.
  2. 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 children a Client Components es un patrón válido y recomendado para mantener partes estáticas renderizadas en servidor dentro de un entorno interactivo.
  3. 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 usar useState en app/page.tsx sin '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.

  1. Server Actions: Utilizar Server Actions ('use server') para manejar envíos de formularios y mutaciones de datos directamente desde Client Components.
  2. Directiva 'use server': Marcar funciones con 'use server' al inicio para indicar que se ejecutan en el servidor.
  3. Validación de Datos: Implementar validación robusta de datos en Server Actions usando librerías como Zod.
  4. Manejo de Errores: Proporcionar manejo de errores apropiado y retornar estados de error claros.
  5. Revalidación: Utilizar revalidatePath o revalidateTag para actualizar datos cached después de mutaciones.
  6. FormData Integration: Integrar naturalmente con HTML forms usando FormData como 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.

  1. 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.
  2. 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 children a Client Components es un patrón válido y recomendado para mantener partes estáticas renderizadas en servidor dentro de un entorno interactivo.
  3. 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 usar useState en app/page.tsx sin '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.