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:
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 (comohttps://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 Archivo | Extensión | Rol Arquitectónico |
|---|---|---|
page | .tsx, .jsx | Obligatorio para exponer una ruta pública. Contiene el contenido visual propio de la URL. |
layout | .tsx, .jsx | UI compartida. Envuelve a las páginas y layouts hijos; no se re-monta al navegar. |
loading | .tsx, .jsx | Estado de carga. Despliega una UI de espera instantánea basada en React Suspense. |
error | .tsx, .jsx | Manejo de excepciones. Boundary de React que captura errores en tiempo de ejecución. Debe ser Client Component ('use client'). |
not-found | .tsx, .jsx | Pantalla 404. Se despliega cuando una ruta no existe o se invoca la función notFound(). |
route | .ts, .js | Endpoint de API HTTP. Permite implementar verbos REST (GET, POST, PUT, DELETE). |
template | .tsx, .jsx | Layout 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):
Explicación de la Composición Visual
Imagina una muñeca rusa (matrioshka): cada archivo especial envuelve de manera protectora al siguiente:
<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.<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).<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.<Suspense>(loading.tsx): Envuelve la página mientras los Server Components asíncronos completan sus operaciones deawait. Mientras los datos llegan del servidor, el usuario visualiza de forma inmediata un skeleton o indicador de carga.<NotFoundBoundary>(not-found.tsx): Captura las invocaciones a la funciónnotFound()dentro del segmento.<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)
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:
Principios Fundamentales de los Layouts
- Persistencia de Estado: Al navegar entre
/cursos/reacty/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. - Recepción de
children: Todo layout recibe una propiedad obligatoriachildren: React.ReactNode, que actúa como el punto de inserción para las páginas o sub-layouts descendientes. - Parámetros de Ruta Asíncronos (Next.js 15+): En páginas dinámicas (
[slug]/page.tsx), la propiedadparamsse entrega como una Promesa:
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:
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:
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,
};
}
7. Navegación del Lado del Cliente: <Link>, usePathname y useRouter
En aplicaciones creadas con Next.js, la navegación entre rutas se gestiona mediante componentes optimizados para evitar la recarga física del navegador:
1. El Componente <Link>
- 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).
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:
'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):
'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>
);
}