Creazione di un blog multi-autore con Next.js

Pubblicato: 2022-03-10
Riepilogo rapido ↬ Questo articolo spiega come collegare diversi tipi di contenuto in un'applicazione Next.js. Con questa tecnica, possiamo aggiungere qualsiasi tipo di relazione uno-a-uno, uno-a-molti o anche molti-a-molti ai nostri progetti.

In questo articolo creeremo un blog con Next.js che supporta due o più autori. Attribuiremo ogni post a un autore e mostreremo il suo nome e la sua immagine con i suoi post. Ogni autore riceve anche una pagina del profilo, che elenca tutti i post a cui hanno contribuito. Sembrerà qualcosa del genere:

A sinistra: l'indice del blog finito che andremo a costruire. A destra: la pagina di un singolo post, che rimanda alla pagina del profilo del suo autore.
A sinistra: l'indice del blog finito che andremo a costruire. A destra: la pagina di un singolo post, che rimanda alla pagina del profilo del suo autore. (Grande anteprima)
La pagina del profilo di un autore, che collega a tutti i suoi post.
Pagina del profilo di un autore, che collega a tutti i suoi post. (Anteprima grande)

Conserveremo tutte le informazioni nei file sul filesystem locale. I due tipi di contenuto, post e autori, utilizzeranno diversi tipi di file. I post ricchi di testo utilizzeranno Markdown, consentendo un processo di modifica più semplice. Poiché le informazioni sugli autori sono più leggere, le manterremo nei file JSON. Le funzioni di supporto renderanno più semplice la lettura di diversi tipi di file e la combinazione del loro contenuto.

Next.js ci consente di leggere i dati da diverse fonti e di diversi tipi senza sforzo. Grazie al suo routing dinamico e next/link , possiamo creare e navigare velocemente tra le varie pagine del nostro sito. Otteniamo anche l'ottimizzazione delle immagini gratuitamente con il pacchetto next/image .

Selezionando le "batterie incluse" Next.js, possiamo concentrarci sulla nostra stessa applicazione. Non dobbiamo dedicare tempo al lavoro di base ripetitivo con cui spesso arrivano nuovi progetti. Invece di costruire tutto a mano, possiamo fare affidamento su un framework testato e collaudato. La comunità ampia e attiva dietro Next.js rende facile ottenere aiuto in caso di problemi lungo il percorso.

Dopo aver letto questo articolo, sarai in grado di aggiungere molti tipi di contenuto a un singolo progetto Next.js. Sarai anche in grado di creare relazioni tra di loro. Ciò ti consente di collegare elementi come autori e post, corsi e lezioni o attori e film.

Questo articolo presuppone una familiarità di base con Next.js. Se non l'hai mai usato prima, potresti voler leggere prima come gestisce le pagine e recupera i dati per loro.

Non tratteremo lo stile in questo articolo e ci concentreremo invece sul far funzionare tutto. Puoi ottenere il risultato su GitHub. C'è anche un foglio di stile che puoi inserire nel tuo progetto se vuoi seguire questo articolo. Per ottenere lo stesso frame, inclusa la navigazione, sostituisci le tue pages/_app.js con questo file.

Altro dopo il salto! Continua a leggere sotto ↓

Impostare

Iniziamo impostando un nuovo progetto usando create-next-app e passando alla sua directory:

 $ npx create-next-app multiauthor-blog $ cd multiauthor-blog

Avremo bisogno di leggere i file Markdown in seguito. Per semplificare, aggiungiamo anche alcune altre dipendenze prima di iniziare.

 multiauthor-blog$ yarn add gray-matter remark remark-html

Una volta completata l'installazione, possiamo eseguire lo script dev per avviare il nostro progetto:

 multiauthor-blog$ yarn dev

Ora possiamo esplorare il nostro sito. Nel tuo browser, apri https://localhost:3000. Dovresti vedere la pagina predefinita aggiunta da create-next-app.

La pagina predefinita creata da create-next-app.
Se vedi questo, la tua configurazione funziona. (Grande anteprima)

Tra un po', avremo bisogno di una navigazione per raggiungere le nostre pagine. Possiamo aggiungerli in pages/_app.js anche prima che le pagine esistano.

 import Link from 'next/link' import '../styles/globals.css' export default function App({ Component, pageProps }) { return ( <> <header> <nav> <ul> <li> <Link href="/"> <a>Home</a> </Link> </li> <li> <Link href="/posts"> <a>Posts</a> </Link> </li> <li> <Link href="/authors"> <a>Authors</a> </Link> </li> </ul> </nav> </header> <main> <Component {...pageProps} /> </main> </> ) }

