Saltar al contenido principal

Arquitectura y Estructura de un Proyecto Next.js

Para construir aplicaciones escalables y mantenibles en Next.js, es fundamental comprender la anatomía de su sistema de archivos. A diferencia de otros entornos de desarrollo donde la estructura interna es completamente arbitraria, Next.js se basa en convenciones de nomenclatura estrictas que determinan el enrutamiento, la jerarquía de componentes y el manejo declarativo de estados de carga y error.

En este documento analizaremos la estructura inicial generada por las herramientas oficiales, el propósito de cada archivo de configuración en la raíz del proyecto y la jerarquía de renderizado que Next.js aplica al componer una ruta.


1. Anatomía de un Proyecto Creado con create-next-app​

Al inicializar un proyecto moderno de Next.js con el comando oficial npx create-next-app@latest, el andamiaje genera una estructura limpia y optimizada para producción:

Anatomía detallada de un proyecto Next.js generado por create-next-app

Desglose de los Componentes del Sistema de Archivos​

1. El Directorio app/ (o src/app/)​

Es el núcleo de la aplicación en el App Router. Contiene todas las rutas, layouts, páginas y hojas de estilo globales:

  • layout.tsx: El layout raíz obligatorio. Define las etiquetas indispensables <html> y <body>, configurando el diseño compartido por toda la aplicación.
  • page.tsx: El componente que renderiza el contenido de la ruta principal (/).
  • globals.css: Hoja de estilos compartida en la que se configuran las directivas de Tailwind CSS y las variables de diseño base.
  • favicon.ico: Icono que se muestra en la pestaña del navegador, gestionado automáticamente por la API de metadata de archivos de Next.js.

2. El Directorio public/​

