Skip to Content

Páginas

Las páginas son los controladores de renderizado en Next.js que se encargan de orquestar features y manejar la lógica específica del enrutador. Su responsabilidad principal es controlar el flujo de renderizado sin importar qué están renderizando.

Principios Fundamentales

🚨 Regla Crítica: Las Páginas son SIEMPRE Server Components

Las páginas NUNCA pueden ser Client Components. Siempre son Server Components que:

  • Se ejecutan en el servidor
  • Pueden llamar a features, componentes aislados o cualquier JSX
  • Orquestan el renderizado de componentes del servidor y del cliente
  • Manejan la lógica específica del enrutador de Next.js

Responsabilidades Únicas de las Páginas

Las páginas NO contienen lógica de negocio. Su única responsabilidad es:

  1. Recibir parámetros del router (params, searchParams)
  2. Orquestar features y componentes apropiados
  3. Manejar estados de carga y error a nivel de página
  4. Controlar el layout y estructura de la página
  5. Ejecutar lógica de servidor (autenticación, redirects, etc.)

Anatomía de una Página

Estructura Básica - Server Component

// app/login/page.tsx // ✅ SIEMPRE Server Component (sin 'use client') import { LoginFeature } from '@/modules/auth/features/login'; interface LoginPageProps { params: {}; searchParams: { [key: string]: string | string[] | undefined }; } // ✅ Server Component por defecto - se ejecuta en el servidor export default function LoginPage({ params, searchParams }: LoginPageProps) { return ( <div className="login-page"> {/* ✅ Puede renderizar features (que pueden contener Client Components) */} <LoginFeature /> </div> ); }

❌ Lo que NO Puede Hacer una Página

// ❌ NUNCA hacer esto - Las páginas NO pueden ser Client Components 'use client'; export default function LoginPage() { const [state, setState] = useState(); // ❌ No puede usar hooks de cliente return <div>...</div>; }

Componentes de una Página

1. Props del Router

Todas las páginas reciben automáticamente params y searchParams:

interface PageProps { params: { [key: string]: string | string[] }; searchParams: { [key: string]: string | string[] | undefined }; }

2. Orquestación de Features y Componentes

La página (Server Component) puede renderizar cualquier combinación de:

// app/dashboard/page.tsx // ✅ Server Component que orquesta diferentes tipos de renderizado import { DashboardFeature } from '@/modules/dashboard/features/dashboard'; import { StatsFeature } from '@/modules/analytics/features/stats'; import { WelcomeHeader } from '@/shared/components/welcome-header'; import { QuickActions } from '@/shared/components/quick-actions'; export default function DashboardPage() { return ( <div className="dashboard-page"> {/* ✅ Componente aislado (Server Component) */} <WelcomeHeader /> {/* ✅ Features modulares */} <StatsFeature /> <DashboardFeature /> {/* ✅ Componente aislado que puede contener Client Components */} <QuickActions /> {/* ✅ JSX directo */} <footer className="dashboard-footer"> <p2024 Creative Minds</p> </footer> </div> ); }

3. Manejo de Estados

Control de loading, error y empty states:

// app/products/page.tsx import { Suspense } from 'react'; import { ProductsFeature } from '@/modules/products/features/products'; import { ProductsLoadingFallback } from '@/modules/products/fallbacks/loading'; export default function ProductsPage() { return ( <div className="products-page"> <h1>Productos</h1> <Suspense fallback={<ProductsLoadingFallback />}> <ProductsFeature /> </Suspense> </div> ); }

Qué Pueden Renderizar las Páginas

Como Server Components, las páginas pueden orquestar cualquier tipo de renderizado:

1. Features Completas

// ✅ Features que pueden contener Server y Client Components import { CheckoutFeature } from '@/modules/payments/features/checkout'; export default function CheckoutPage() { return <CheckoutFeature />; }

2. Componentes Aislados

// ✅ Componentes individuales del sistema de diseño import { Hero } from '@/shared/components/hero'; import { ContactForm } from '@/shared/components/contact-form'; export default function ContactPage() { return ( <div> <Hero title="Contacto" /> <ContactForm /> </div> ); }

3. Combinación de Features y Componentes