In questo articolo, aggiungeremo queste pagine mancanti a cui punta la navigazione. Aggiungiamo prima alcuni post in modo da avere qualcosa su cui lavorare in una pagina di panoramica del blog.

Creazione di post

Per mantenere i nostri contenuti separati dal codice, inseriremo i nostri post in una directory chiamata _posts/ . Per semplificare la scrittura e la modifica, creeremo ogni post come file Markdown. Il nome del file di ogni post servirà come slug nei nostri percorsi in seguito. Il file _posts/hello-world.md sarà accessibile in /posts/hello-world , per esempio.

Alcune informazioni, come il titolo completo e un breve estratto, vanno in primo piano all'inizio del file.

 --- title: "Hello World!" excerpt: "This is my first blog post." createdAt: "2021-05-03" --- Hey, how are you doing? Welcome to my blog. In this post, …

Aggiungi qualche altro file come questo in modo che il blog non inizi vuoto:

 multi-author-blog/ ├─ _posts/ │ ├─ hello-world.md │ ├─ multi-author-blog-in-nextjs.md │ ├─ styling-react-with-tailwind.md │ └─ ten-hidden-gems-in-javascript.md └─ pages/ └─ …

Puoi aggiungere il tuo o prendere questi post di esempio dal repository GitHub.

Elenco di tutti i post

Ora che abbiamo alcuni post, abbiamo bisogno di un modo per inserirli nel nostro blog. Iniziamo aggiungendo una pagina che li elenca tutti, fungendo da indice del nostro blog.

In Next.js, un file creato in pages/posts/index.js sarà accessibile come /posts sul nostro sito. Il file deve esportare una funzione che fungerà da corpo della pagina. La sua prima versione assomiglia a questa:

 export default function Posts() { return ( <div className="posts"> <h1>Posts</h1> {/* TODO: render posts */} </div> ) }

Non andiamo molto lontano perché non abbiamo ancora un modo per leggere i file Markdown. Possiamo già navigare su https://localhost:3000/posts, ma vediamo solo l'intestazione.

Una pagina vuota con un'intestazione che dice "Post".
Possiamo accedere alla nostra pagina e iniziare a riempirla di vita. (Grande anteprima)

Ora abbiamo bisogno di un modo per inserire i nostri post lì. Next.js usa una funzione chiamata getStaticProps() per passare i dati a un componente della pagina. La funzione passa gli props di scena nell'oggetto restituito al componente come oggetti di scena.

Da getStaticProps() , passeremo i post al componente come prop chiamato posts . Codificheremo due post segnaposto in questo primo passaggio. Iniziando in questo modo, definiamo in quale formato vogliamo ricevere i post reali. Se una funzione di supporto li restituisce in questo formato, possiamo passare ad esso senza modificare il componente.

La panoramica del post non mostrerà il testo completo dei post. Per questa pagina sono sufficienti il ​​titolo, l'estratto, il permalink e la data di ogni post.

 export default function Posts() { … } +export function getStaticProps() { + return { + props: { + posts: [ + { + title: "My first post", + createdAt: "2021-05-01", + excerpt: "A short excerpt summarizing the post.", + permalink: "/posts/my-first-post", + slug: "my-first-post", + }, { + title: "My second post", + createdAt: "2021-05-04", + excerpt: "Another summary that is short.", + permalink: "/posts/my-second-post", + slug: "my-second-post", + } + ] + } + } +}

Per verificare la connessione, possiamo prendere i post dagli oggetti di scena e mostrarli nel componente Posts . Includeremo il titolo, la data di creazione, l'estratto e un collegamento al post. Per ora, quel collegamento non porterà ancora da nessuna parte.

 +import Link from 'next/link' -export default function Posts() { +export default function Posts({ posts }) { return ( <div className="posts"> <h1>Posts</h1> - {/* TODO: render posts */} + {posts.map(post => { + const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { + month: 'short', + day: '2-digit', + year: 'numeric', + }) + + return ( + <article key={post.slug}> + <h2> + <Link href={post.permalink}> + <a>{post.title}</a> + </Link> + </h2> + + <time dateTime={post.createdAt}>{prettyDate}</time> + + <p>{post.excerpt}</p> + + <Link href={post.permalink}> + <a>Read more →</a> + </Link> + </article> + ) + })} </div> ) } export function getStaticProps() { … }