Almacena recursos estáticos que se sirven directamente en la raíz del dominio:

  • Los archivos dentro de public/ (por ejemplo, public/logo.svg) son accesibles desde la URL base sin prefijos (como https://midominio.com/logo.svg).
  • Se recomienda ubicar aquí logotipos institucionales, robots.txt, sitemaps estáticos o imágenes que no dependan del pipeline de construcción.

3. Archivos de Configuración en la Raíz​

  • next.config.ts: Archivo de configuración principal del framework. Permite habilitar características experimentales, configurar dominios seguros para imágenes remotas (images.remotePatterns) y definir redirecciones HTTP.
  • tsconfig.json: Configuración del compilador de TypeScript. Incluye de forma nativa la resolución de alias de importación (paths: { "@/*": ["./*"] }), lo que permite importar componentes desde la raíz sin utilizar rutas relativas engorrosas como ../../../components/Boton.
  • package.json: Declara las dependencias (next, react, react-dom) y los scripts estándar de ciclo de vida:
    • npm run dev: Inicia el servidor de desarrollo local con compilación incremental ultrarrápida impulsada por Turbopack.
    • npm run build: Genera la versión compilada y optimizada para producción.
    • npm run start: Arranca el servidor de producción con la versión previamente compilada.
    • npm run lint: Ejecuta las reglas de verificación de código con ESLint.

2. Los Archivos Especiales de Convención en app/​

Dentro de cualquier carpeta del directorio app/, Next.js reserva un conjunto específico de nombres de archivo para resolver diferentes responsabilidades de la interfaz de usuario:

Nombre de ArchivoExtensiónRol Arquitectónico
page.tsx, .jsxObligatorio para exponer una ruta pública. Contiene el contenido visual propio de la URL.
layout.tsx, .jsxUI compartida. Envuelve a las páginas y layouts hijos; no se re-monta al navegar.
loading.tsx, .jsxEstado de carga. Despliega una UI de espera instantánea basada en React Suspense.
error.tsx, .jsxManejo de excepciones. Boundary de React que captura errores en tiempo de ejecución. Debe ser Client Component ('use client').
not-found.tsx, .jsxPantalla 404. Se despliega cuando una ruta no existe o se invoca la función notFound().
route.ts, .jsEndpoint de API HTTP. Permite implementar verbos REST (GET, POST, PUT, DELETE).
template.tsx, .jsxLayout re-montable. Similar a layout, pero crea una instancia nueva en cada transición.

3. Jerarquía de Renderizado: El Modelo de Anidamiento​

Uno de los aspectos más elegantes de la arquitectura del App Router es cómo Next.js combina los archivos especiales de una misma carpeta. El framework los compone recursivamente siguiendo un orden estricto de límites anidados (nested boundaries):

Jerarquía de composición y anidamiento de archivos especiales en el App Router

Explicación de la Composición Visual​

Imagina una muñeca rusa (matrioshka): cada archivo especial envuelve de manera protectora al siguiente:

  1. <Layout> (layout.tsx): Constituye el contenedor exterior. Almacena la barra de navegación, el menú lateral y el pie de página. Persiste durante la navegación.
  2. <Template> (template.tsx): Opcional. Si existe, se ubica inmediatamente dentro del Layout y se reinicializa en cada navegación (ideal para animaciones de entrada o registro de analíticas por página).
  3. <ErrorBoundary> (error.tsx): Si ocurre una excepción no controlada en la página o en la obtención de datos, este límite la intercepta y muestra una interfaz de recuperación con un botón de reintento (reset()), impidiendo que toda la aplicación se caiga.
  4. <Suspense> (loading.tsx): Envuelve la página mientras los Server Components asíncronos completan sus operaciones de await. Mientras los datos llegan del servidor, el usuario visualiza de forma inmediata un skeleton o indicador de carga.
  5. <NotFoundBoundary> (not-found.tsx): Captura las invocaciones a la función notFound() dentro del segmento.
  6. <Page /> (page.tsx): En el centro de toda la jerarquía se ubica el componente de la página, el cual provee el contenido único que cambia al navegar.

4. Convenciones Avanzadas de Organización de Carpetas​

Para mantener una base de código limpia en proyectos de gran tamaño, Next.js ofrece dos patrones de organización basados en prefijos de carpetas:

A. Carpetas Privadas (Prefijo con Guion Bajo: _folder)​

Si deseas crear una carpeta para almacenar componentes internos, librerías auxiliares o estilos sin que Next.js la considere parte del árbol de rutas de la URL, puedes anteponer un guion bajo a su nombre:

app/
├── cursos/
│ ├── _components/ <-- Carpeta privada: no genera ruta /cursos/_components
│ │ ├── CourseCard.tsx
│ │ └── FilterBar.tsx
│ ├── page.tsx <-- Responde a /cursos
│ └── layout.tsx

B. Grupos de Rutas (Paréntesis: (nombre))​

Los Route Groups permiten organizar rutas en módulos lógicos o aplicar layouts completamente distintos a diferentes secciones de la aplicación sin alterar la estructura de la URL pública:

app/
├── (marketing)/
│ ├── layout.tsx <-- Layout con navbar público comercial
│ ├── about/
│ │ └── page.tsx <-- Responde a /about (ignora el prefijo marketing)
│ └── contact/
│ └── page.tsx <-- Responde a /contact
└── (dashboard)/
├── layout.tsx <-- Layout con sidebar de administración y barra de usuario
└── admin/
└── page.tsx <-- Responde a /admin (ignora el prefijo dashboard)
Utilidad de los Route Groups

Los grupos de rutas son especialmente útiles para separar la experiencia pública (landing page, registro, términos de servicio) de la experiencia autenticada (panel de control, configuración, analíticas), permitiendo que cada entorno tenga su propio layout independiente sin duplicar código.


5. Layouts Anidados y Páginas en Acción​

Para comprender cómo los layouts y las páginas se integran en una aplicación real, consideremos un catálogo formativo donde coexisten un layout global y un layout de sección:

Composición de Layouts Anidados y Páginas en el App Router

Principios Fundamentales de los Layouts​

  1. Persistencia de Estado: Al navegar entre /cursos/react y /cursos/nextjs, el layout raíz y el layout de cursos permanecen montados. Si el usuario tiene un campo de búsqueda o un menú colapsable en el layout, su estado no se pierde.
  2. Recepción de children: Todo layout recibe una propiedad obligatoria children: React.ReactNode, que actúa como el punto de inserción para las páginas o sub-layouts descendientes.
  3. Parámetros de Ruta Asíncronos (Next.js 15+): En páginas dinámicas ([slug]/page.tsx), la propiedad params se entrega como una Promesa:
app/cursos/[slug]/page.tsx
interface PageProps {
params: Promise<{ slug: string }>;
}

export default async function CursoDetalle({ params }: PageProps) {
// En Next.js 15+, los parámetros de ruta dinámicos se resuelven con await
const { slug } = await params;

return (
<article className="p-6">
<h1 className="text-3xl font-bold">Módulo: {slug}</h1>
<p className="mt-2 text-slate-600">Detalle del curso renderizado en el servidor.</p>
</article>
);
}

6. Configuración de Metadata para SEO​

El App Router simplifica la gestión de metadatos de optimización en motores de búsqueda (SEO) y tarjetas de redes sociales (Open Graph) mediante una API unificada.

A. Metadata Estática y Plantillas de Título​

En layouts o páginas estáticas, puedes exportar una constante metadata de tipo Metadata:

app/layout.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
// title.template permite que las páginas hijas definan solo su nombre
title: {
template: '%s | CompuNet Academy',
default: 'CompuNet Academy',
},
description: 'Plataforma de desarrollo web moderno de la Universidad Icesi',
};