// ✅ Mezcla de features, componentes y JSX import { ProductsFeature } from '@/modules/products/features/products'; import { NewsletterSignup } from '@/shared/components/newsletter-signup'; export default function HomePage() { return ( <div> {/* JSX directo */} <section className="hero"> <h1>Bienvenido</h1> </section> {/* Feature modular */} <ProductsFeature /> {/* Componente aislado */} <NewsletterSignup /> </div> ); }

4. Layouts Dinámicos

// ✅ Control dinámico del layout basado en datos del servidor import { getDashboardLayout } from '@/modules/dashboard/services'; import { DashboardFeature } from '@/modules/dashboard/features/dashboard'; export default async function DashboardPage() { const layout = await getDashboardLayout(); if (layout.type === 'compact') { return ( <div className="compact-layout"> <DashboardFeature variant="compact" /> </div> ); } return ( <div className="full-layout"> <DashboardFeature variant="full" /> </div> ); }

Patrones de Páginas

1. Página Simple - Renderiza una Feature

// app/about/page.tsx import { AboutFeature } from '@/features/about'; export default function AboutPage() { return <AboutFeature />; }

2. Página con Parámetros - Pasa datos del router

// app/products/[id]/page.tsx import { ProductDetailFeature } from '@/modules/products/features/product-detail'; interface ProductPageProps { params: { id: string }; } export default function ProductPage({ params }: ProductPageProps) { return ( <div className="product-page"> <ProductDetailFeature productId={params.id} /> </div> ); }

3. Página con Query Parameters - Maneja filtros y búsquedas

// app/search/page.tsx import { SearchFeature } from '@/features/search'; interface SearchPageProps { searchParams: { q?: string; category?: string; page?: string; }; } export default function SearchPage({ searchParams }: SearchPageProps) { return ( <div className="search-page"> <SearchFeature query={searchParams.q} category={searchParams.category} page={searchParams.page ? parseInt(searchParams.page) : 1} /> </div> ); }

4. Página Compuesta - Múltiples Features

// app/dashboard/analytics/page.tsx import { Suspense } from 'react'; import { AnalyticsOverviewFeature } from '@/modules/analytics/features/overview'; import { ReportsFeature } from '@/modules/analytics/features/reports'; import { ChartsFeature } from '@/modules/analytics/features/charts'; export default function AnalyticsPage() { return ( <div className="analytics-page"> <div className="page-header"> <h1>Analytics Dashboard</h1> </div> <div className="analytics-grid"> <Suspense fallback={<div>Cargando overview...</div>}> <AnalyticsOverviewFeature /> </Suspense> <Suspense fallback={<div>Cargando reportes...</div>}> <ReportsFeature /> </Suspense> <Suspense fallback={<div>Cargando gráficos...</div>}> <ChartsFeature /> </Suspense> </div> </div> ); }

5. Página con Validación de Parámetros

// app/users/[id]/edit/page.tsx import { notFound } from 'next/navigation'; import { UserEditFeature } from '@/modules/users/features/user-edit'; interface UserEditPageProps { params: { id: string }; } export default function UserEditPage({ params }: UserEditPageProps) { // Validación simple de parámetros const userId = params.id; if (!userId || userId === 'undefined') { notFound(); } return ( <div className="user-edit-page"> <UserEditFeature userId={userId} /> </div> ); }

6. Página con Lógica de Servidor (Async Server Component)

// app/profile/page.tsx // ✅ Server Component asíncrono - puede ejecutar lógica de servidor import { redirect } from 'next/navigation'; import { getCurrentUser } from '@/modules/auth/services'; import { ProfileFeature } from '@/modules/users/features/profile'; import { ProfileHeader } from '@/shared/components/profile-header'; export default async function ProfilePage() { // ✅ Lógica de servidor - autenticación, validación, redirects const user = await getCurrentUser(); if (!user) { redirect('/login'); } if (!user.profileCompleted) { redirect('/onboarding'); } return ( <div className="profile-page"> {/* ✅ Componente aislado con datos del servidor */} <ProfileHeader user={user} /> {/* ✅ Feature que puede contener Client Components */} <ProfileFeature user={user} /> </div> ); }

Manejo de Estados en Páginas

1. Loading States con Suspense

// app/orders/page.tsx import { Suspense } from 'react'; import { OrdersFeature } from '@/modules/orders/features/orders'; export default function OrdersPage() { return ( <div className="orders-page"> <h1>Mis Pedidos</h1> <Suspense fallback={ <div className="loading-container"> <div className="spinner" /> <p>Cargando pedidos...</p> </div> } > <OrdersFeature /> </Suspense> </div> ); }