Dopo aver ricaricato la pagina nel browser, ora mostra questi due post:

Un elenco dei nostri due post segnaposto.
La connessione funziona. Ora possiamo lavorare per inserire post reali qui. (Grande anteprima)

Non vogliamo codificare per sempre tutti i nostri post del blog in getStaticProps() . Dopotutto, è per questo che abbiamo creato tutti questi file nella directory _posts/ in precedenza. Ora abbiamo bisogno di un modo per leggere quei file e passare il loro contenuto al componente della pagina.

Ci sono alcuni modi in cui potremmo farlo. Potremmo leggere i file direttamente in getStaticProps() . Poiché questa funzione viene eseguita sul server e non sul client, abbiamo accesso ai moduli Node.js nativi come fs al suo interno. Potremmo leggere, trasformare e persino manipolare file locali nello stesso file in cui conserviamo il componente della pagina.

Per mantenere il file breve e focalizzato su un'attività, sposteremo invece quella funzionalità in un file separato. In questo modo, il componente Posts deve solo visualizzare i dati, senza dover leggere anche quei dati stessi. Questo aggiunge una certa separazione e organizzazione al nostro progetto.

Per convenzione, inseriremo le funzioni di lettura dei dati in un file chiamato lib/api.js . Quel file conterrà tutte le funzioni che catturano il nostro contenuto per i componenti che lo visualizzano.

