Routing lato client in Next.js
Pubblicato: 2022-03-10I collegamenti ipertestuali sono stati uno dei gioielli del Web sin dal suo inizio. Secondo MDN, i collegamenti ipertestuali sono ciò che rende il Web, un Web. Sebbene utilizzato per scopi come il collegamento tra documenti, il suo utilizzo principale è fare riferimento a diverse pagine Web identificabili da un indirizzo Web univoco o da un URL.
Il routing è un aspetto importante di ogni applicazione Web tanto quanto i collegamenti ipertestuali lo sono al Web. È un meccanismo attraverso il quale le richieste vengono instradate al codice che le gestisce. In relazione al routing, le pagine Next.js sono referenziate e identificabili da un percorso URL univoco. Se il Web è costituito da pagine Web di navigazione interconnesse da collegamenti ipertestuali , ciascuna app Next.js è costituita da pagine instradabili (gestori di percorsi o percorsi) interconnesse da un router.
Next.js ha un supporto integrato per il routing che può essere ingombrante da decomprimere, specialmente quando si considera il rendering e il recupero dei dati. Come prerequisito per comprendere l'instradamento lato client in Next.js, è necessario avere una panoramica di concetti come l'instradamento, il rendering e il recupero dei dati in Next.js.
Questo articolo sarà utile per gli sviluppatori React che hanno familiarità con Next.js e vogliono imparare come gestisce il routing. È necessario avere una conoscenza pratica di React e Next.js per ottenere il massimo dall'articolo, che riguarda esclusivamente il routing lato client e i concetti correlati in Next.js.
Instradamento e rendering
Routing e Rendering sono complementari tra loro e giocheranno un ruolo enorme nel corso di questo articolo. Mi piace come Gaurav li spiega:
Il routing è il processo attraverso il quale l'utente viene indirizzato a diverse pagine di un sito web.
Il rendering è il processo di inserimento di quelle pagine nell'interfaccia utente. Ogni volta che richiedi un percorso verso una pagina particolare, stai anche visualizzando quella pagina, ma non tutti i rendering sono il risultato di un percorso.
Prenditi cinque minuti per pensarci.
Quello che devi capire sul rendering in Next.js è che ogni pagina viene pre-renderizzata in anticipo insieme al codice JavaScript minimo necessario affinché diventi completamente interattiva attraverso un processo noto come idratazione. Il modo in cui Next.js esegue questa operazione dipende fortemente dalla forma di pre-rendering: Generazione statica o Rendering lato server , che sono entrambi altamente accoppiati alla tecnica di recupero dei dati utilizzata e separati da quando viene generato l'HTML per una pagina.
A seconda dei requisiti di recupero dei dati, potresti ritrovarti a utilizzare funzioni di recupero dati integrate come getStaticProps
, getStaticPaths
o getServerSideProps
, strumenti di recupero dati lato client come SWR, react-query o approcci tradizionali di recupero dati come fetch-on- render, fetch-then-render, render-as-you-fetch (con Suspense).
Il pre-rendering (prima del rendering - all'interfaccia utente ) è complementare al Routing e altamente associato al recupero dei dati, un argomento a sé stante in Next.js. Quindi, sebbene questi concetti siano complementari o strettamente correlati, questo articolo si concentrerà esclusivamente sulla semplice navigazione tra le pagine (routing), con riferimenti a concetti correlati ove necessario.
Detto questo, iniziamo con l'essenza fondamentale: Next.js ha un router basato su file system basato sul concetto di pagine.
Pagine
Le pagine in Next.js sono componenti React che sono automaticamente disponibili come percorsi. Vengono esportati come esportazioni predefinite dalla directory delle pagine con estensioni di file supportate come .js
, .jsx
, .ts
o .tsx
.
Una tipica app Next.js avrà una struttura di cartelle con directory di primo livello come 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
Ogni pagina è un componente React:
// pages/books.js — `base-url/book` export default function Book() { return
Libri
}
Nota : tieni presente che le pagine possono anche essere chiamate "gestori di percorsi".
Pagine personalizzate
Si tratta di pagine speciali che risiedono nella directory pages ma non partecipano al routing. Sono preceduti dal simbolo di sottolineatura, come in, _app.js
e _document.js
.
-
_app.js
Questo è un componente personalizzato che risiede nella cartella delle pagine. Next.js usa questo componente per inizializzare le pagine. -
_document.js
Come_app.js
,_document.js
è un componente personalizzato che Next.js utilizza per aumentare i<html>
e<body>
delle tue applicazioni. Ciò è necessario perché le pagine Next.js saltano la definizione del markup del documento circostante.
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
Collegamento tra le pagine
Next.js espone un componente Link
dall'API next/link
che può essere utilizzato per eseguire transizioni di route lato client tra le pagine.
// 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> ) }
Il componente Link
può essere utilizzato all'interno di qualsiasi componente, pagina o meno. Quando viene utilizzato nella sua forma più semplice come nell'esempio sopra, il componente Link
si traduce in un collegamento ipertestuale con un attributo href
. (Ulteriori informazioni sul Link
nella sezione successiva/collegamento di seguito.)
Instradamento
Il sistema di routing basato su file Next.js può essere utilizzato per definire i modelli di percorso più comuni. Per adattarsi a questi schemi, ogni percorso è separato in base alla sua definizione.
Indice Rotte
Per impostazione predefinita, nell'app Next.js, il percorso iniziale/predefinito è pages/index.js
che funge automaticamente da punto di partenza dell'applicazione come /
. Con un URL di base di localhost:3000
, è possibile accedere a questa route di indice a livello di URL di base dell'applicazione nel browser.
Le route di indicizzazione agiscono automaticamente come route predefinite per ciascuna directory e possono eliminare le ridondanze di denominazione. La struttura di directory seguente espone due percorsi di route: /
e /home
.
next-app └── pages ├── index.js // path: base-url (/) └── home.js // path: /home
L'eliminazione è più evidente con i percorsi nidificati .
Percorsi annidati
Un percorso come pages/book
è profondo un livello. Andare più in profondità significa creare percorsi nidificati, che richiedono una struttura di cartelle nidificate. Con un URL di base di https://www.smashingmagazine.com
, puoi accedere al percorso https://www.smashingmagazine.com/printed-books/printed-books
creando una struttura di cartelle simile a quella seguente:
next-app └── pages ├── index.js // top index route └── printed-books // nested route └── printed-books.js // path: /printed-books/printed-books
Oppure elimina la ridondanza del percorso con i percorsi dell'indice e accedi al percorso per i libri stampati su https://www.smashingmagazine.com/printed-books
.
next-app └── pages ├── index.js // top index route └── printed-books // nested route └── index.js // path: /printed-books
Anche i percorsi dinamici svolgono un ruolo importante nell'eliminazione delle ridondanze.
Percorsi Dinamici
Dall'esempio precedente utilizziamo il percorso dell'indice per accedere a tutti i libri stampati. Per accedere ai singoli libri è necessario creare percorsi diversi per ogni libro come:
// ️ 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
che è altamente ridondante, non scalabile e può essere risolto con percorsi dinamici come:
// 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 sintassi tra parentesi — [book-id]
— è il segmento dinamico e non è limitata ai soli file. Può anche essere utilizzato con cartelle come l'esempio seguente, rendendo l'autore disponibile nel percorso /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
I segmenti dinamici di una route sono esposti come parametro di query a cui è possibile accedere in qualsiasi componente di connessione coinvolto nella route con oggetto query
useRouter()
— (ulteriori informazioni su questo nella sezione dell'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> ); }
Estendere i segmenti dinamici del percorso con Catch All Routes
Hai visto la sintassi della parentesi del segmento di percorso dinamico come nell'esempio precedente con [book-id].js
. La bellezza di questa sintassi è che si spinge ancora oltre con i percorsi Catch-All . Puoi dedurre ciò che fa dal nome: cattura tutte le rotte.
Quando abbiamo esaminato l'esempio dinamico, abbiamo appreso come aiuta a eliminare la ridondanza della creazione di file per un singolo percorso per accedere a più libri con il loro ID. Ma c'è qualcos'altro che avremmo potuto fare.
In particolare, avevamo il percorso /printed-books/:book-id
, con una struttura di directory:
next-app └── pages ├── index.js └── printed-books ├── index.js └── [book-id].js
Se aggiornassimo il percorso per avere più segmenti come le categorie, potremmo finire con qualcosa come: /printed-books/design/:book-id
, /printed-books/engineering/:book-id
, o meglio ancora /printed-books/:category/:book-id
.
Aggiungiamo l'anno di rilascio: /printed-books/:category/:release-year/:book-id
. Riesci a vedere uno schema? La struttura della directory diventa:
next-app └── pages ├── index.js └── printed-books └── [category] └── [release-year] └── [book-id].js
Abbiamo sostituito l'uso di file con nome per percorsi dinamici, ma in qualche modo abbiamo comunque finito con un'altra forma di ridondanza. Bene, c'è una soluzione: Catch All Routes che elimina la necessità di percorsi profondamente nidificati:
next-app └── pages ├── index.js └── printed-books └── [...slug].js
Utilizza la stessa sintassi delle parentesi, tranne per il fatto che è preceduta da tre punti. Pensa ai punti come la sintassi di diffusione di JavaScript. Ti starai chiedendo: se utilizzo i percorsi catch-all, come accedo alla categoria ( [category]
) e all'anno di rilascio ( [release-year]
). Due strade:
- Nel caso dell'esempio dei libri stampati, l'obiettivo finale è il libro e ogni informazione sul libro avrà i suoi metadati allegati, oppure
- I segmenti "slug" vengono restituiti come una matrice di parametri di query.
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> ); }
Ecco un altro esempio per il percorso /printed-books/[…slug]
:
Sentiero | Parametro di ricerca |
---|---|
/printed-books/click.js | { "slug": ["clic"] } |
/printed-books/2020/click.js | { "slug": ["2020", "clic"] } |
/printed-books/design/2020/click.js | { "slug": ["design", "2020", "click"] } |
Come per il percorso catch-all, il percorso /printed-books
genererà un errore 404 a meno che tu non fornisca un percorso indice di fallback.
next-app └── pages ├── index.js └── printed-books ├── index.js // path: /printed-books └── [...slug].js
Questo perché il percorso catch-all è "rigoroso". O corrisponde a uno slug o genera un errore. Se desideri evitare di creare percorsi indice insieme a percorsi catch-all, puoi invece utilizzare i percorsi catch-all opzionali .
Estensione di segmenti di percorso dinamico con percorsi Catch-All opzionali
La sintassi è la stessa di catch-all-routes, ma con doppie parentesi quadre.
next-app └── pages ├── index.js └── printed-books └── [[...slug]].js
In questo caso, il percorso catch-all (slug) è facoltativo e, se non disponibile, esegue il fallback al percorso /printed-books
, visualizzato con [[…slug]].js
route handler, senza parametri di query.
Utilizza il catch-all insieme ai percorsi dell'indice o solo i percorsi catch-all opzionali. Evita di utilizzare insieme percorsi catch-all e catch-all opzionali.
Precedenza dei percorsi
La capacità di poter definire i percorsi più comuni può essere un "cigno nero". La possibilità che le rotte si scontrino è una minaccia incombente, soprattutto quando inizi a far lavorare le rotte dinamiche.
Quando ha senso farlo, Next.js ti informa sui conflitti di percorso sotto forma di errori. In caso contrario, applica la precedenza ai percorsi in base alla loro specificità.
Ad esempio, è un errore avere più di un percorso dinamico sullo stesso livello.
// 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 guardi da vicino i percorsi definiti di seguito, noterai il potenziale di scontri.
// 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
Ad esempio, prova a rispondere a questo: quale percorso gestisce il percorso /printed-books/inclusive-components
?
-
/printed-books/[book-id].js
, o -
/printed-books/[…slug].js
.
La risposta sta nella "specificità" dei gestori del percorso. Vengono prima i percorsi predefiniti, seguiti dai percorsi dinamici, quindi i percorsi catch-all. Puoi pensare al modello di richiesta/gestione del percorso come a uno pseudo-codice con i seguenti passaggi:
- Esiste un gestore di percorso predefinito in grado di gestire il percorso?
-
true
— gestisce la richiesta di route. -
false
— vai al 2.
-
- Esiste un gestore di percorso dinamico in grado di gestire il percorso?
-
true
— gestisce la richiesta di route. -
false
— vai al 3.
-
- Esiste un gestore di percorso catch-all in grado di gestire il percorso?
-
true
— gestisce la richiesta di route. -
false
— lancia una pagina 404 non trovata.
-
Pertanto, vince /printed-books/[book-id].js
.
Ecco altri esempi:
Rotta | Gestore del percorso | Tipo di percorso |
---|---|---|
/printed-books | /printed-books | Percorso indice |
/printed-books/tags | /printed-books/tags.js | Percorso predefinito |
/printed-books/inclusive-components | /printed-books/[book-id].js | Percorso dinamico |
/printed-books/design/inclusive-components | /printed-books/[...slug].js | Percorso catch-all |
L'API di next/link
L'API next/link
espone il componente Link
come metodo dichiarativo per eseguire transizioni di route lato client.
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> ) }
Il componente Link
si risolverà in un normale collegamento ipertestuale HTML. Cioè, <Link href="/">Smashing Magazine</Link>
si risolverà in <a href="/">Smashing Magazine</a>
.
Il prop href
è l'unico prop richiesto per il componente Link
. Consulta i documenti per un elenco completo degli oggetti di scena disponibili sul componente Link
.
Ci sono altri meccanismi del componente Link
di cui essere a conoscenza.
Percorsi con segmenti dinamici
Prima di Next.js 9.5.3, il Link
a percorsi dinamici significava che dovevi fornire sia l' href
che as
prop a Link
come in:
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> )); }
Sebbene ciò consentisse a Next.js di interpolare l'href per i parametri dinamici, era noioso, soggetto a errori e alquanto imperativo, e ora è stato risolto per la maggior parte dei casi d'uso con il rilascio di Next.js 10.
Questa correzione è anche compatibile con le versioni precedenti. Se hai utilizzato sia as
che href
, non si interrompe nulla. Per adottare la nuova sintassi, scartare href
prop e il suo valore e rinominare as
prop to href
come nell'esempio seguente:
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> )); }
Vedere Risoluzione automatica di href.
Casi d'uso per il passHref
Prop
Dai un'occhiata da vicino allo snippet qui sotto:
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> )); }
Le props passHref
forzano il componente Link
a passare la props href
al componente figlio CustomLink
. Questo è obbligatorio se il componente Link
esegue il wrapping su un componente che restituisce un tag <a>
collegamento ipertestuale. Il tuo caso d'uso potrebbe essere dovuto al fatto che stai utilizzando una libreria come i componenti con stile o se devi passare più figli al componente Link
, poiché si aspetta un solo figlio.
Consulta i documenti per saperne di più.
Oggetti URL
Il prop href
del componente Link
può anche essere un oggetto URL con proprietà come query
che viene automaticamente formattato in una stringa URL.
Con l'oggetto printedBooks
, l'esempio seguente si collegherà 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 includi un segmento dinamico nel nome del pathname
, devi includerlo anche come proprietà nell'oggetto della query per assicurarti che la query sia interpolata nel nome del 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> )); }
L'esempio sopra ha percorsi:
-
/printed-books/ethical-design
e -
/printed-books/design-systems
.
Se controlli l'attributo href
in VSCode, troverai il tipo LinkProps
, con la proprietà href
un tipo Url
, che è una string
o UrlObject
come menzionato in precedenza.
L'ispezione UrlObject
porta ulteriormente all'interfaccia con le proprietà:
Puoi saperne di più su queste proprietà nella documentazione del modulo URL di Node.js.
Un caso d'uso dell'hash è il collegamento a sezioni specifiche in una pagina.
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> )); }
Il collegamento ipertestuale si risolverà in /printed-books/ethical-design#faq
.
Scopri di più nei documenti.
L'API next/router
Se il next/link
è dichiarativo, il next/router
è imperativo. Espone un hook useRouter
che consente l'accesso all'oggetto router
all'interno di qualsiasi componente della funzione. È possibile utilizzare questo hook per eseguire manualmente l'instradamento, soprattutto in alcuni scenari in cui il next/link
non è sufficiente o in cui è necessario "agganciarsi" all'instradamento.
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
è un hook React e non può essere utilizzato con le classi. Hai bisogno dell'oggetto router
nei componenti della classe? Utilizzare 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);
L'oggetto router
Sia l'hook useRouter
che il componente di ordine superiore withRouter
, restituiscono un oggetto router con proprietà come pathname
, query
, asPath
e basePath
che fornisce informazioni sullo stato dell'URL della pagina corrente, locale
, locales
e defaultLocale
che fornisce informazioni sul locale predefinito attivo, supportato o corrente.
L'oggetto router ha anche metodi come push
per passare a un nuovo URL aggiungendo una nuova voce URL nello stack della cronologia, replace
, simile a push ma sostituisce l'URL corrente invece di aggiungere una nuova voce URL nello stack della cronologia.
Ulteriori informazioni sull'oggetto router.
Configurazione del percorso personalizzato con next.config.js
Questo è un normale modulo Node.js che può essere utilizzato per configurare determinati comportamenti di Next.js.
module.exports = { // configuration options }
Ricordati di riavviare il server ogni volta che aggiorni
next.config.js
. Per saperne di più.
Percorso Base
È stato menzionato che il percorso iniziale/predefinito in Next.js è pages/index.js
con percorso /
. Questo è configurabile e puoi rendere il tuo percorso predefinito un sottopercorso del dominio.
module.exports = { // old default path: / // new default path: /dashboard basePath: '/dashboard', };
Queste modifiche avranno automaticamente effetto nell'applicazione con tutti i percorsi indirizzati a /
/dashboard
.
Questa funzione può essere utilizzata solo con Next.js 9.5 e versioni successive. Per saperne di più.
Taglio finale
Per impostazione predefinita, una barra finale non sarà disponibile alla fine di ogni URL. Tuttavia, puoi cambiarlo con:
module.exports = { trailingSlash: true };
# trailingSlash: false /printed-books/ethical-design#faq # trailingSlash: true /printed-books/ethical-design/#faq
Sia il percorso di base che la barra finale possono essere utilizzati solo con Next.js 9.5 e versioni successive.
Conclusione
Il routing è una delle parti più importanti della tua applicazione Next.js e si riflette nel router basato su file system basato sul concetto di pagine. Le pagine possono essere utilizzate per definire i percorsi più comuni. I concetti di routing e rendering sono strettamente correlati. Porta con te le lezioni di questo articolo mentre crei la tua app Next.js o lavori su una base di codice Next.js. E controlla le risorse qui sotto per saperne di più.
Risorse correlate
- Documentazione ufficiale di Next.js per Pages
- Documentazione ufficiale di Next.js per il recupero dei dati
- Documentazione ufficiale di Next.js per next.config.js
- Next.js 10: risoluzione automatica di
href
- Documentazione ufficiale di Next.js per next/link
- Documentazione ufficiale Next.js per next/router