2. Error Boundaries

// app/payments/error.tsx 'use client'; export default function PaymentsError({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { return ( <div className="error-page"> <h2>Error en Pagos</h2> <p>Ocurrió un error al cargar la información de pagos.</p> <button onClick={reset}>Intentar de nuevo</button> </div> ); }

3. Not Found Pages

// app/products/[id]/not-found.tsx export default function ProductNotFound() { return ( <div className="not-found-page"> <h2>Producto no encontrado</h2> <p>El producto que buscas no existe o ha sido eliminado.</p> <a href="/products">Ver todos los productos</a> </div> ); }

Patrones Avanzados

1. Página con Múltiples Layouts

// app/(dashboard)/analytics/page.tsx import { DashboardLayout } from '@/shared/layouts/dashboard-layout'; import { AnalyticsFeature } from '@/modules/analytics/features/analytics'; export default function AnalyticsPage() { return ( <DashboardLayout> <AnalyticsFeature /> </DashboardLayout> ); }

2. Página con Datos Pre-cargados

// app/blog/[slug]/page.tsx import { getBlogPost } from '@/modules/blog/services'; import { BlogPostFeature } from '@/modules/blog/features/blog-post'; import { notFound } from 'next/navigation'; interface BlogPostPageProps { params: { slug: string }; } export default async function BlogPostPage({ params }: BlogPostPageProps) { const post = await getBlogPost(params.slug); if (!post) { notFound(); } return ( <article className="blog-post-page"> <BlogPostFeature post={post} /> </article> ); } // Generar metadata dinámicamente export async function generateMetadata({ params }: BlogPostPageProps) { const post = await getBlogPost(params.slug); return { title: post?.title || 'Post no encontrado', description: post?.excerpt, }; }

3. Página con Streaming

// app/dashboard/page.tsx import { Suspense } from 'react'; import { DashboardOverview } from '@/modules/dashboard/features/overview'; import { RecentActivity } from '@/modules/dashboard/features/recent-activity'; import { QuickStats } from '@/modules/dashboard/features/quick-stats'; export default function DashboardPage() { return ( <div className="dashboard-page"> {/* Se carga inmediatamente */} <div className="dashboard-header"> <h1>Dashboard</h1> </div> {/* Se carga de forma independiente */} <div className="dashboard-grid"> <Suspense fallback={<QuickStatsSkeleton />}> <QuickStats /> </Suspense> <Suspense fallback={<OverviewSkeleton />}> <DashboardOverview /> </Suspense> <Suspense fallback={<ActivitySkeleton />}> <RecentActivity /> </Suspense> </div> </div> ); }

Mejores Prácticas para Páginas

1. Mantener Páginas como Server Components

// ✅ Correcto - Server Component que orquesta features export default function LoginPage() { return <LoginFeature />; } // ✅ Correcto - Server Component con lógica de servidor export default async function LoginPage() { const user = await getCurrentUser(); if (user) { redirect('/dashboard'); } return <LoginFeature />; } // ❌ Incorrecto - Client Component (NUNCA hacer esto) 'use client'; export default function LoginPage() { const [email, setEmail] = useState(''); // ❌ No puede usar hooks de cliente return <form>...</form>; }

2. Usar Props Tipadas

// ✅ Correcto - Props bien tipadas interface UserPageProps { params: { id: string }; searchParams: { tab?: string }; } export default function UserPage({ params, searchParams }: UserPageProps) { return ( <UserFeature userId={params.id} activeTab={searchParams.tab} /> ); }

3. Validar Parámetros Críticos

// ✅ Correcto - Validación de parámetros export default function ProductPage({ params }: { params: { id: string } }) { if (!params.id || params.id === 'undefined') { notFound(); } return <ProductFeature productId={params.id} />; }

4. Usar Suspense para Loading States

// ✅ Correcto - Suspense para cargas asíncronas export default function OrdersPage() { return ( <Suspense fallback={<OrdersLoadingSkeleton />}> <OrdersFeature /> </Suspense> ); }

5. Separar Concerns - Server vs Client

// ✅ Correcto - Server Component que orquesta todo export default function CheckoutPage() { return ( <div className="checkout-page"> {/* Server Components para layout */} <CheckoutHeader /> {/* Feature que puede contener Client Components internamente */} <CheckoutFeature /> {/* Server Component para footer */} <CheckoutFooter /> </div> ); }

