Roteamento do lado do cliente no Next.js
Publicados: 2022-03-10Os hiperlinks têm sido uma das jóias da Web desde seu início. De acordo com o MDN, os hiperlinks são o que torna a Web, uma web. Embora usado para fins como links entre documentos, seu uso principal é fazer referência a diferentes páginas da Web identificáveis por um endereço da Web exclusivo ou um URL.
O roteamento é um aspecto importante de cada aplicativo da Web, tanto quanto os hiperlinks são para a Web. É um mecanismo através do qual os pedidos são encaminhados para o código que os trata. Em relação ao roteamento, as páginas Next.js são referenciadas e identificáveis por um caminho de URL exclusivo. Se a Web consistir em páginas da Web de navegação interconectadas por hiperlinks , cada aplicativo Next.js consistirá em páginas roteáveis (manipuladores ou rotas de rota) interconectadas por um roteador.
O Next.js tem suporte integrado para roteamento que pode ser difícil de descompactar, especialmente ao considerar renderização e busca de dados. Como pré-requisito para entender o roteamento do lado do cliente em Next.js, é necessário ter uma visão geral de conceitos como roteamento, renderização e busca de dados em Next.js.
Este artigo será útil para desenvolvedores React que estão familiarizados com Next.js e querem aprender como ele lida com roteamento. Você precisa ter um conhecimento prático de React e Next.js para tirar o máximo proveito do artigo, que é apenas sobre roteamento do lado do cliente e conceitos relacionados em Next.js.
Roteamento e renderização
Roteamento e Renderização são complementares entre si e desempenharão um papel importante ao longo deste artigo. Eu gosto de como Gaurav os explica:
Roteamento é o processo pelo qual o usuário é navegado para diferentes páginas em um site.
A renderização é o processo de colocar essas páginas na interface do usuário. Toda vez que você solicita uma rota para uma página específica, você também está renderizando essa página, mas nem toda renderização é resultado de uma rota.
Reserve cinco minutos para pensar sobre isso.
O que você precisa entender sobre renderização no Next.js é que cada página é pré-renderizada com antecedência junto com o código JavaScript mínimo necessário para que ela se torne totalmente interativa por meio de um processo conhecido como hidratação. Como o Next.js faz isso depende muito da forma de pré-renderização: geração estática ou renderização do lado do servidor , que são altamente acopladas à técnica de busca de dados usada e separadas por quando o HTML de uma página é gerado.
Dependendo dos seus requisitos de busca de dados, você pode usar funções de busca de dados integradas como getStaticProps
, getStaticPaths
ou, getServerSideProps
, ferramentas de busca de dados do lado do cliente como SWR, react-query ou abordagens tradicionais de busca de dados como fetch-on- renderizar, buscar e renderizar, renderizar conforme você busca (com Suspense).
A pré-renderização (antes da renderização — para a interface do usuário ) é complementar ao Roteamento e altamente associada à busca de dados — um tópico próprio em Next.js. Portanto, embora esses conceitos sejam complementares ou intimamente relacionados, este artigo será focado apenas na mera navegação entre páginas (roteamento), com referências a conceitos relacionados quando necessário.
Com isso fora do caminho, vamos começar com a essência fundamental: Next.js tem um roteador baseado em sistema de arquivos construído sobre o conceito de páginas.
Páginas
Páginas em Next.js são componentes React que estão automaticamente disponíveis como rotas. Eles são exportados como exportações padrão do diretório de páginas com extensões de arquivo compatíveis, como .js
, .jsx
, .ts
ou .tsx
.
Um aplicativo Next.js típico terá uma estrutura de pastas com diretórios de nível superior como pages , public e styles.
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 é um componente React:
// pages/books.js — `base-url/book` export default function Book() { return
Livros
}
Nota : Lembre-se de que as páginas também podem ser chamadas de “manipuladores de rota”.
Páginas personalizadas
Essas são páginas especiais que residem no diretório de páginas , mas não participam do roteamento. Eles são prefixados com o símbolo de sublinhado, como em _app.js
e _document.js
.
-
_app.js
Este é um componente personalizado que reside na pasta de páginas. Next.js usa esse componente para inicializar páginas. -
_document.js
Assim como_app.js
,_document.js
é um componente personalizado que o Next.js usa para aumentar as tags<html>
e<body>
de seus aplicativos. Isso é necessário porque as páginas Next.js ignoram a definição da marcação do 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
Vinculação entre páginas
Next.js expõe um componente Link
da API next/link
que pode ser usado para realizar transições de rota do lado do cliente entre as 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> ) }
O componente Link
pode ser usado dentro de qualquer componente, página ou não. Quando usado em sua forma mais básica, como no exemplo acima, o componente Link
se traduz em um hiperlink com um atributo href
. (Mais sobre Link
na próxima seção/link abaixo.)
Roteamento
O sistema de roteamento baseado em arquivo Next.js pode ser usado para definir os padrões de roteamento mais comuns. Para acomodar esses padrões, cada rota é separada com base em sua definição.
Rotas de índice
Por padrão, em seu aplicativo Next.js, a rota inicial/padrão é pages/index.js
, que serve automaticamente como ponto de partida de seu aplicativo como /
. Com uma URL base de localhost:3000
, essa rota de índice pode ser acessada no nível de URL base do aplicativo no navegador.
As rotas de índice agem automaticamente como a rota padrão para cada diretório e podem eliminar redundâncias de nomenclatura. A estrutura de diretórios abaixo expõe dois caminhos de rota: /
e /home
.
next-app └── pages ├── index.js // path: base-url (/) └── home.js // path: /home
A eliminação é mais aparente com rotas aninhadas .
Rotas aninhadas
Uma rota como pages/book
tem um nível de profundidade. Para ir mais fundo é criar rotas aninhadas, o que requer uma estrutura de pastas aninhadas. Com um URL base de https://www.smashingmagazine.com
, você pode acessar a rota https://www.smashingmagazine.com/printed-books/printed-books
criando uma estrutura de pastas semelhante à abaixo:
next-app └── pages ├── index.js // top index route └── printed-books // nested route └── printed-books.js // path: /printed-books/printed-books
Ou elimine a redundância de caminho com rotas de índice e acesse a rota para livros impressos em https://www.smashingmagazine.com/printed-books
.
next-app └── pages ├── index.js // top index route └── printed-books // nested route └── index.js // path: /printed-books
As rotas dinâmicas também desempenham um papel importante na eliminação de redundâncias.
Rotas dinâmicas
Do exemplo anterior, usamos a rota de índice para acessar todos os livros impressos. Para acessar livros individuais, é necessário criar rotas diferentes para cada livro, 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 é altamente redundante, não escalável e pode ser corrigido com rotas 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
A sintaxe de colchetes — [book-id]
— é o segmento dinâmico e não se limita apenas a arquivos. Também pode ser usado com pastas como no exemplo abaixo, disponibilizando o autor na rota /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
Os segmentos dinâmicos de uma rota são expostos como um parâmetro de consulta que pode ser acessado em qualquer um dos componentes de conexão envolvidos na rota com o objeto de query
do gancho useRouter()
— (Mais sobre isso na seção API next/router ).
// 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> ); }
Estendendo segmentos de rotas dinâmicas com captura de todas as rotas
Você viu a sintaxe de colchetes de segmento de rota dinâmica como no exemplo anterior com [book-id].js
. A beleza dessa sintaxe é que ela leva as coisas ainda mais longe com Catch-All Routes . Você pode inferir o que isso faz a partir do nome: ele pega todas as rotas.
Quando analisamos o exemplo dinâmico, aprendemos como ele ajuda a eliminar a redundância de criação de arquivos para uma única rota para acessar vários livros com seu ID. Mas há outra coisa que poderíamos ter feito.
Especificamente, tínhamos o caminho /printed-books/:book-id
, com uma estrutura de diretórios:
next-app └── pages ├── index.js └── printed-books ├── index.js └── [book-id].js
Se atualizarmos o caminho para ter mais segmentos como categorias, podemos acabar com algo como: /printed-books/design/:book-id
, /printed-books/engineering/:book-id
, ou melhor ainda /printed-books/:category/:book-id
.
Vamos adicionar o ano de lançamento: /printed-books/:category/:release-year/:book-id
. Você pode ver um padrão? A estrutura de diretórios se torna:
next-app └── pages ├── index.js └── printed-books └── [category] └── [release-year] └── [book-id].js
Substituímos o uso de arquivos nomeados por rotas dinâmicas, mas de alguma forma ainda acabamos com outra forma de redundância. Bem, há uma correção: Catch All Routes que elimina a necessidade de rotas profundamente aninhadas:
next-app └── pages ├── index.js └── printed-books └── [...slug].js
Ele usa a mesma sintaxe de colchetes, exceto que é prefixado com três pontos. Pense nos pontos como a sintaxe de propagação do JavaScript. Você pode estar se perguntando: se eu usar as rotas catch-all, como acesso a categoria ( [category]
) e o ano de lançamento ( [release-year]
). Dois caminhos:
- No caso do exemplo de livros impressos, o objetivo final é o livro, e cada informação de livro terá seus metadados anexados a ele, ou
- Os segmentos “slug” são retornados como uma 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> ); }
Aqui está mais exemplo para a rota /printed-books/[…slug]
:
Caminho | Parâmetro de consulta |
---|---|
/printed-books/click.js | { “slug”: [“clique”] } |
/printed-books/2020/click.js | { “slug”: [“2020”, “clique”] } |
/printed-books/design/2020/click.js | { “slug”: [“design”, “2020”, “clique”] } |
Como acontece com a rota catch-all, a rota /printed-books
lançará um erro 404, a menos que você forneça uma rota de índice de fallback.
next-app └── pages ├── index.js └── printed-books ├── index.js // path: /printed-books └── [...slug].js
Isso ocorre porque a rota catch-all é “estrita”. Ele corresponde a um slug ou gera um erro. Se quiser evitar a criação de rotas de índice ao lado de rotas catch-all, você pode usar as rotas catch-all opcionais .
Estendendo segmentos de rotas dinâmicas com rotas catch-all opcionais
A sintaxe é a mesma do catch-all-routes, mas com colchetes duplos.
next-app └── pages ├── index.js └── printed-books └── [[...slug]].js
Nesse caso, a rota catch-all (slug) é opcional e, se não estiver disponível, faz fallbacks para o caminho /printed-books
, renderizado com o manipulador de rota [[…slug]].js
, sem nenhum parâmetro de consulta.
Use catch-all junto com rotas de índice ou apenas rotas catch-all opcionais. Evite usar rotas catch-all e catch-all opcionais ao lado.
Precedência de rotas
A capacidade de definir os padrões de roteamento mais comuns pode ser um “cisne negro”. A possibilidade de conflito de rotas é uma ameaça iminente, principalmente quando você começa a trabalhar com rotas dinâmicas.
Quando faz sentido fazer isso, o Next.js informa sobre conflitos de rota na forma de erros. Quando não, aplica precedência às rotas de acordo com sua especificidade.
Por exemplo, é um erro ter mais de uma rota dinâmica no mesmo nível.
// 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
Se você observar atentamente as rotas definidas abaixo, notará o potencial de confrontos.
// 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 exemplo, tente responder a isto: qual rota lida com o caminho /printed-books/inclusive-components
?
-
/printed-books/[book-id].js
ou -
/printed-books/[…slug].js
.
A resposta está na “especificidade” dos manipuladores de rotas. As rotas predefinidas vêm primeiro, seguidas pelas rotas dinâmicas e, em seguida, pelas rotas abrangentes. Você pode pensar no modelo de solicitação/tratamento de rota como um pseudocódigo com as seguintes etapas:
- Existe um manipulador de rota predefinido que pode manipular a rota?
-
true
— trata da solicitação de rota. -
false
— vá para 2.
-
- Existe um manipulador de rota dinâmica que pode manipular a rota?
-
true
— trata da solicitação de rota. -
false
— vá para 3.
-
- Existe um manipulador de rota abrangente que pode lidar com a rota?
-
true
— trata da solicitação de rota. -
false
— lança uma página 404 não encontrada.
-
Portanto, /printed-books/[book-id].js
vence.
Aqui estão mais exemplos:
Rota | Gerenciador de rota | Tipo de rota |
---|---|---|
/printed-books | /printed-books | Rota do índice |
/printed-books/tags | /printed-books/tags.js | Rota predefinida |
/printed-books/inclusive-components | /printed-books/[book-id].js | Rota Dinâmica |
/printed-books/design/inclusive-components | /printed-books/[...slug].js | Rota pega-tudo |
A next/link
API
A API next/link
expõe o componente Link
como uma forma declarativa de realizar transições de rota do lado do 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> ) }
O componente Link
será resolvido para um hiperlink HTML normal. Ou seja, a <Link href="/">Smashing Magazine</Link>
se transformará em <a href="/">Smashing Magazine</a>
.
A prop href
é a única prop necessária para o componente Link
. Consulte os documentos para obter uma lista completa de adereços disponíveis no componente Link
.
Existem outros mecanismos do componente Link
que você deve conhecer.
Rotas com segmentos dinâmicos
Antes do Next.js 9.5.3, Link
a rotas dinâmicas significava que você tinha que fornecer o href
e as
prop para Link
como em:
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> )); }
Embora isso permitisse ao Next.js interpolar o href para os parâmetros dinâmicos, era tedioso, propenso a erros e um tanto imperativo, e agora foi corrigido para a maioria dos casos de uso com o lançamento do Next.js 10.
Essa correção também é compatível com versões anteriores. Se você estiver usando as
e href
, nada quebra. Para adotar a nova sintaxe, descarte a prop href
e seu valor, e renomeie as
prop para href
como no exemplo abaixo:
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 Resolução automática de href.
Casos de uso para o passHref
Prop
Dê uma olhada no trecho abaixo:
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> )); }
As props passHref
forçam o componente Link
a passar a prop href
para o componente filho CustomLink
. Isso é obrigatório se o componente Link
envolver um componente que retorna uma tag <a>
de hiperlink. Seu caso de uso pode ser porque você está usando uma biblioteca como styled-components, ou se você precisa passar vários filhos para o componente Link
, pois ele espera apenas um único filho.
Consulte os documentos para saber mais.
Objetos de URL
A prop href
do componente Link
também pode ser um objeto URL com propriedades como query
que é formatada automaticamente em uma string de URL.
Com o objeto printedBooks
, o exemplo abaixo será vinculado a:
-
/printed-books/ethical-design?name=Ethical+Design
e -
/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> )); }
Se você incluir um segmento dinâmico no pathname
, também deverá incluí-lo como uma propriedade no objeto de consulta para garantir que a consulta seja interpolada no 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> )); }
O exemplo acima tem caminhos:
-
/printed-books/ethical-design
, e -
/printed-books/design-systems
.
Se você inspecionar o atributo href
no VSCode, encontrará o tipo LinkProps
, com a propriedade href
um tipo Url
, que é uma string
ou UrlObject
, conforme mencionado anteriormente.
Inspecionar o UrlObject
ainda leva à interface com as propriedades:
Você pode saber mais sobre essas propriedades na documentação do módulo de URL do Node.js.
Um caso de uso do hash é vincular a seções específicas em uma 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> )); }
O hiperlink será resolvido para /printed-books/ethical-design#faq
.
Saiba mais nos documentos.
A API do next/router
Se o next/link
é declarativo, então o next/router
é imperativo. Ele expõe um gancho useRouter
que permite o acesso ao objeto router
dentro de qualquer componente de função. Você pode usar este gancho para executar o roteamento manualmente, principalmente em certos cenários onde o next/link
não é suficiente, ou onde você precisa “ligar” no roteamento.
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
é um gancho React e não pode ser usado com classes. Precisa do objeto router
em componentes de classe? Use 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);
O objeto router
Tanto o gancho useRouter
quanto o componente de ordem superior withRouter
retornam um objeto roteador com propriedades como pathname
, query
, asPath
e basePath
que fornece informações sobre o estado da URL da página atual, locale
, locales
e defaultLocale
que fornece informações sobre o localidade padrão ativa, suportada ou atual.
O objeto roteador também possui métodos como push
para navegar para uma nova URL adicionando uma nova entrada de URL na pilha de histórico, replace
, semelhante a push, mas substitui a URL atual em vez de adicionar uma nova entrada de URL na pilha de histórico.
Saiba mais sobre o objeto roteador.
Configuração de rota personalizada com next.config.js
Este é um módulo normal do Node.js que pode ser usado para configurar determinado comportamento do Next.js.
module.exports = { // configuration options }
Lembre-se de reiniciar seu servidor sempre que atualizar
next.config.js
. Saber mais.
Caminho básico
Foi mencionado que a rota inicial/padrão em Next.js é pages/index.js
com caminho /
. Isso é configurável e você pode tornar sua rota padrão um subcaminho do domínio.
module.exports = { // old default path: / // new default path: /dashboard basePath: '/dashboard', };
Essas alterações entrarão em vigor automaticamente em seu aplicativo com todos os /
caminhos roteados para /dashboard
.
Esse recurso só pode ser usado com Next.js 9.5 e superior. Saber mais.
Barra à direita
Por padrão, uma barra final não estará disponível no final de cada URL. No entanto, você pode mudar isso com:
module.exports = { trailingSlash: true };
# trailingSlash: false /printed-books/ethical-design#faq # trailingSlash: true /printed-books/ethical-design/#faq
Os recursos de caminho base e barra final só podem ser usados com o Next.js 9.5 e superior.
Conclusão
O roteamento é uma das partes mais importantes do seu aplicativo Next.js e reflete no roteador baseado em sistema de arquivos construído no conceito de páginas. As páginas podem ser usadas para definir os padrões de rota mais comuns. Os conceitos de roteamento e renderização estão intimamente relacionados. Leve as lições deste artigo com você enquanto cria seu próprio aplicativo Next.js ou trabalha em uma base de código Next.js. E confira os recursos abaixo para saber mais.
Recursos Relacionados
- Documentação oficial do Next.js para páginas
- Documentação oficial do Next.js para busca de dados
- Documentação oficial do Next.js para o next.config.js
- Next.js 10: resolução automática de
href
- Documentação oficial do Next.js para next/link
- Documentação oficial do Next.js para o próximo/roteador