Desbloquea el poder del App Router de Next.js con nuestra guía detallada sobre el enrutamiento basado en archivos. Aprende a estructurar tu aplicación, crear rutas dinámicas, gestionar layouts y más.
App Router de Next.js: Una Guía Completa sobre el Enrutamiento Basado en Archivos
El App Router de Next.js, introducido en Next.js 13 y convirtiéndose en el estándar en versiones posteriores, revoluciona cómo estructuramos y navegamos por las aplicaciones. Introduce un sistema de enrutamiento basado en archivos potente e intuitivo que simplifica el desarrollo, mejora el rendimiento y potencia la experiencia general del desarrollador. Esta guía completa profundizará en el enrutamiento basado en archivos del App Router, proporcionándote el conocimiento y las habilidades para construir aplicaciones de Next.js robustas y escalables.
¿Qué es el Enrutamiento Basado en Archivos?
El enrutamiento basado en archivos es un sistema de enrutamiento donde la estructura de las rutas de tu aplicación está directamente determinada por la organización de tus archivos y directorios. En el App Router de Next.js, defines las rutas creando archivos dentro del directorio `app`. Cada carpeta representa un segmento de ruta, y archivos especiales dentro de esas carpetas definen cómo se manejará ese segmento de ruta. Este enfoque ofrece varias ventajas:
- Estructura Intuitiva: El sistema de archivos refleja la estructura de rutas de la aplicación, facilitando su comprensión y navegación.
- Enrutamiento Automático: Next.js genera rutas automáticamente basándose en la estructura de tus archivos, eliminando la necesidad de configuración manual.
- Colocación de Código: Los manejadores de ruta y los componentes de la interfaz de usuario se ubican juntos, mejorando la organización y mantenibilidad del código.
- Funcionalidades Integradas: El App Router proporciona soporte integrado para layouts, rutas dinámicas, obtención de datos y más, simplificando escenarios de enrutamiento complejos.
Primeros Pasos con el App Router
Para usar el App Router, necesitas crear un nuevo proyecto de Next.js o migrar un proyecto existente. Asegúrate de estar usando la versión 13 de Next.js o posterior.
Creando un Nuevo Proyecto:
Puedes crear un nuevo proyecto de Next.js con el App Router usando el siguiente comando:
npx create-next-app@latest my-app --example with-app
Migrando un Proyecto Existente:
Para migrar un proyecto existente, necesitas mover tus páginas del directorio `pages` al directorio `app`. Puede que necesites ajustar tu lógica de enrutamiento en consecuencia. Next.js proporciona una guía de migración para ayudarte con este proceso.
Conceptos Fundamentales del Enrutamiento Basado en Archivos
El App Router introduce varios archivos y convenciones especiales que definen cómo se manejan tus rutas:
1. El Directorio `app`
El directorio `app` es la raíz de las rutas de tu aplicación. Todos los archivos y carpetas dentro de este directorio se usarán para generar rutas. Cualquier cosa fuera del directorio `app` (como el directorio `pages` si estás migrando) será ignorada por el App Router.
2. El Archivo `page.js`
El archivo `page.js` (o `page.jsx`, `page.ts`, `page.tsx`) es la parte más fundamental del App Router. Define el componente de UI que se renderizará para un segmento de ruta específico. Es un archivo **obligatorio** para cualquier segmento de ruta al que quieras que se pueda acceder directamente.
Ejemplo:
Si tienes una estructura de archivos como esta:
app/
about/
page.js
El componente exportado desde `app/about/page.js` se renderizará cuando un usuario navegue a `/about`.
// app/about/page.js
import React from 'react';
export default function AboutPage() {
return (
<div>
<h1>Sobre Nosotros</h1>
<p>Aprende más sobre nuestra compañía.</p>
</div>
);
}
3. El Archivo `layout.js`
El archivo `layout.js` (o `layout.jsx`, `layout.ts`, `layout.tsx`) define una UI que se comparte entre múltiples páginas dentro de un segmento de ruta. Los layouts son útiles para crear encabezados, pies de página, barras laterales y otros elementos consistentes que deben estar presentes en múltiples páginas.
Ejemplo:
Digamos que quieres añadir un encabezado tanto a la página `/about` como a una hipotética página `/about/team`. Puedes crear un archivo `layout.js` en el directorio `app/about`:
// app/about/layout.js
import React from 'react';
export default function AboutLayout({ children }) {
return (
<div>
<header>
<h1>Acerca de Nuestra Compañía</h1>
</header>
<main>{children}</main>
</div>
);
}
La prop `children` será reemplazada con la UI renderizada por el archivo `page.js` en el mismo directorio o en cualquier directorio anidado.
4. El Archivo `template.js`
El archivo `template.js` es similar a `layout.js`, pero crea una nueva instancia del componente para cada ruta hija. Esto es útil para escenarios donde quieres mantener el estado del componente o evitar re-renderizaciones al navegar entre rutas hijas. A diferencia de los layouts, las plantillas se volverán a renderizar en la navegación. Usar plantillas es excelente para animar elementos durante la navegación.
Ejemplo:
// app/template.js
'use client'
import { useState } from 'react'
export default function Template({ children }) {
const [count, setCount] = useState(0)
return (
<main>
<p>Plantilla: {count}</p>
<button onClick={() => setCount(count + 1)}>Actualizar Plantilla</button>
{children}
</main>
)
}
5. El Archivo `loading.js`
El archivo `loading.js` (o `loading.jsx`, `loading.ts`, `loading.tsx`) te permite crear una UI de carga que se muestra mientras un segmento de ruta se está cargando. Esto es útil para proporcionar una mejor experiencia de usuario al obtener datos o realizar otras operaciones asíncronas.
Ejemplo:
// app/about/loading.js
import React from 'react';
export default function Loading() {
return <p>Cargando información de "sobre nosotros"...</p>;
}
Cuando un usuario navega a `/about`, el componente `Loading` se mostrará hasta que el componente `page.js` esté completamente renderizado.
6. El Archivo `error.js`
El archivo `error.js` (o `error.jsx`, `error.ts`, `error.tsx`) te permite crear una UI de error personalizada que se muestra cuando ocurre un error dentro de un segmento de ruta. Esto es útil para proporcionar un mensaje de error más amigable para el usuario y evitar que toda la aplicación se bloquee.
Ejemplo:
// app/about/error.js
'use client'
import React from 'react';
export default function Error({ error, reset }) {
return (
<div>
<h2>¡Ha ocurrido un error!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Intentar de nuevo</button>
</div>
);
}
Si ocurre un error mientras se renderiza la página `/about`, se mostrará el componente `Error`. La prop `error` contiene información sobre el error, y la función `reset` permite al usuario intentar recargar la página.
7. Grupos de Rutas
Los Grupos de Rutas `(nombreGrupo)` te permiten organizar tus rutas sin afectar la estructura de la URL. Se crean envolviendo el nombre de una carpeta entre paréntesis. Esto es particularmente útil para organizar layouts y componentes compartidos.
Ejemplo:
app/
(marketing)/
about/
page.js
contact/
page.js
(shop)/
products/
page.js
En este ejemplo, las páginas `about` y `contact` están agrupadas bajo el grupo `marketing`, y la página `products` está bajo el grupo `shop`. Las URLs permanecen como `/about`, `/contact` y `/products`, respectivamente.
8. Rutas Dinámicas
Las rutas dinámicas te permiten crear rutas con segmentos variables. Esto es útil para mostrar contenido basado en datos obtenidos de una base de datos o API. Los segmentos de ruta dinámicos se definen envolviendo el nombre del segmento en corchetes (p. ej., `[id]`).
Ejemplo:
Digamos que quieres crear una ruta para mostrar publicaciones de blog individuales según su ID. Puedes crear una estructura de archivos como esta:
app/
blog/
[id]/
page.js
El segmento `[id]` es un segmento dinámico. El componente exportado desde `app/blog/[id]/page.js` se renderizará cuando un usuario navegue a una URL como `/blog/123` o `/blog/456`. El valor del parámetro `id` estará disponible en la prop `params` del componente.
// app/blog/[id]/page.js
import React from 'react';
export default async function BlogPost({ params }) {
const { id } = params;
// Obtener datos para la publicación del blog con el ID dado
const post = await fetchBlogPost(id);
if (!post) {
return <p>Publicación de blog no encontrada.</p>;
}
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
async function fetchBlogPost(id) {
// Simular la obtención de datos de una base de datos o API
return new Promise((resolve) => {
setTimeout(() => {
const posts = {
'123': { title: 'Mi Primera Publicación de Blog', content: 'Este es el contenido de mi primera publicación de blog.' },
'456': { title: 'Otra Publicación de Blog', content: 'Este es más contenido emocionante.' },
};
resolve(posts[id] || null);
}, 500);
});
}
También puedes usar múltiples segmentos dinámicos en una ruta. Por ejemplo, podrías tener una ruta como `/blog/[categoria]/[id]`.
9. Segmentos Catch-all
Los segmentos catch-all (de captura total) te permiten crear rutas que coinciden con cualquier número de segmentos. Esto es útil para escenarios como la creación de un CMS donde la estructura de la URL es determinada por el usuario. Los segmentos catch-all se definen añadiendo tres puntos antes del nombre del segmento (p. ej., `[...slug]`).
Ejemplo:
app/
docs/
[...slug]/
page.js
El segmento `[...slug]` coincidirá con cualquier número de segmentos después de `/docs`. Por ejemplo, coincidirá con `/docs/getting-started`, `/docs/api/users` y `/docs/advanced/configuration`. El valor del parámetro `slug` será un array que contiene los segmentos coincidentes.
// app/docs/[...slug]/page.js
import React from 'react';
export default function DocsPage({ params }) {
const { slug } = params;
return (
<div>
<h1>Documentación</h1>
<p>Slug: {slug ? slug.join('/') : 'Sin slug'}</p>
</div>
);
}
Los segmentos catch-all opcionales se pueden crear añadiendo el nombre del segmento en dobles corchetes `[[...slug]]`. Esto hace que el segmento de la ruta sea opcional. Ejemplo:
app/
blog/
[[...slug]]/
page.js
Esta configuración renderizará el componente page.js tanto en `/blog` como en `/blog/cualquier/numero/de/segmentos`.
10. Rutas Paralelas
Las Rutas Paralelas te permiten renderizar simultáneamente una o más páginas en el mismo layout. Esto es particularmente útil para layouts complejos, como paneles de control, donde diferentes secciones de la página pueden cargarse de forma independiente. Las Rutas Paralelas se definen usando el símbolo `@` seguido de un nombre de slot (p. ej., `@sidebar`, `@main`).
Ejemplo:
app/
@sidebar/
page.js // Contenido para la barra lateral
@main/
page.js // Contenido para la sección principal
default.js // Requerido: Define el layout por defecto para las rutas paralelas
El archivo `default.js` es requerido cuando se usan rutas paralelas. Define cómo se combinan los diferentes slots para crear el layout final.
// app/default.js
export default function RootLayout({ children: { sidebar, main } }) {
return (
<div style={{ display: 'flex' }}>
<aside style={{ width: '200px', backgroundColor: '#f0f0f0' }}>
{sidebar}
</aside>
<main style={{ flex: 1, padding: '20px' }}>
{main}
</main>
</div>
);
}
11. Rutas de Interceptación
Las Rutas de Interceptación te permiten cargar una ruta de una parte diferente de tu aplicación dentro del layout actual. Esto es útil para crear modales, galerías de imágenes y otros elementos de UI que deben aparecer sobre el contenido de la página existente. Las Rutas de Interceptación se definen usando la sintaxis `(..)`, que indica cuántos niveles hacia arriba en el árbol de directorios se debe ir para encontrar la ruta interceptada.
Ejemplo:
app/
(.)photos/
[id]/
page.js // La ruta interceptada
feed/
page.js // La página donde se muestra el modal de la foto
En este ejemplo, cuando un usuario hace clic en una foto en la página `/feed`, la ruta `app/(.)photos/[id]/page.js` es interceptada y mostrada como un modal sobre la página `/feed`. La sintaxis `(.)` le dice a Next.js que busque un nivel hacia arriba (al directorio `app`) para encontrar la ruta `photos/[id]`.
Obtención de Datos con el App Router
El App Router proporciona soporte integrado para la obtención de datos usando Componentes de Servidor y Componentes de Cliente. Los Componentes de Servidor se renderizan en el servidor, mientras que los Componentes de Cliente se renderizan en el cliente. Esto te permite elegir el mejor enfoque para cada componente según sus requisitos.
Componentes de Servidor
Los Componentes de Servidor son el valor por defecto en el App Router. Te permiten obtener datos directamente en tus componentes sin la necesidad de rutas de API separadas. Esto puede mejorar el rendimiento y simplificar tu código.
Ejemplo:
// app/products/page.js
import React from 'react';
export default async function ProductsPage() {
const products = await fetchProducts();
return (
<div>
<h1>Productos</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
async function fetchProducts() {
// Simular la obtención de datos de una base de datos o API
return new Promise((resolve) => {
setTimeout(() => {
const products = [
{ id: 1, name: 'Producto A' },
{ id: 2, name: 'Producto B' },
{ id: 3, name: 'Producto C' },
];
resolve(products);
}, 500);
});
}
En este ejemplo, la función `fetchProducts` se llama directamente dentro del componente `ProductsPage`. El componente se renderiza en el servidor, y los datos se obtienen antes de que el HTML se envíe al cliente.
Componentes de Cliente
Los Componentes de Cliente se renderizan en el cliente y te permiten usar características del lado del cliente como escuchas de eventos, estado y APIs del navegador. Para usar un Componente de Cliente, necesitas añadir la directiva `'use client'` en la parte superior del archivo.
Ejemplo:
// app/counter/page.js
'use client'
import React, { useState } from 'react';
export default function CounterPage() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Contador</h1>
<p>Conteo: {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
);
}
En este ejemplo, el componente `CounterPage` es un Componente de Cliente porque usa el hook `useState`. La directiva `'use client'` le dice a Next.js que renderice este componente en el cliente.
Técnicas de Enrutamiento Avanzadas
El App Router ofrece varias técnicas de enrutamiento avanzadas que se pueden usar para crear aplicaciones complejas y sofisticadas.
1. Manejadores de Ruta (Route Handlers)
Los Manejadores de Ruta te permiten crear puntos finales de API (API endpoints) dentro de tu directorio `app`. Esto elimina la necesidad de un directorio `pages/api` separado. Los Manejadores de Ruta se definen en archivos llamados `route.js` (o `route.ts`) y exportan funciones que manejan diferentes métodos HTTP (p. ej., `GET`, `POST`, `PUT`, `DELETE`).
Ejemplo:
// app/api/users/route.js
import { NextResponse } from 'next/server'
export async function GET(request) {
// Simular la obtención de usuarios de una base de datos
const users = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
];
return NextResponse.json(users);
}
export async function POST(request) {
const body = await request.json()
console.log('Datos recibidos:', body)
return NextResponse.json({ message: 'Usuario creado' }, { status: 201 })
}
Este ejemplo define un manejador de ruta en `/api/users` que maneja tanto las solicitudes `GET` como `POST`. La función `GET` devuelve una lista de usuarios, y la función `POST` crea un nuevo usuario.
2. Grupos de Rutas con Múltiples Layouts
Puedes combinar grupos de rutas con layouts para crear diferentes diseños para diferentes secciones de tu aplicación. Esto es útil para escenarios en los que deseas tener un encabezado o una barra lateral diferente para distintas partes de tu sitio.
Ejemplo:
app/
(marketing)/
layout.js // Layout de Marketing
about/
page.js
contact/
page.js
(admin)/
layout.js // Layout de Admin
dashboard/
page.js
En este ejemplo, las páginas `about` y `contact` usarán el layout de `marketing`, mientras que la página `dashboard` usará el layout de `admin`.
3. Middleware
El Middleware te permite ejecutar código antes de que una solicitud sea manejada por tu aplicación. Esto es útil para tareas como autenticación, autorización, registro y redirección de usuarios según su ubicación o dispositivo.
El Middleware se define en un archivo llamado `middleware.js` (o `middleware.ts`) en la raíz de tu proyecto.
Ejemplo:
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
// Comprobar si el usuario está autenticado
const isAuthenticated = false; // Reemplaza con tu lógica de autenticación
if (!isAuthenticated && request.nextUrl.pathname.startsWith('/admin')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// Consulta "Matching Paths" a continuación para saber más
export const config = {
matcher: '/admin/:path*',
}
Este ejemplo define un middleware que comprueba si el usuario está autenticado antes de permitirle acceder a cualquier ruta bajo `/admin`. Si el usuario no está autenticado, es redirigido a la página `/login`.
Mejores Prácticas para el Enrutamiento Basado en Archivos
Para aprovechar al máximo el sistema de enrutamiento basado en archivos del App Router, considera las siguientes mejores prácticas:
- Mantén tu estructura de archivos organizada: Usa nombres de carpeta significativos y agrupa los archivos relacionados.
- Usa layouts para la UI compartida: Crea layouts para encabezados, pies de página, barras laterales y otros elementos que se comparten en múltiples páginas.
- Usa UIs de carga: Proporciona UIs de carga para rutas que obtienen datos o realizan otras operaciones asíncronas.
- Maneja los errores con elegancia: Crea UIs de error personalizadas para proporcionar una mejor experiencia de usuario cuando ocurren errores.
- Usa grupos de rutas para la organización: Usa grupos de rutas para organizar tus rutas sin afectar la estructura de la URL.
- Aprovecha los componentes de servidor para el rendimiento: Usa componentes de servidor para obtener datos y renderizar la UI en el servidor, mejorando el rendimiento y el SEO.
- Usa componentes de cliente cuando sea necesario: Usa componentes de cliente cuando necesites usar características del lado del cliente como escuchas de eventos, estado y APIs del navegador.
Ejemplos de Internacionalización con el App Router de Next.js
El App Router de Next.js simplifica la internacionalización (i18n) a través del enrutamiento basado en archivos. A continuación se explica cómo puedes implementar i18n de manera efectiva:
1. Enrutamiento por Sub-ruta
Organiza tus rutas basadas en el idioma (locale) usando sub-rutas. Por ejemplo:
app/
[locale]/
page.tsx // Página de inicio para el idioma
about/
page.tsx // Página "acerca de" para el idioma
// app/[locale]/page.tsx
import { getTranslations } from './dictionaries';
export default async function HomePage({ params: { locale } }) {
const t = await getTranslations(locale);
return (<h1>{t.home.title}</h1>);
}
// dictionaries.js
const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
es: () => import('./dictionaries/es.json').then((module) => module.default),
};
export const getTranslations = async (locale) => {
try {
return dictionaries[locale]() ?? dictionaries.en();
} catch (error) {
console.error(`No se pudieron cargar las traducciones para el idioma ${locale}`, error);
return dictionaries.en();
}
};
En esta configuración, el segmento de ruta dinámico `[locale]` maneja diferentes idiomas (p. ej., `/en`, `/es`). Las traducciones se cargan dinámicamente según el idioma.
2. Enrutamiento por Dominio
Para un enfoque más avanzado, puedes usar diferentes dominios o subdominios para cada idioma. Esto a menudo implica una configuración adicional con tu proveedor de hosting.
3. Middleware para la Detección de Idioma
Usa middleware para detectar automáticamente el idioma preferido del usuario y redirigirlo en consecuencia.
// middleware.js
import { NextResponse } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';
let locales = ['en', 'es', 'fr'];
function getLocale(request) {
const negotiatorHeaders = {};
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
let languages = new Negotiator({ headers: negotiatorHeaders }).languages();
try {
return match(languages, locales, 'en'); // Usar "en" como idioma por defecto
} catch (error) {
console.error("Error al buscar el idioma:", error);
return 'en'; // Volver a inglés si la búsqueda falla
}
}
export function middleware(request) {
const pathname = request.nextUrl.pathname;
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
);
if (pathnameIsMissingLocale) {
const locale = getLocale(request);
return NextResponse.redirect(
new URL(
`/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
request.url
)
);
}
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
Este middleware comprueba si la ruta solicitada tiene un prefijo de idioma. Si no es así, detecta el idioma preferido del usuario utilizando la cabecera `Accept-Language` y lo redirige a la ruta específica del idioma apropiado. Bibliotecas como `@formatjs/intl-localematcher` y `negotiator` se utilizan para manejar la negociación del idioma.
App Router de Next.js y Accesibilidad Global
Crear aplicaciones web accesibles a nivel mundial requiere una cuidadosa consideración de los principios de accesibilidad (a11y). El App Router de Next.js proporciona una base sólida para construir experiencias accesibles, pero es esencial implementar las mejores prácticas para asegurar que tu aplicación sea usable por todos, independientemente de sus habilidades.
Consideraciones Clave de Accesibilidad
- HTML Semántico: Usa elementos HTML semánticos (p. ej., `<article>`, `<nav>`, `<aside>`, `<main>`) para estructurar tu contenido. Esto proporciona significado a las tecnologías de asistencia y ayuda a los usuarios a navegar por tu sitio más fácilmente.
- Atributos ARIA: Usa atributos ARIA (Accessible Rich Internet Applications) para mejorar la accesibilidad de componentes y widgets personalizados. Los atributos ARIA proporcionan información adicional sobre el rol, estado y propiedades de los elementos a las tecnologías de asistencia.
- Navegación por Teclado: Asegúrate de que todos los elementos interactivos sean accesibles a través del teclado. Los usuarios deben poder navegar por tu aplicación usando la tecla `Tab` e interactuar con los elementos usando la tecla `Enter` o `Espacio`.
- Contraste de Color: Usa un contraste de color suficiente entre el texto y el fondo para asegurar la legibilidad para los usuarios con discapacidades visuales. Las Pautas de Accesibilidad al Contenido Web (WCAG) recomiendan una relación de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande.
- Texto Alternativo de Imagen: Proporciona texto alternativo descriptivo para todas las imágenes. El texto alternativo proporciona una alternativa textual para las imágenes que puede ser leída por los lectores de pantalla.
- Etiquetas de Formulario: Asocia las etiquetas de los formularios con sus campos de entrada correspondientes usando el elemento `<label>`. Esto deja claro a los usuarios qué información se espera en cada campo.
- Pruebas con Lector de Pantalla: Prueba tu aplicación con un lector de pantalla para asegurar que sea accesible para los usuarios con discapacidades visuales. Los lectores de pantalla populares incluyen NVDA, JAWS y VoiceOver.
Implementando la Accesibilidad en el App Router de Next.js
- Usa el Componente Link de Next.js: Usa el componente `<Link>` para la navegación. Proporciona características de accesibilidad incorporadas, como la precarga (prefetching) y la gestión del foco.
- Gestión del Foco: Al navegar entre páginas o abrir modales, asegúrate de que el foco se gestione correctamente. El foco debe establecerse en el elemento más lógico de la nueva página o modal.
- Componentes Personalizados Accesibles: Al crear componentes personalizados, asegúrate de que sean accesibles siguiendo los principios descritos anteriormente. Usa HTML semántico, atributos ARIA y navegación por teclado para que tus componentes sean usables por todos.
- Linting y Pruebas: Usa herramientas de linting como ESLint con plugins de accesibilidad para identificar posibles problemas de accesibilidad en tu código. Además, usa herramientas de prueba automatizadas para probar las violaciones de accesibilidad de tu aplicación.
Conclusión
El sistema de enrutamiento basado en archivos del App Router de Next.js ofrece una forma potente e intuitiva de estructurar y navegar por tus aplicaciones. Al comprender los conceptos básicos y las mejores prácticas descritas en esta guía, puedes construir aplicaciones de Next.js robustas, escalables y mantenibles. Experimenta con las diferentes características del App Router y descubre cómo puede simplificar tu flujo de trabajo de desarrollo y mejorar la experiencia del usuario.