6. Aprovechar las Capacidades del Server Component

// ✅ Correcto - Usar capacidades del servidor export default async function ProductPage({ params }: { params: { id: string } }) { // ✅ Fetch de datos en el servidor const product = await getProduct(params.id); // ✅ Lógica de servidor if (!product) { notFound(); } // ✅ Generar contenido dinámico en el servidor const relatedProducts = await getRelatedProducts(product.category); return ( <div> {/* ✅ Pasar datos del servidor a features/componentes */} <ProductDetailFeature product={product} /> <RelatedProductsFeature products={relatedProducts} /> </div> ); }

Estructura de Archivos de Páginas

Organización Recomendada

app/ ├── (auth)/ # Grupo de rutas │ ├── login/ │ │ └── page.tsx # Página de login │ ├── register/ │ │ └── page.tsx # Página de registro │ └── layout.tsx # Layout para auth ├── (dashboard)/ # Grupo de rutas │ ├── analytics/ │ │ ├── page.tsx # Página principal │ │ ├── loading.tsx # Loading UI │ │ └── error.tsx # Error UI │ ├── users/ │ │ ├── page.tsx │ │ └── [id]/ │ │ ├── page.tsx # Detalle de usuario │ │ ├── edit/ │ │ │ └── page.tsx # Editar usuario │ │ └── not-found.tsx │ └── layout.tsx # Layout para dashboard ├── blog/ │ ├── page.tsx # Lista de posts │ └── [slug]/ │ ├── page.tsx # Post individual │ └── loading.tsx ├── api/ # API routes ├── globals.css ├── layout.tsx # Root layout └── page.tsx # Home page

Comunicación Página-Feature

Reglas de Serialización: Qué Pueden Pasar las Páginas

Como Server Components, las páginas solo pueden pasar datos serializables a las features:

✅ Datos Permitidos (Serializables)

// app/products/page.tsx export default async function ProductsPage({ params, searchParams }) { const serverData = await getProductsData(); return ( <ProductsFeature // ✅ Strings category={searchParams.category} sortBy={searchParams.sort} // ✅ Numbers page={searchParams.page ? parseInt(searchParams.page) : 1} // ✅ Booleans isAdmin={true} // ✅ Objects (serializables) filters={{ category: 'tech', price: 100 }} // ✅ Arrays categories={['tech', 'design', 'business']} // ✅ Datos del servidor serverData={serverData} // ✅ Null/undefined selectedItem={null} /> ); }

❌ Datos NO Permitidos (No Serializables)

// ❌ INCORRECTO - Estos NO se pueden pasar export default function ProductsPage() { return ( <ProductsFeature // ❌ Funciones onFilter={() => {}} onClick={handleClick} // ❌ Objetos con métodos utils={{ format: () => {} }} // ❌ Clases service={new ProductService()} // ❌ Symbols key={Symbol('key')} // ❌ Dates (usar strings ISO) createdAt={new Date()} // Usar createdAt={new Date().toISOString()} /> ); }

1. Pasar Datos del Router y Servidor

// app/products/page.tsx export default async function ProductsPage({ params, searchParams }) { // ✅ Datos del servidor const categories = await getCategories(); const userPreferences = await getUserPreferences(); return ( <ProductsFeature // ✅ Parámetros del router category={searchParams.category} sortBy={searchParams.sort} page={searchParams.page ? parseInt(searchParams.page) : 1} // ✅ Datos del servidor availableCategories={categories} userPreferences={userPreferences} /> ); }

2. Pasar Datos del Servidor a Features

// app/cart/page.tsx // ✅ Correcto - Server Component que pasa datos, no funciones export default async function CartPage({ searchParams }: { searchParams: { coupon?: string } }) { // ✅ Lógica de servidor const cartData = await getCartData(); const appliedCoupon = searchParams.coupon ? await validateCoupon(searchParams.coupon) : null; return ( <div className="cart-page"> <div className="cart-header"> <h1>Carrito de Compras</h1> </div> <Suspense fallback={<CartLoadingSkeleton />}> {/* ✅ Solo pasar datos serializables, no funciones */} <CartFeature cartData={cartData} appliedCoupon={appliedCoupon} /> </Suspense> </div> ); }

❌ Lo que NO se puede hacer:

