Enrutamiento del lado del cliente en Next.js
Publicado: 2022-03-10Los hipervínculos han sido una de las joyas de la Web desde sus inicios. Según MDN, los hipervínculos son lo que hace que la Web sea una web. Si bien se utiliza para fines como la vinculación entre documentos, su uso principal es hacer referencia a diferentes páginas web identificables por una dirección web única o una URL.
El enrutamiento es un aspecto importante de cada aplicación web tanto como lo son los hipervínculos a la Web. Es un mecanismo a través del cual las solicitudes se enrutan al código que las maneja. En relación con el enrutamiento, las páginas de Next.js son referenciadas e identificables por una ruta de URL única. Si la web consiste en páginas web de navegación interconectadas por hipervínculos , entonces cada aplicación Next.js consta de páginas que se pueden enrutar (controladores de ruta o rutas) interconectadas por un enrutador.
Next.js tiene soporte incorporado para el enrutamiento que puede ser difícil de desempaquetar, especialmente cuando se considera la representación y la obtención de datos. Como requisito previo para comprender el enrutamiento del lado del cliente en Next.js, es necesario tener una visión general de conceptos como el enrutamiento, la representación y la obtención de datos en Next.js.
Este artículo será beneficioso para los desarrolladores de React que estén familiarizados con Next.js y deseen aprender cómo maneja el enrutamiento. Debe tener un conocimiento práctico de React y Next.js para aprovechar al máximo el artículo, que trata únicamente sobre el enrutamiento del lado del cliente y los conceptos relacionados en Next.js.
Enrutamiento y renderizado
El enrutamiento y el renderizado son complementarios entre sí y jugarán un papel importante a lo largo de este artículo. Me gusta cómo los explica Gaurav:
El enrutamiento es el proceso a través del cual el usuario navega a diferentes páginas en un sitio web.
La representación es el proceso de poner esas páginas en la interfaz de usuario. Cada vez que solicita una ruta a una página en particular, también está procesando esa página, pero no todos los renderizados son el resultado de una ruta.
Tómate cinco minutos para pensar en eso.
Lo que debe comprender acerca de la representación en Next.js es que cada página se procesa previamente junto con el código JavaScript mínimo necesario para que se vuelva completamente interactiva a través de un proceso conocido como hidratación. La forma en que Next.js hace esto depende en gran medida de la forma de renderizado previo: generación estática o renderizado del lado del servidor , que están altamente acoplados a la técnica de obtención de datos utilizada y separados por cuándo se genera el HTML para una página.
Según sus requisitos de obtención de datos, es posible que utilice funciones de obtención de datos integradas como getStaticProps
, getStaticPaths
o getServerSideProps
, herramientas de obtención de datos del lado del cliente como SWR, react-query o enfoques tradicionales de obtención de datos como fetch-on- renderizar, buscar y luego renderizar, renderizar mientras se busca (con suspenso).
La representación previa (antes de la representación, a la interfaz de usuario ) es complementaria al enrutamiento y está muy relacionada con la obtención de datos, un tema completo propio en Next.js. Entonces, si bien estos conceptos son complementarios o están estrechamente relacionados, este artículo se centrará únicamente en la mera navegación entre páginas (enrutamiento), con referencias a conceptos relacionados cuando sea necesario.
Con eso fuera del camino, comencemos con la esencia fundamental: Next.js tiene un enrutador basado en un sistema de archivos construido sobre el concepto de páginas.
Paginas
Las páginas en Next.js son componentes de React que están disponibles automáticamente como rutas. Se exportan como exportaciones predeterminadas desde el directorio de páginas con extensiones de archivo admitidas como .js
, .jsx
, .ts
o .tsx
.
Una aplicación típica de Next.js tendrá una estructura de carpetas con directorios de nivel superior como páginas , público y estilos.
next-app ├── node_modules ├── pages │ ├── index.js // path: base-url (/) │ ├── books.jsx // path: /books │ └── book.ts // path: /book ├── public ├── styles ├── .gitignore ├── package.json └── README.md
Cada página es un componente de React:
// pages/books.js — `base-url/book` export default function Book() { return
Libros
}
Nota : tenga en cuenta que las páginas también pueden denominarse "controladores de ruta".
Páginas personalizadas
Estas son páginas especiales que residen en el directorio de páginas pero que no participan en el enrutamiento. Tienen el prefijo del símbolo de guión bajo, como en _app.js
y _document.js
.
-
_app.js
Este es un componente personalizado que reside en la carpeta de páginas. Next.js usa este componente para inicializar páginas. -
_document.js
Al igual que_app.js
,_document.js
es un componente personalizado que utiliza Next.js para aumentar las etiquetas<html>
y<body>
de sus aplicaciones. Esto es necesario porque las páginas de Next.js omiten la definición del marcado del documento circundante.
next-app ├── node_modules ├── pages │ ├── _app.js // ️ Custom page (unavailable as a route) │ ├── _document.jsx // ️ Custom page (unavailable as a route) │ └── index.ts // path: base-url (/) ├── public ├── styles ├── .gitignore ├── package.json └── README.md
Vinculación entre páginas
Next.js expone un componente de Link
de la API next/link
que se puede usar para realizar transiciones de ruta del lado del cliente entre páginas.
// Import the <Link/> component import Link from "next/link"; // This could be a page component export default function TopNav() { return ( <nav> <Link href="/">Home</Link> <Link href="/">Publications</Link> <Link href="/">About</Link> </nav> ) } // This could be a non-page component export default function Publications() { return ( <section> <TopNav/> {/* ... */} </section> ) }
El componente Link
se puede utilizar dentro de cualquier componente, página o no. Cuando se usa en su forma más básica, como en el ejemplo anterior, el componente Link
se traduce en un hipervínculo con un atributo href
. (Más información sobre el Link
en la siguiente sección/enlace a continuación).
Enrutamiento
El sistema de enrutamiento basado en archivos Next.js se puede usar para definir los patrones de ruta más comunes. Para adaptarse a estos patrones, cada ruta se separa en función de su definición.
Índice Rutas
De forma predeterminada, en su aplicación Next.js, la ruta inicial/predeterminada es pages/index.js
, que sirve automáticamente como punto de partida de su aplicación como /
. Con una URL base de localhost:3000
, se puede acceder a esta ruta de índice en el nivel de URL base de la aplicación en el navegador.
Las rutas de índice actúan automáticamente como la ruta predeterminada para cada directorio y pueden eliminar las redundancias de nombres. La siguiente estructura de directorios expone dos rutas de ruta: /
y /home
.
next-app └── pages ├── index.js // path: base-url (/) └── home.js // path: /home
La eliminación es más evidente con las rutas anidadas .
Rutas anidadas
Una ruta como pages/book
tiene un nivel de profundidad. Profundizar es crear rutas anidadas, lo que requiere una estructura de carpetas anidadas. Con una URL base de https://www.smashingmagazine.com
, puede acceder a la ruta https://www.smashingmagazine.com/printed-books/printed-books
creando una estructura de carpetas similar a la siguiente:
next-app └── pages ├── index.js // top index route └── printed-books // nested route └── printed-books.js // path: /printed-books/printed-books
O elimine la redundancia de ruta con rutas de índice y acceda a la ruta para libros impresos en https://www.smashingmagazine.com/printed-books
.
next-app └── pages ├── index.js // top index route └── printed-books // nested route └── index.js // path: /printed-books
Las rutas dinámicas también juegan un papel importante en la eliminación de redundancias.
Rutas Dinámicas
Del ejemplo anterior, usamos la ruta de índice para acceder a todos los libros impresos. Para acceder a libros individuales es necesario crear diferentes rutas para cada libro como:
// ️ Don't do this. next-app └── pages ├── index.js // top index route └── printed-books // nested route ├── index.js // path: /printed-books ├── typesript-in-50-lessons.js // path: /printed-books/typesript-in-50-lessons ├── checklist-cards.js // path: /printed-books/checklist-cards ├── ethical-design-handbook.js // path: /printed-books/ethical-design-handbook ├── inclusive-components.js // path: /printed-books/inclusive-components └── click.js // path: /printed-books/click
que es altamente redundante, no escalable y puede remediarse con rutas dinámicas como:
// Do this instead. next-app └── pages ├── index.js // top index route └── printed-books ├── index.js // path: /printed-books └── [book-id].js // path: /printed-books/:book-id
La sintaxis de paréntesis — [book-id]
— es el segmento dinámico y no se limita solo a los archivos. También se puede usar con carpetas como el ejemplo a continuación, haciendo que el autor esté disponible en la ruta /printed-books/:book-id/author
.
next-app └── pages ├── index.js // top index route └── printed-books ├── index.js // path: /printed-books └── [book-id] └── author.js // path: /printed-books/:book-id/author
El (los) segmento (s) dinámico (s) de una ruta se exponen como un parámetro de consulta al que se puede acceder en cualquiera de los componentes de conexión involucrados en la ruta con el objeto de query
del gancho useRouter()
— (Más sobre esto en la siguiente sección de la API del enrutador) ).
// printed-books/:book-id import { useRouter } from 'next/router'; export default function Book() { const { query } = useRouter(); return ( <div> <h1> book-id <em>{query['book-id']}</em> </h1> </div> ); }
// /printed-books/:book-id/author import { useRouter } from 'next/router'; export default function Author() { const { query } = useRouter(); return ( <div> <h1> Fetch author with book-id <em>{query['book-id']}</em> </h1> </div> ); }
Ampliación de segmentos de rutas dinámicas con Catch All Routes
Ha visto la sintaxis de corchete de segmento de ruta dinámica como en el ejemplo anterior con [book-id].js
. La belleza de esta sintaxis es que lleva las cosas aún más lejos con Catch-All Routes . Puede inferir lo que esto hace por el nombre: captura todas las rutas.
Cuando observamos el ejemplo dinámico, aprendimos cómo ayuda a eliminar la redundancia en la creación de archivos para que una sola ruta acceda a varios libros con su ID. Pero hay algo más que podríamos haber hecho.
Específicamente, teníamos la ruta /printed-books/:book-id
, con una estructura de directorios:
next-app └── pages ├── index.js └── printed-books ├── index.js └── [book-id].js
Si actualizamos la ruta para tener más segmentos como categorías, podríamos terminar con algo como: /printed-books/design/:book-id
, /printed-books/engineering/:book-id
, o mejor aún /printed-books/:category/:book-id
.
Agreguemos el año de publicación: /printed-books/:category/:release-year/:book-id
. ¿Puedes ver un patrón? La estructura del directorio se convierte en:
next-app └── pages ├── index.js └── printed-books └── [category] └── [release-year] └── [book-id].js
Sustituimos el uso de archivos con nombre por rutas dinámicas, pero de alguna manera terminamos con otra forma de redundancia. Bueno, hay una solución: Capturar todas las rutas que elimina la necesidad de rutas profundamente anidadas:
next-app └── pages ├── index.js └── printed-books └── [...slug].js
Utiliza la misma sintaxis de paréntesis excepto que tiene el prefijo de tres puntos. Piense en los puntos como la sintaxis extendida de JavaScript. Quizás se pregunte: si uso las rutas generales, ¿cómo accedo a la categoría ( [category]
) y al año de publicación ( [release-year]
). Dos caminos:
- En el caso del ejemplo de los libros impresos, el objetivo final es el libro, y la información de cada libro tendrá sus metadatos adjuntos, o
- Los segmentos "slug" se devuelven como una matriz de parámetros de consulta.
import { useRouter } from 'next/router'; export default function Book() { const { query } = useRouter(); // There's a brief moment where `slug` is undefined // so we use the Optional Chaining (?.) and Nullish coalescing operator (??) // to check if slug is undefined, then fall back to an empty array const [category, releaseYear, bookId] = query?.slug ?? []; return ( <table> <tbody> <tr> <th>Book Id</th> <td>{bookId}</td> </tr> <tr> <th>Category</th> <td>{category}</td> </tr> <tr> <th>Release Year</th> <td>{releaseYear}</td> </tr> </tbody> </table> ); }
Aquí hay más ejemplos para la ruta /printed-books/[…slug]
:
Sendero | Parámetro de consulta |
---|---|
/printed-books/click.js | { “slug”: [“clic”] } |
/printed-books/2020/click.js | { "babosa": ["2020", "clic"] } |
/printed-books/design/2020/click.js | { “slug”: [“diseño”, “2020”, “clic”] } |
Como ocurre con la ruta catch-all, la ruta /printed-books
generará un error 404 a menos que proporcione una ruta de índice de reserva.
next-app └── pages ├── index.js └── printed-books ├── index.js // path: /printed-books └── [...slug].js
Esto se debe a que la ruta general es "estricta". O coincide con un slug o arroja un error. Si desea evitar crear rutas de índice junto con rutas generales, puede usar las rutas generales opcionales en su lugar.
Ampliación de segmentos de rutas dinámicas con rutas Catch-All opcionales
La sintaxis es la misma que la de catch-all-routes, pero con corchetes dobles en su lugar.
next-app └── pages ├── index.js └── printed-books └── [[...slug]].js
En este caso, la ruta catch-all (slug) es opcional y, si no está disponible, recurre a la ruta /printed-books
, representada con el controlador de ruta [[…slug]].js
, sin ningún parámetro de consulta.
Utilice rutas catch-all junto con rutas de índice o solo rutas catch-all opcionales. Evite el uso de rutas catch-all y catch-all opcionales al lado.
Prioridad de rutas
La capacidad de poder definir los patrones de enrutamiento más comunes puede ser un "cisne negro". La posibilidad de que las rutas entren en conflicto es una amenaza inminente, especialmente cuando comienza a trabajar con rutas dinámicas.
Cuando tiene sentido hacerlo, Next.js le informa sobre los conflictos de rutas en forma de errores. Cuando no es así, aplica prioridad a las rutas según su especificidad.
Por ejemplo, es un error tener más de una ruta dinámica en el mismo nivel.
// This is an error // Failed to reload dynamic routes: Error: You cannot use different slug names for the // same dynamic path ('book-id' !== 'id'). next-app └── pages ├── index.js └── printed-books ├── [book-id].js └── [id].js
Si observa detenidamente las rutas definidas a continuación, notará la posibilidad de conflictos.
// Directory structure flattened for simplicity next-app └── pages ├── index.js // index route (also a predefined route) └── printed-books ├── index.js ├── tags.js // predefined route ├── [book-id].js // handles dynamic route └── [...slug].js // handles catch all route
Por ejemplo, intente responder esto: ¿qué ruta maneja la ruta /printed-books/inclusive-components
?
-
/printed-books/[book-id].js
, o -
/printed-books/[…slug].js
.
La respuesta radica en la "especificidad" de los controladores de ruta. Las rutas predefinidas son lo primero, seguidas de las rutas dinámicas y luego las rutas generales. Puede pensar en el modelo de manejo/solicitud de ruta como un pseudocódigo con los siguientes pasos:
- ¿Hay un controlador de ruta predefinido que pueda manejar la ruta?
-
true
: maneja la solicitud de ruta. -
false
: vaya a 2.
-
- ¿Hay un controlador de ruta dinámico que pueda manejar la ruta?
-
true
: maneja la solicitud de ruta. -
false
: vaya a 3.
-
- ¿Hay un controlador de ruta general que pueda manejar la ruta?
-
true
: maneja la solicitud de ruta. -
false
: lanza una página 404 no encontrada.
-
Por lo tanto, /printed-books/[book-id].js
gana.
Aquí hay más ejemplos:
Ruta | Controlador de ruta | tipo de ruta |
---|---|---|
/printed-books | /printed-books | Índice de ruta |
/printed-books/tags | /printed-books/tags.js | Ruta predefinida |
/printed-books/inclusive-components | /printed-books/[book-id].js | Ruta Dinámica |
/printed-books/design/inclusive-components | /printed-books/[...slug].js | Ruta general |
La API next/link
La API next/link
expone el componente Link
como una forma declarativa de realizar transiciones de ruta del lado del cliente.
import Link from 'next/link' function TopNav() { return ( <nav> <Link href="/">Smashing Magazine</Link> <Link href="/articles">Articles</Link> <Link href="/guides">Guides</Link> <Link href="/printed-books">Books</Link> </nav> ) }
El componente Link
se resolverá en un hipervínculo HTML normal. Es decir, <Link href="/">Smashing Magazine</Link>
se resolverá como <a href="/">Smashing Magazine</a>
.
La propiedad href
es la única propiedad necesaria para el componente Link
. Consulte los documentos para obtener una lista completa de accesorios disponibles en el componente Link
.
Hay otros mecanismos del componente Link
a tener en cuenta.
Rutas con segmentos dinámicos
Antes de Link
9.5.3, la vinculación a rutas dinámicas significaba que tenía que proporcionar tanto el href
as
accesorio a Link
como en:
import Link from 'next/link'; const printedBooks = [ { name: 'Ethical Design', id: 'ethical-design' }, { name: 'Design Systems', id: 'design-systems' }, ]; export default function PrintedBooks() { return printedBooks.map((printedBook) => ( <Link href="/printed-books/[printed-book-id]" as={`/printed-books/${printedBook.id}`} > {printedBook.name} </Link> )); }
Aunque esto permitió que Next.js interpolara el href para los parámetros dinámicos, era tedioso, propenso a errores y algo imperativo, y ahora se solucionó para la mayoría de los casos de uso con el lanzamiento de Next.js 10.
Esta solución también es compatible con versiones anteriores. Si ha estado usando as
y href
, nada se rompe. Para adoptar la nueva sintaxis, descarte href
prop y su valor, y cambie el nombre de as
prop a href
como en el siguiente ejemplo:
import Link from 'next/link'; const printedBooks = [ { name: 'Ethical Design', id: 'ethical-design' }, { name: 'Design Systems', id: 'design-systems' }, ]; export default function PrintedBooks() { return printedBooks.map((printedBook) => ( <Link href={`/printed-books/${printedBook.id}`}>{printedBook.name}</Link> )); }
Consulte Resolución automática de href.
Casos de uso para la passHref
Eche un vistazo de cerca al fragmento a continuación:
import Link from 'next/link'; const printedBooks = [ { name: 'Ethical Design', id: 'ethical-design' }, { name: 'Design Systems', id: 'design-systems' }, ]; // Say this has some sort of base styling attached function CustomLink({ href, name }) { return <a href={href}>{name}</a>; } export default function PrintedBooks() { return printedBooks.map((printedBook) => ( <Link href={`/printed-books/${printedBook.id}`} passHref> <CustomLink name={printedBook.name} /> </Link> )); }
Los accesorios passHref
obligan al componente Link
a pasar el accesorio href
al componente secundario CustomLink
. Esto es obligatorio si el componente Link
envuelve un componente que devuelve una etiqueta de hipervínculo <a>
. Su caso de uso podría deberse a que está utilizando una biblioteca como componentes con estilo, o si necesita pasar varios elementos secundarios al componente Link
, ya que solo espera un solo elemento secundario.
Consulte los documentos para obtener más información.
Objetos de URL
El href
prop del componente Link
también puede ser un objeto de URL con propiedades como query
que se formatea automáticamente en una cadena de URL.
Con el objeto printedBooks
, el siguiente ejemplo se vinculará a:
-
/printed-books/ethical-design?name=Ethical+Design
y -
/printed-books/design-systems?name=Design+Systems
.
import Link from 'next/link'; const printedBooks = [ { name: 'Ethical Design', id: 'ethical-design' }, { name: 'Design Systems', id: 'design-systems' }, ]; export default function PrintedBooks() { return printedBooks.map((printedBook) => ( <Link href={{ pathname: `/printed-books/${printedBook.id}`, query: { name: `${printedBook.name}` }, }} > {printedBook.name} </Link> )); }
Si incluye un segmento dinámico en el nombre de la pathname
, también debe incluirlo como una propiedad en el objeto de consulta para asegurarse de que la consulta se interpole en el nombre de la pathname
:
import Link from 'next/link'; const printedBooks = [ { name: 'Ethical Design', id: 'ethical-design' }, { name: 'Design Systems', id: 'design-systems' }, ]; // In this case the dynamic segment `[book-id]` in pathname // maps directly to the query param `book-id` export default function PrintedBooks() { return printedBooks.map((printedBook) => ( <Link href={{ pathname: `/printed-books/[book-id]`, query: { 'book-id': `${printedBook.id}` }, }} > {printedBook.name} </Link> )); }
El ejemplo anterior tiene rutas:
-
/printed-books/ethical-design
, y -
/printed-books/design-systems
.
Si inspecciona el atributo href
en VSCode, encontrará el tipo LinkProps
, con la propiedad href
un tipo Url
, que es una string
o UrlObject
como se mencionó anteriormente.
La inspección de UrlObject
conduce a la interfaz con las propiedades:
Puede obtener más información sobre estas propiedades en la documentación del módulo URL de Node.js.
Un caso de uso del hash es vincular a secciones específicas de una página.
import Link from 'next/link'; const printedBooks = [{ name: 'Ethical Design', id: 'ethical-design' }]; export default function PrintedBooks() { return printedBooks.map((printedBook) => ( <Link href={{ pathname: `/printed-books/${printedBook.id}`, hash: 'faq', }} > {printedBook.name} </Link> )); }
El hipervínculo se resolverá en /printed-books/ethical-design#faq
.
Obtenga más información en los documentos.
La next/router
Si next/link
es declarativo, entonces next/router
es imperativo. Expone un gancho useRouter
que permite el acceso al objeto del router
dentro de cualquier componente de función. Puede usar este gancho para realizar el enrutamiento manualmente, especialmente en ciertos escenarios donde el next/link
no es suficiente, o donde necesita "enganchar" al enrutamiento.
import { useRouter } from 'next/router'; export default function Home() { const router = useRouter(); function handleClick(e) { e.preventDefault(); router.push(href); } return ( <button type="button" onClick={handleClick}>Click me</button> ) }
useRouter
es un enlace de React y no se puede usar con clases. ¿Necesita el objeto de router
en los componentes de clase? Usar withRouter
.
import { withRouter } from 'next/router'; function Home({router}) { function handleClick(e) { e.preventDefault(); router.push(href); } return ( <button type="button" onClick={handleClick}>Click me</button> ) } export default withRouter(Home);
El objeto router
Tanto el useRouter
como el componente de orden superior withRouter
devuelven un objeto de enrutador con propiedades como pathname
, query
, asPath
y basePath
que le brindan información sobre el estado de URL de la página actual, locale
, locales
y defaultLocale
que brinda información sobre el configuración regional predeterminada activa, admitida o actual.
El objeto de enrutador también tiene métodos como push
para navegar a una nueva URL agregando una nueva entrada de URL en la pila de historial, replace
, similar a empujar pero reemplaza la URL actual en lugar de agregar una nueva entrada de URL en la pila de historial.
Obtenga más información sobre el objeto de enrutador.
Configuración de ruta personalizada con next.config.js
Este es un módulo normal de Node.js que se puede usar para configurar cierto comportamiento de Next.js.
module.exports = { // configuration options }
Recuerde reiniciar su servidor cada vez que actualice
next.config.js
. Aprende más.
Ruta básica
Se mencionó que la ruta inicial/predeterminada en Next.js es pages/index.js
con la ruta /
. Esto es configurable y puede hacer que su ruta predeterminada sea una ruta secundaria del dominio.
module.exports = { // old default path: / // new default path: /dashboard basePath: '/dashboard', };
Estos cambios surtirán efecto automáticamente en su aplicación con todas las rutas enrutadas a /
/dashboard
.
Esta característica solo se puede usar con Next.js 9.5 y superior. Aprende más.
Barra diagonal
De forma predeterminada, una barra diagonal final no estará disponible al final de cada URL. Sin embargo, puedes cambiar eso con:
module.exports = { trailingSlash: true };
# trailingSlash: false /printed-books/ethical-design#faq # trailingSlash: true /printed-books/ethical-design/#faq
Tanto la ruta base como las funciones de barra inclinada final solo se pueden usar con Next.js 9.5 y versiones posteriores.
Conclusión
El enrutamiento es una de las partes más importantes de su aplicación Next.js y se refleja en el enrutador basado en el sistema de archivos construido sobre el concepto de páginas. Las páginas se pueden utilizar para definir los patrones de ruta más comunes. Los conceptos de enrutamiento y renderizado están estrechamente relacionados. Tome las lecciones de este artículo con usted mientras crea su propia aplicación Next.js o trabaja en una base de código de Next.js. Y consulte los recursos a continuación para obtener más información.
Recursos Relacionados
- Documentación oficial de Next.js para Pages
- Documentación oficial de Next.js para la obtención de datos
- Documentación oficial de Next.js para next.config.js
- Next.js 10: resolución automática de
href
- Documentación oficial de Next.js para next/link
- Documentación oficial de Next.js para next/router