Roteamento do lado do cliente no Next.js

Publicados: 2022-03-10
Resumo rápido ↬ O Next.js possui um sistema de roteamento baseado em arquivo no qual cada página se torna automaticamente uma rota com base no nome do arquivo. Cada página é um componente React exportado padrão do diretório de páginas que pode ser usado para definir os padrões de rota mais comuns. Este artigo o guiará por quase tudo o que você precisa saber sobre o roteamento no Next.js e o direcionará para tópicos e conceitos relacionados.

Os 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.

Mais depois do salto! Continue lendo abaixo ↓

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:

  1. 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
  2. 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:

  1. Existe um manipulador de rota predefinido que pode manipular a rota?
    • true — trata da solicitação de rota.
    • false — vá para 2.
  2. Existe um manipulador de rota dinâmica que pode manipular a rota?
    • true — trata da solicitação de rota.
    • false — vá para 3.
  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:

  1. /printed-books/ethical-design?name=Ethical+Design e
  2. /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:

  1. /printed-books/ethical-design , e
  2. /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.

Uma captura de tela do tipo LinkProps inspecionado no VSCode
Inspecionando LinkProps no VSCode. (Visualização grande)

Inspecionar o UrlObject ainda leva à interface com as propriedades:

Uma captura de tela do <code>UrlObject</code> inspecionado no VSCode
Inspecionando UrlObject no VSCode. (Visualização grande)

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