Si una página hija define title: 'Next.js', el título final desplegado en la pestaña del navegador será automáticamente:

Next.js | CompuNet Academy

B. Metadata Dinámica con generateMetadata​

Cuando los títulos o descripciones dependen de parámetros de ruta o consultas a bases de datos, se exporta la función asíncrona generateMetadata:

app/cursos/[slug]/page.tsx
import type { Metadata } from 'next';

export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const curso = await obtenerCurso(slug);

return {
title: curso.titulo,
description: curso.resumen,
};
}

En aplicaciones creadas con Next.js, la navegación entre rutas se gestiona mediante componentes optimizados para evitar la recarga física del navegador:

Comparación entre etiqueta de ancla tradicional y componente Link de Next.js
  • Reemplaza la etiqueta HTML estándar <a>.
  • Realiza una transición suave del lado del cliente (SPA) preservando el estado de la aplicación.
  • Prefetching Automático: En producción, precarga silenciosamente en segundo plano el código y los datos de cualquier enlace visible en la pantalla (in viewport).
app/components/Navbar.tsx
import Link from 'next/link';

export function Navbar() {
return (
<nav className="flex space-x-4">
<Link href="/" className="hover:underline">Inicio</Link>
<Link href="/cursos" className="hover:underline">Cursos</Link>
</nav>
);
}

2. Detección de Rutas Activas con usePathname()​

Para aplicar estilos visuales al enlace que coincide con la URL actual, se utiliza el hook usePathname() en un Client Component:

app/components/NavLink.tsx
'use client';

import Link from 'next/link';
import { usePathname } from 'next/navigation';

export function NavLink({ href, label }: { href: string; label: string }) {
const pathname = usePathname();
const isActive = pathname === href;

return (
<Link
href={href}
className={`px-3 py-2 rounded-lg text-sm font-medium ${
isActive ? 'bg-sky-600 text-white' : 'text-slate-600 hover:bg-slate-100'
}`}
>
{label}
</Link>
);
}

3. Navegación Programática con useRouter()​

Cuando la redirección debe ejecutarse como consecuencia de una acción del usuario (como hacer clic en un botón tras completar una validación):

app/components/LoginButton.tsx
'use client';

import { useRouter } from 'next/navigation';

export function LoginButton() {
const router = useRouter();

const handleLogin = () => {
// Redirección imperativa del lado del cliente
router.push('/dashboard');
};

return (
<button onClick={handleLogin} className="btn-primary">
Iniciar Sesión
</button>
);
}

Cuestionario de Autoevaluación​

Cargando cuestionario...