Per la pagina di panoramica dei post, vogliamo una funzione che legga, elabori e restituisca tutti i post. Lo chiameremo getAllPosts() . In esso, utilizziamo prima path.join() per costruire il percorso della directory _posts/ . Usiamo quindi fs.readdirSync() per leggere quella directory, che ci fornisce i nomi di tutti i file in essa contenuti. Mappando questi nomi, quindi leggiamo ogni file a turno.

 import fs from 'fs' import path from 'path' export function getAllPosts() { const postsDirectory = path.join(process.cwd(), '_posts') const filenames = fs.readdirSync(postsDirectory) return filenames.map(filename => { const file = fs.readFileSync(path.join(process.cwd(), '_posts', filename), 'utf8') // TODO: transform and return file }) }

Dopo aver letto il file, otteniamo il suo contenuto come una lunga stringa. Per separare il frontmatter dal testo del post, eseguiamo quella stringa attraverso gray-matter . Prenderemo anche lo slug di ogni post rimuovendo il .md dalla fine del nome del file. Abbiamo bisogno di quello slug per costruire l'URL da cui il post sarà accessibile in seguito. Dal momento che non abbiamo bisogno del corpo Markdown dei post per questa funzione, possiamo ignorare il contenuto rimanente.

 import fs from 'fs' import path from 'path' +import matter from 'gray-matter' export function getAllPosts() { const postsDirectory = path.join(process.cwd(), '_posts') const filenames = fs.readdirSync(postsDirectory) return filenames.map(filename => { const file = fs.readFileSync(path.join(process.cwd(), '_posts', filename), 'utf8') - // TODO: transform and return file + // get frontmatter + const { data } = matter(file) + + // get slug from filename + const slug = filename.replace(/\.md$/, '') + + // return combined frontmatter and slug; build permalink + return { + ...data, + slug, + permalink: `/posts/${slug}`, + } }) }

Nota come diffondiamo ...data nell'oggetto restituito qui. Ciò ci consente di accedere ai valori dal suo argomento principale come {post.title} anziché {post.data.title} secondo momento.

Tornando alla nostra pagina di panoramica dei post, ora possiamo sostituire i post segnaposto con questa nuova funzione.

 +import { getAllPosts } from '../../lib/api' export default function Posts({ posts }) { … } export function getStaticProps() { return { props: { - posts: [ - { - title: "My first post", - createdAt: "2021-05-01", - excerpt: "A short excerpt summarizing the post.", - permalink: "/posts/my-first-post", - slug: "my-first-post", - }, { - title: "My second post", - createdAt: "2021-05-04", - excerpt: "Another summary that is short.", - permalink: "/posts/my-second-post", - slug: "my-second-post", - } - ] + posts: getAllPosts(), } } }

Dopo aver ricaricato il browser, ora vediamo i nostri post reali invece dei segnaposto che avevamo prima.

Un elenco dei nostri veri post sul blog.
Grazie alla funzione di supporto, questa pagina ora mostra i nostri post reali. (Grande anteprima)

Aggiunta di singole pagine di post

I link che abbiamo aggiunto a ogni post non portano ancora da nessuna parte. Non esiste ancora una pagina che risponda a URL come /posts/hello-world . Con il routing dinamico, possiamo aggiungere una pagina che corrisponde a tutti i percorsi come questo.

Un file creato come pages/posts/[slug].js corrisponderà a tutti gli URL che assomigliano a /posts/abc . Il valore che appare al posto di [slug] nell'URL sarà disponibile per la pagina come parametro di query. Possiamo usarlo nel getStaticProps() della pagina corrispondente come params.slug per chiamare una funzione di supporto.

Come controparte di getAllPosts() , chiameremo quella funzione di supporto getPostBySlug(slug) . Invece di tutti i post, restituirà un singolo post che corrisponde allo slug che gli passiamo. Nella pagina di un post, dobbiamo anche mostrare il contenuto Markdown del file sottostante.

La pagina per i singoli post è simile a quella per la panoramica dei post. Invece di passare i posts alla pagina in getStaticProps() , passiamo solo un singolo post . Eseguiamo la configurazione generale prima di esaminare come trasformare il corpo Markdown del post in HTML utilizzabile. Salteremo qui il post segnaposto, usando la funzione di supporto che aggiungeremo immediatamente nel passaggio successivo.

 import { getPostBySlug } from '../../lib/api' export default function Post({ post }) { const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }) return ( <div className="post"> <h1>{post.title}</h1> <time dateTime={post.createdAt}>{prettyDate}</time> {/* TODO: render body */} </div> ) } export function getStaticProps({ params }) { return { props: { post: getPostBySlug(params.slug), }, } }

Ora dobbiamo aggiungere la funzione getPostBySlug(slug) al nostro file di supporto lib/api.js . È come getAllPosts() , con alcune differenze notevoli. Poiché possiamo ottenere il nome del file del post dallo slug, non è necessario leggere prima l'intera directory. Se lo slug è 'hello-world' , leggeremo un file chiamato _posts/hello-world.md . Se quel file non esiste, Next.js mostrerà una pagina di errore 404.

Un'altra differenza rispetto a getAllPosts() è che questa volta dobbiamo anche leggere il contenuto Markdown del post. Possiamo restituirlo come HTML pronto per il rendering invece di Markdown grezzo elaborandolo prima con il remark .

 import fs from 'fs' import path from 'path' import matter from 'gray-matter' +import remark from 'remark' +import html from 'remark-html' export function getAllPosts() { … } +export function getPostBySlug(slug) { + const file = fs.readFileSync(path.join(process.cwd(), '_posts', `${slug}.md`), 'utf8') + + const { + content, + data, + } = matter(file) + + const body = remark().use(html).processSync(content).toString() + + return { + ...data, + body, + } +}

In teoria, potremmo usare la funzione getAllPosts() all'interno getPostBySlug(slug) . Prima otterremmo tutti i post con esso, che potremmo quindi cercare per uno che corrisponda allo slug specificato. Ciò significherebbe che dovremmo sempre leggere tutti i post prima di poterne ottenere uno solo, il che è un lavoro non necessario. getAllPosts() inoltre non restituisce il contenuto Markdown dei post. Potremmo aggiornarlo per farlo, nel qual caso farebbe più lavoro del necessario.

Poiché le due funzioni di supporto fanno cose diverse, le terremo separate. In questo modo, possiamo concentrare le funzioni esattamente e solo sul lavoro che ciascuna di esse deve svolgere.

Le pagine che utilizzano il routing dinamico possono fornire un getStaticPaths() accanto a getStaticProps() . Questa funzione dice a Next.js per quali valori dei segmenti di percorso dinamico creare le pagine. Possiamo fornirli usando getAllPosts() e restituendo un elenco di oggetti che definiscono lo slug di ogni post.

 -import { getPostBySlug } from '../../lib/api' +import { getAllPosts, getPostBySlug } from '../../lib/api' export default function Post({ post }) { … } export function getStaticProps({ params }) { … } +export function getStaticPaths() { + return { + fallback: false, + paths: getAllPosts().map(post => ({ + params: { + slug: post.slug, + }, + })), + } +}

Poiché analizziamo il contenuto di Markdown in getPostBySlug(slug) , ora possiamo renderizzarlo sulla pagina. Dobbiamo usare dangerouslySetInnerHTML per questo passaggio in modo che Next.js possa eseguire il rendering dell'HTML dietro post.body . Nonostante il nome, è sicuro utilizzare la proprietà in questo scenario. Poiché abbiamo il pieno controllo sui nostri post, è improbabile che iniettino script non sicuri.

 import { getAllPosts, getPostBySlug } from '../../lib/api' export default function Post({ post }) { const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }) return ( <div className="post"> <h1>{post.title}</h1> <time dateTime={post.createdAt}>{prettyDate}</time> - {/* TODO: render body */} + <div dangerouslySetInnerHTML={{ __html: post.body }} /> </div> ) } export function getStaticProps({ params }) { … } export function getStaticPaths() { … }

Se seguiamo uno dei link dalla panoramica del post, ora arriviamo alla pagina di quel post.

La pagina di un singolo post.
Possiamo mostrare il contenuto del post, ma non sappiamo ancora chi l'ha scritto. (Grande anteprima)

Aggiunta di autori

Ora che abbiamo i post cablati, dobbiamo ripetere gli stessi passaggi per i nostri autori. Questa volta useremo JSON invece di Markdown per descriverli. Possiamo mescolare diversi tipi di file nello stesso progetto in questo modo ogni volta che ha senso. Le funzioni di supporto che utilizziamo per leggere i file si occupano di eventuali differenze per noi. Le pagine possono utilizzare queste funzioni senza sapere in quale formato memorizziamo i nostri contenuti.

Innanzitutto, crea una directory chiamata _authors/ e aggiungi alcuni file dell'autore ad essa. Come abbiamo fatto con i post, nomina i file in base allo slug di ciascun autore. Lo useremo per cercare gli autori in seguito. In ogni file, specifichiamo il nome completo di un autore in un oggetto JSON.

 { "name": "Adrian Webber" }

Per ora basta avere due autori nel nostro progetto.

Per dare loro un po' più di personalità, aggiungiamo anche un'immagine del profilo per ogni autore. Metteremo quei file statici nella directory public/ . Denominando i file con lo stesso slug, possiamo collegarli usando solo la convenzione implicita. Potremmo aggiungere il percorso dell'immagine al file JSON di ciascun autore per collegare i due. Denominando tutti i file in base agli slug, possiamo gestire questa connessione senza doverla scrivere. Gli oggetti JSON devono solo contenere informazioni che non possiamo creare con il codice.

Quando hai finito, la directory del tuo progetto dovrebbe assomigliare a questa.

 multi-author-blog/ ├─ _authors/ │ ├─ adrian-webber.json │ └─ megan-carter.json ├─ _posts/ │ └─ … ├─ pages/ │ └─ … └─ public/ ├─ adrian-webber.jpg └─ megan-carter.jpg

Come per i post, ora abbiamo bisogno di funzioni di supporto per leggere tutti gli autori e ottenere i singoli autori. Anche le nuove funzioni getAllAuthors() e getAuthorBySlug(slug) vanno in lib/api.js . Fanno quasi esattamente lo stesso delle loro controparti postali. Poiché utilizziamo JSON per descrivere gli autori, non è necessario analizzare alcun Markdown con remark qui. Inoltre, non abbiamo bisogno gray-matter per analizzare la materia anteriore. Invece, possiamo usare JSON.parse() integrato di JavaScript per leggere il contenuto del testo dei nostri file in oggetti.

 const contents = fs.readFileSync(somePath, 'utf8') // ⇒ looks like an object, but is a string // eg '{ "name": "John Doe" }' const json = JSON.parse(contents) // ⇒ a real JavaScript object we can do things with // eg { name: "John Doe" }

Con questa conoscenza, le nostre funzioni di supporto si presentano così:

 export function getAllPosts() { … } export function getPostBySlug(slug) { … } +export function getAllAuthors() { + const authorsDirectory = path.join(process.cwd(), '_authors') + const filenames = fs.readdirSync(authorsDirectory) + + return filenames.map(filename => { + const file = fs.readFileSync(path.join(process.cwd(), '_authors', filename), 'utf8') + + // get data + const data = JSON.parse(file) + + // get slug from filename + const slug = filename.replace(/\.json/, '') + + // return combined frontmatter and slug; build permalink + return { + ...data, + slug, + permalink: `/authors/${slug}`, + profilePictureUrl: `${slug}.jpg`, + } + }) +} + +export function getAuthorBySlug(slug) { + const file = fs.readFileSync(path.join(process.cwd(), '_authors', `${slug}.json`), 'utf8') + + const data = JSON.parse(file) + + return { + ...data, + permalink: `/authors/${slug}`, + profilePictureUrl: `/${slug}.jpg`, + slug, + } +}

Con un modo per leggere gli autori nella nostra applicazione, ora possiamo aggiungere una pagina che li elenca tutti. La creazione di una nuova pagina in pages/authors/index.js ci fornisce una pagina /authors sul nostro sito.

Le funzioni di supporto si occupano della lettura dei file per noi. Questo componente della pagina non ha bisogno di sapere che gli autori sono file JSON nel filesystem. Può usare getAllAuthors() senza sapere dove o come ottiene i suoi dati. Il formato non ha importanza fintanto che le nostre funzioni di supporto restituiscono i loro dati in un formato con cui possiamo lavorare. Astrazioni come questa ci consentono di mescolare diversi tipi di contenuto nella nostra applicazione.

La pagina dell'indice per gli autori assomiglia molto a quella per i post. Otteniamo tutti gli autori in getStaticProps() , che li passa al componente Authors . Quel componente esegue il mapping su ciascun autore ed elenca alcune informazioni su di loro. Non è necessario creare altri collegamenti o URL dallo slug. La funzione di supporto restituisce già gli autori in un formato utilizzabile.

 import Image from 'next/image' import Link from 'next/link' import { getAllAuthors } from '../../lib/api/authors' export default function Authors({ authors }) { return ( <div className="authors"> <h1>Authors</h1> {authors.map(author => ( <div key={author.slug}> <h2> <Link href={author.permalink}> <a>{author.name}</a> </Link> </h2> <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" /> <Link href={author.permalink}> <a>Go to profile →</a> </Link> </div> ))} </div> ) } export function getStaticProps() { return { props: { authors: getAllAuthors(), }, } }

Se visitiamo /authors sul nostro sito, vediamo un elenco di tutti gli autori con i loro nomi e immagini.

L'elenco degli autori.
Possiamo elencare tutti gli autori, ma non abbiamo modo di sapere quanti articoli hanno contribuito. (Grande anteprima)

I link ai profili degli autori non portano ancora da nessuna parte. Per aggiungere le pagine del profilo, creiamo un file in pages/authors/[slug].js . Poiché gli autori non hanno alcun contenuto di testo, tutto ciò che possiamo aggiungere per ora sono i loro nomi e le immagini del profilo. Abbiamo anche bisogno di un altro getStaticPaths() per dire a Next.js per quali slug creare le pagine.

 import Image from 'next/image' import { getAllAuthors, getAuthorBySlug } from '../../lib/api' export default function Author({ author }) { return ( <div className="author"> <h1>{author.name}</h1> <Image alt={author.name} src={author.profilePictureUrl} height="80" width="80" /> </div> ) } export function getStaticProps({ params }) { return { props: { author: getAuthorBySlug(params.slug), }, } } export function getStaticPaths() { return { fallback: false, paths: getAllAuthors().map(author => ({ params: { slug: author.slug, }, })), } }

Con questo, ora abbiamo una pagina del profilo dell'autore di base che è molto leggera sulle informazioni.

La pagina del profilo di un autore, che mostra il suo nome e il suo primo piano.
La pagina del profilo di un autore è per lo più vuota in questo momento. (Grande anteprima)

A questo punto autori e post non sono ancora collegati. Costruiremo quel ponte in seguito in modo da poter aggiungere un elenco dei post di ciascun autore alle pagine del loro profilo.

Collegamento di post e autori

Per collegare due contenuti, dobbiamo fare riferimento a uno nell'altro. Poiché identifichiamo già post e autori in base ai loro slug, li faremo riferimento con quello. Potremmo aggiungere autori ai post e post agli autori, ma una direzione è sufficiente per collegarli. Dal momento che vogliamo attribuire i post agli autori, aggiungeremo lo slug dell'autore al frontmatter di ogni post.

 --- title: "Hello World!" excerpt: "This is my first blog post." createdAt: "2021-05-03" +author: adrian-webber --- Hey, how are you doing? Welcome to my blog. In this post, …

Se lo manteniamo così, l'esecuzione del post attraverso gray-matter aggiunge il campo dell'autore al post come stringa:

 const post = getPostBySlug("hello-world") const author = post.author console.log(author) // "adrian-webber"

Per ottenere l'oggetto che rappresenta l'autore, possiamo usare quello slug e chiamare getAuthorBySlug(slug) con esso.

 const post = getPostBySlug("hello-world") -const author = post.author +const author = getAuthorBySlug(post.author) console.log(author) // { // name: "Adrian Webber", // slug: "adrian-webber", // profilePictureUrl: "/adrian-webber.jpg", // permalink: "/authors/adrian-webber" // }

Per aggiungere l'autore alla pagina di un singolo post, dobbiamo chiamare getAuthorBySlug(slug) una volta in getStaticProps() .

 +import Image from 'next/image' +import Link from 'next/link' -import { getPostBySlug } from '../../lib/api' +import { getAuthorBySlug, getPostBySlug } from '../../lib/api' export default function Post({ post }) { const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }) return ( <div className="post"> <h1>{post.title}</h1> <time dateTime={post.createdAt}>{prettyDate}</time> + <div> + <Image alt={post.author.name} src={post.author.profilePictureUrl} height="40" width="40" /> + + <Link href={post.author.permalink}> + <a> + {post.author.name} + </a> + </Link> + </div> <div dangerouslySetInnerHTML={{ __html: post.body }}> </div> ) } export function getStaticProps({ params }) { + const post = getPostBySlug(params.slug) return { props: { - post: getPostBySlug(params.slug), + post: { + ...post, + author: getAuthorBySlug(post.author), + }, }, } }

Nota come abbiamo diffuso ...post in un oggetto chiamato anche post in getStaticProps() . Posizionando l' author dopo quella riga, finiamo per sostituire la versione stringa dell'autore con il suo oggetto completo. Ciò ci consente di accedere alle proprietà di un autore tramite post.author.name nel componente Post .

Con questa modifica, ora otteniamo un collegamento alla pagina del profilo dell'autore, completo di nome e immagine, nella pagina di un post.

La pagina del post finita, che ora include il nome dell'autore e il primo piano.
Il post è ora correttamente attribuito all'autore. (Grande anteprima)

L'aggiunta di autori alla pagina di panoramica del post richiede una modifica simile. Invece di chiamare getAuthorBySlug(slug) una volta, dobbiamo mappare tutti i post e chiamarlo per ognuno di essi.

 +import Image from 'next/image' +import Link from 'next/link' -import { getAllPosts } from '../../lib/api' +import { getAllPosts, getAuthorBySlug } from '../../lib/api' export default function Posts({ posts }) { return ( <div className="posts"> <h1>Posts</h1> {posts.map(post => { const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }) return ( <article key={post.slug}> <h2> <Link href={post.permalink}> <a>{post.title}</a> </Link> </h2> <time dateTime={post.createdAt}>{prettyDate}</time> + <div> + <Image alt={post.author.name} src={post.author.profilePictureUrl} height="40" width="40" /> + + <span>{post.author.name}</span> + </div> <p>{post.excerpt}</p> <Link href={post.permalink}> <a>Read more →</a> </Link> </article> ) })} </div> ) } export function getStaticProps() { return { props: { - posts: getAllPosts(), + posts: getAllPosts().map(post => ({ + ...post, + author: getAuthorBySlug(post.author), + })), } } }

Ciò aggiunge gli autori a ogni post nella panoramica del post:

Un elenco di post del blog, inclusi i nomi e le foto dei loro autori.
Adesso sembra un vero blog. (Grande anteprima)

Non è necessario aggiungere un elenco di post di un autore al suo file JSON. Sulle pagine del loro profilo, otteniamo prima tutti i post con getAllPosts() . Possiamo quindi filtrare l'elenco completo per quelli attribuiti a questo autore.

 import Image from 'next/image' +import Link from 'next/link' -import { getAllAuthors, getAuthorBySlug } from '../../lib/api' +import { getAllAuthors, getAllPosts, getAuthorBySlug } from '../../lib/api' export default function Author({ author }) { return ( <div className="author"> <h1>{author.name}</h1> <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" /> + <h2>Posts</h2> + + <ul> + {author.posts.map(post => ( + <li> + <Link href={post.permalink}> + <a> + {post.title} + </a> + </Link> + </li> + ))} + </ul> </div> ) } export function getStaticProps({ params }) { const author = getAuthorBySlug(params.slug) return { props: { - author: getAuthorBySlug(params.slug), + author: { + ...author, + posts: getAllPosts().filter(post => post.author === author.slug), + }, }, } } export function getStaticPaths() { … }

Questo ci fornisce un elenco di articoli sulla pagina del profilo di ogni autore.

La pagina del profilo di un autore, che ora include un elenco di collegamenti ai loro post.
Ora possiamo elencare e collegare i post di ciascun autore. (Grande anteprima)

Nella pagina di panoramica dell'autore, aggiungeremo solo quanti post hanno scritto per non ingombrare l'interfaccia.

 import Image from 'next/image' import Link from 'next/link' -import { getAllAuthors } from '../../lib/api' +import { getAllAuthors, getAllPosts } from '../../lib/api' export default function Authors({ authors }) { return ( <div className="authors"> <h1>Authors</h1> {authors.map(author => ( <div key={author.slug}> <h2> <Link href={author.permalink}> <a> {author.name} </a> </Link> </h2> <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" /> + <p>{author.posts.length} post(s)</p> <Link href={author.permalink}> <a>Go to profile →</a> </Link> </div> ))} </div> ) } export function getStaticProps() { return { props: { - authors: getAllAuthors(), + authors: getAllAuthors().map(author => ({ + ...author, + posts: getAllPosts().filter(post => post.author === author.slug), + })), } } }

Con ciò, la pagina Panoramica degli autori mostra quanti post ha contribuito ogni autore.

L'elenco degli autori con il loro numero di post.
Ora possiamo inserire il numero di post che hanno contribuito con la voce di ciascun autore. (Grande anteprima)

E questo è tutto! I post e gli autori sono ora completamente collegati. Possiamo passare da un post alla pagina del profilo di un autore e da lì agli altri post.

Riepilogo e prospettive

In questo articolo, abbiamo collegato due tipi di contenuto correlati attraverso i loro slug unici. La definizione della relazione tra post e autore ha consentito una varietà di scenari. Ora possiamo mostrare l'autore su ogni post ed elencare i suoi post nelle pagine del suo profilo.

Con questa tecnica, possiamo aggiungere molti altri tipi di relazioni. Ogni post potrebbe avere un revisore in cima a un autore. Possiamo configurarlo aggiungendo un campo reviewer al frontmatter di un post.

 --- title: "Hello World!" excerpt: "This is my first blog post." createdAt: "2021-05-03" author: adrian-webber +reviewer: megan-carter --- Hey, how are you doing? Welcome to my blog. In this post, …

Sul filesystem, il revisore è un altro autore della directory _authors/ . Possiamo usare getAuthorBySlug(slug) anche per ottenere le loro informazioni.

 export function getStaticProps({ params }) { const post = getPostBySlug(params.slug) return { props: { post: { ...post, author: getAuthorBySlug(post.author), + reviewer: getAuthorBySlug(post.reviewer), }, }, } }

Potremmo anche supportare i coautori nominando due o più autori su un post invece di una sola persona.

 --- title: "Hello World!" excerpt: "This is my first blog post." createdAt: "2021-05-03" -author: adrian-webber +authors: + - adrian-webber + - megan-carter --- Hey, how are you doing? Welcome to my blog. In this post, …

In questo scenario, non potremmo più cercare un singolo autore in getStaticProps() di un post. Invece, mapperemmo questa serie di autori per ottenerli tutti.

 export function getStaticProps({ params }) { const post = getPostBySlug(params.slug) return { props: { post: { ...post, - author: getAuthorBySlug(post.author), + authors: post.authors.map(getAuthorBySlug), }, }, } }

Possiamo anche produrre altri tipi di scenari con questa tecnica. Consente qualsiasi tipo di relazione uno-a-uno, uno-a-molti o anche molti-a-molti. Se il tuo progetto include anche newsletter e case study, puoi anche aggiungere autori a ciascuno di essi.

Su un sito tutto sull'universo Marvel, potremmo collegare i personaggi e i film in cui appaiono. Nello sport, potremmo collegare i giocatori e le squadre per cui giocano attualmente.

Poiché le funzioni di supporto nascondono l'origine dati, il contenuto potrebbe provenire da sistemi diversi. Potremmo leggere articoli dal filesystem, commenti da un'API e unirli nel nostro codice. Se alcuni contenuti si riferiscono a un altro tipo di contenuto, possiamo collegarli a questo modello.

Ulteriori risorse

Next.js offre ulteriori informazioni sulle funzioni che abbiamo utilizzato nella loro pagina sul recupero dei dati. Include collegamenti a progetti di esempio che recuperano dati da diversi tipi di origini.

Se vuoi portare avanti questo progetto iniziale, dai un'occhiata a questi articoli:

  • Costruire un clone di siti Web CSS Tricks con Strapi e Next.js
    Sostituisci i file sul filesystem locale con un backend basato su Strapi.
  • Confronto dei metodi di stile in Next.js
    Esplora diversi modi di scrivere CSS personalizzati per cambiare lo stile di questo starter.
  • Markdown/MDX con Next.js
    Aggiungi MDX al tuo progetto in modo da poter utilizzare i componenti JSX e React nel tuo Markdown.