// ❌ INCORRECTO - Server Components no pueden pasar funciones export default function CartPage() { return ( <CartFeature onCheckout={() => {}} // ❌ Error: funciones no son serializables onClick={handleClick} // ❌ Error: funciones no son serializables /> ); }

3. Las Features Manejan Su Propia Interactividad

// ✅ Correcto - La feature maneja sus propios eventos y estado // app/checkout/page.tsx export default async function CheckoutPage() { const cartItems = await getCartItems(); return ( <div className="checkout-page"> {/* ✅ Solo pasar datos, la feature maneja la interactividad */} <CheckoutFeature items={cartItems} // La feature internamente manejará: // - onSubmit con Server Actions // - onChange con Client Components // - onError con error boundaries /> </div> ); } // Dentro de CheckoutFeature (puede contener Client Components) // src/modules/payments/features/checkout/components/_checkout-form.tsx 'use client'; import { processPaymentAction } from '@/modules/payments/actions'; export function CheckoutForm({ items }) { // ✅ La feature maneja su propia interactividad const handleSubmit = async (formData) => { await processPaymentAction(formData); }; return ( <form action={handleSubmit}> {/* Formulario interactivo */} </form> ); }

Beneficios del Patrón de Páginas como Server Components

1. Performance Óptima

  • Zero JavaScript por defecto: Las páginas no envían JS al cliente
  • Server-side rendering: Contenido generado en el servidor
  • Streaming: Carga progresiva con Suspense
  • Caching: Aprovecha el cache del servidor de Next.js

2. Acceso Completo al Servidor

// ✅ Acceso directo a recursos del servidor export default async function DashboardPage() { // Base de datos const user = await db.user.findUnique({ where: { id: userId } }); // Variables de entorno const apiKey = process.env.API_KEY; // Sistema de archivos const config = await fs.readFile('./config.json'); return <DashboardFeature user={user} />; }

3. SEO y Accesibilidad

  • HTML completo en el servidor: Mejor SEO
  • Metadata dinámica: Generada en el servidor
  • Accesibilidad: Contenido disponible inmediatamente

4. Separación Clara de Responsabilidades

  • Páginas (Server): Routing, datos del servidor, orchestración
  • Features: Lógica de negocio, UI, interacción (Server + Client)
  • Client Components: Solo interactividad específica

5. Reutilización Inteligente

Las features pueden usarse en diferentes contextos de servidor:

// app/admin/users/page.tsx - Server Component export default async function AdminUsersPage() { const adminUser = await getAdminUser(); return ( <AdminLayout> <UsersFeature user={adminUser} adminMode={true} /> </AdminLayout> ); } // app/users/page.tsx - Server Component export default async function UsersPage() { const publicData = await getPublicUserData(); return ( <PublicLayout> <UsersFeature data={publicData} adminMode={false} /> </PublicLayout> ); }

6. Testing Simplificado

  • Páginas: Test de routing, datos del servidor, y rendering
  • Features: Test de lógica de negocio e interacción
  • Separación clara: Server logic vs Client logic

Las páginas actúan como orquestadores inteligentes del servidor que conectan el sistema de routing de Next.js con las features de la aplicación. Al ser siempre Server Components, aprovechan al máximo las capacidades del servidor mientras mantienen una arquitectura limpia, performante y escalable.

Resumen: Páginas como Server Components

✅ Lo que SÍ hacen las páginas:

  • Server Components: Siempre se ejecutan en el servidor
  • Orquestar renderizado: Features, componentes, JSX
  • Manejar routing: params, searchParams, redirects
  • Acceder al servidor: Base de datos, APIs, sistema de archivos
  • Generar metadata: SEO dinámico
  • Controlar layout: Estructura y composición

❌ Lo que NO hacen las páginas:

  • Client Components: Nunca usan ‘use client’
  • Hooks de cliente: useState, useEffect, etc.
  • Lógica de negocio: Delegada a features y servicios
  • Interactividad directa: Manejada por Client Components internos

🎯 Patrón Ideal:

// ✅ Página perfecta: Server Component que orquesta export default async function IdealPage({ params, searchParams }) { // Lógica de servidor const data = await getServerData(params.id); return ( <div> {/* Orquestar diferentes tipos de renderizado */} <ServerComponent data={data} /> <FeatureWithClientComponents filter={searchParams.filter} /> <AnotherFeature /> </div> ); }
Last updated on