Crearea unui blog cu mai mulți autori cu Next.js

Publicat: 2022-03-10
Rezumat rapid ↬ Acest articol explică cum putem conecta diferite tipuri de conținut într-o aplicație Next.js. Cu această tehnică, putem adăuga orice fel de relație unu-la-unu, unu-la-mulți sau chiar multi-la-mulți la proiectele noastre.

În acest articol, vom construi un blog cu Next.js care acceptă doi sau mai mulți autori. Vom atribui fiecare postare unui autor și vom arăta numele și fotografia acestuia împreună cu postările lor. Fiecare autor primește, de asemenea, o pagină de profil, care listează toate postările cu care a contribuit. Va arata cam asa:

În stânga: indexul final al blogului pe care îl vom construi. În dreapta: pagina unei postări individuale, care trimite către pagina de profil a autorului său.
În stânga: indexul final al blogului pe care îl vom construi. În dreapta: pagina unei postări individuale, care trimite către pagina de profil a autorului acesteia. (Previzualizare mare)
Pagina de profil a unui autor, cu link către toate postările sale.
Pagina de profil a unui autor, cu link către toate postările sale. (Previzualizare mare)

Vom păstra toate informațiile în fișierele din sistemul de fișiere local. Cele două tipuri de conținut, postări și autori, vor folosi tipuri diferite de fișiere. Postările grele de text vor folosi Markdown, permițând un proces de editare mai ușor. Deoarece informațiile despre autori sunt mai ușoare, le vom păstra în fișierele JSON. Funcțiile de ajutor vor facilita citirea diferitelor tipuri de fișiere și combinarea conținutului acestora.

Next.js ne permite să citim fără efort date din diferite surse și de diferite tipuri. Datorită rutării sale dinamice și next/link , putem construi și naviga rapid către diferitele pagini ale site-ului nostru. De asemenea, obținem optimizarea imaginii gratuit cu pachetul next/image .

Alegând „baterii incluse” Next.js, ne putem concentra asupra aplicației noastre în sine. Nu trebuie să petrecem timp pe bazele repetitive cu care vin adesea proiectele noi. În loc să construim totul manual, ne putem baza pe cadrul testat și dovedit. Comunitatea mare și activă din spatele Next.js facilitează obținerea de ajutor dacă întâmpinăm probleme pe parcurs.

După ce ați citit acest articol, veți putea adăuga multe tipuri de conținut la un singur proiect Next.js. De asemenea, vei putea crea relații între ei. Acest lucru vă permite să legați lucruri precum autori și postări, cursuri și lecții sau actori și filme.

Acest articol presupune familiaritatea de bază cu Next.js. Dacă nu l-ați folosit înainte, poate doriți să citiți mai întâi cum gestionează paginile și preia datele pentru ele.

Nu vom acoperi stilul în acest articol și ne vom concentra pe ca totul să funcționeze. Puteți obține rezultatul pe GitHub. Există, de asemenea, o foaie de stil pe care o puteți introduce în proiectul dvs. dacă doriți să urmați acest articol. Pentru a obține același cadru, inclusiv navigarea, înlocuiți pages/_app.js cu acest fișier.

Mai multe după săritură! Continuați să citiți mai jos ↓

Înființat

Începem prin a configura un nou proiect utilizând create-next-app și schimbând directorul său:

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

Va trebui să citim fișierele Markdown mai târziu. Pentru a face acest lucru mai ușor, mai adăugăm câteva dependențe înainte de a începe.

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

Odată ce instalarea este finalizată, putem dev scriptul de dezvoltare pentru a începe proiectul nostru:

 multiauthor-blog$ yarn dev

Acum putem explora site-ul nostru. În browser, deschideți https://localhost:3000. Ar trebui să vedeți pagina implicită adăugată de create-next-app.

Pagina implicită creată de create-next-app.
Dacă vedeți asta, configurația dvs. funcționează. (Previzualizare mare)

În scurt timp, vom avea nevoie de o navigare pentru a ajunge la paginile noastre. Le putem adăuga în pages/_app.js chiar înainte ca paginile să existe.

 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> </> ) }

Pe parcursul acestui articol, vom adăuga aceste pagini lipsă către care indică navigarea. Să adăugăm mai întâi câteva postări, astfel încât să avem ceva cu care să lucrăm pe o pagină de prezentare generală a blogului.

Crearea de postări

Pentru a păstra conținutul nostru separat de cod, ne vom pune postările într-un director numit _posts/ . Pentru a ușura scrierea și editarea, vom crea fiecare postare ca fișier Markdown. Numele fișierului fiecărei postări va servi mai târziu ca melc în rutele noastre. Fișierul _posts/hello-world.md va fi accesibil sub /posts/hello-world , de exemplu.

Unele informații, cum ar fi titlul complet și un scurt fragment, se află în prima pagină la începutul fișierului.

 --- 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, …

Adăugați mai multe fișiere ca acesta, astfel încât blogul să nu înceapă gol:

 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/ └─ …

Puteți să vă adăugați propriile postări sau să luați aceste exemple de postări din depozitul GitHub.

Listarea tuturor postărilor

Acum că avem câteva postări, avem nevoie de o modalitate de a le introduce pe blogul nostru. Să începem prin a adăuga o pagină care le listează pe toate, servind drept index al blogului nostru.

În Next.js, un fișier creat sub pages/posts/index.js va fi accesibil ca /posts pe site-ul nostru. Fișierul trebuie să exporte o funcție care va servi drept corp al acelei pagini. Prima sa versiune arată cam așa:

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

Nu ajungem prea departe pentru că încă nu avem o modalitate de a citi fișierele Markdown. Putem deja naviga la https://localhost:3000/posts, dar vedem doar titlul.

O pagină goală cu un titlu care spune „Postări”.
Putem accesa pagina noastră și putem începe să o umplem de viață. (Previzualizare mare)

Acum avem nevoie de o modalitate de a ne trimite postările acolo. Next.js folosește o funcție numită getStaticProps() pentru a transmite date unei componente de pagină. Funcția transmite elementele de props din obiectul returnat componentului ca elemente de recuzită.

Din getStaticProps() , vom trece postările către componentă ca un suport numit posts . Vom codifica două postări de substituent în acest prim pas. Începând astfel, definim în ce format dorim să primim mai târziu postările reale. Dacă o funcție de ajutor le returnează în acest format, putem trece la acesta fără a schimba componenta.

Prezentarea generală a postării nu va afișa textul integral al postărilor. Pentru această pagină, titlul, fragmentul, permalinkul și data fiecărei postări sunt suficiente.

 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", + } + ] + } + } +}

Pentru a verifica conexiunea, putem lua postările din recuzită și le putem arăta în componenta Posts . Vom include titlul, data creării, fragmentul și un link către postare. Deocamdată, acel link nu va duce încă nicăieri.

 +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() { … }

După reîncărcarea paginii în browser, acum arată aceste două postări:

O listă a celor două postări de substituent.
Conexiunea funcționează. Acum putem lucra la publicarea unor postări reale aici. (Previzualizare mare)

Nu vrem să codificăm pentru totdeauna toate postările de blog în getStaticProps() . La urma urmei, de aceea am creat mai devreme toate aceste fișiere în directorul _posts/ . Acum avem nevoie de o modalitate de a citi acele fișiere și de a le transmite conținutul către componenta paginii.

Există câteva moduri în care am putea face asta. Am putea citi fișierele chiar în getStaticProps() . Deoarece această funcție rulează pe server și nu pe client, avem acces la modulele Node.js native precum fs în ea. Am putea citi, transforma și chiar manipula fișiere locale în același fișier în care păstrăm componenta paginii.

Pentru a menține fișierul scurt și concentrat pe o singură sarcină, vom muta acea funcționalitate într-un fișier separat. În acest fel, componenta Posts trebuie doar să afișeze datele, fără a fi nevoie să citească și acele date în sine. Acest lucru adaugă o oarecare separare și organizare proiectului nostru.

Prin convenție, vom pune funcții de citire a datelor într-un fișier numit lib/api.js . Fișierul respectiv va deține toate funcțiile care ne captează conținutul pentru componentele care îl afișează.

Pentru pagina de prezentare generală a postărilor, dorim o funcție care citește, procesează și returnează toate postările. Îl vom numi getAllPosts() . În el, mai întâi folosim path.join() pentru a construi calea către directorul _posts/ . Apoi folosim fs.readdirSync() pentru a citi acel director, care ne oferă numele tuturor fișierelor din el. Mapând peste aceste nume, citim apoi fiecare fișier pe rând.

 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 }) }

După citirea fișierului, obținem conținutul acestuia ca un șir lung. Pentru a separa prima pagină de textul postării, trecem acel șir prin gray-matter . De asemenea, vom lua slug-ul fiecărei postări eliminând .md de la sfârșitul numelui său de fișier. Avem nevoie de acel slug pentru a construi adresa URL de la care postarea va fi accesibilă mai târziu. Deoarece nu avem nevoie de corpul Markdown al postărilor pentru această funcție, putem ignora conținutul rămas.

 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}`, + } }) }

Observați cum răspândim ...data în obiectul returnat aici. Acest lucru ne permite să accesăm valorile din prima sa ca {post.title} în loc de {post.data.title} mai târziu.

Înapoi în pagina noastră de prezentare generală a postărilor, acum putem înlocui postările substituenților cu această nouă funcție.

 +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(), } } }

După reîncărcarea browserului, acum vedem postările noastre reale în loc de substituenții pe care îi aveam înainte.

O listă a postărilor noastre reale de blog.
Datorită funcției de ajutor, această pagină arată acum postările noastre reale. (Previzualizare mare)

Adăugarea de pagini de postare individuale

Linkurile pe care le-am adăugat la fiecare postare nu duc încă nicăieri. Nu există încă nicio pagină care să răspundă la URL-uri precum /posts/hello-world . Cu rutarea dinamică, putem adăuga o pagină care se potrivește cu toate căile astfel.

Un fișier creat ca pages/posts/[slug].js se va potrivi cu toate adresele URL care arată ca /posts/abc . Valoarea care apare în loc de [slug] în adresa URL va fi disponibilă pentru pagină ca parametru de interogare. Putem folosi asta în getStaticProps() din pagina corespunzătoare ca params.slug pentru a apela o funcție de ajutor.

Ca o contrapartidă la getAllPosts() , vom apela acea funcție de ajutor getPostBySlug(slug) . În loc de toate postările, va returna o singură postare care se potrivește cu melcul pe care îl trecem. Pe pagina unei postări, trebuie să arătăm și conținutul Markdown al fișierului de bază.

Pagina pentru postări individuale arată ca cea pentru prezentarea generală a postării. În loc să transmitem posts către pagină în getStaticProps() , transmitem doar o singură post . Să facem mai întâi configurația generală înainte de a ne uita la cum să transformăm corpul Markdown al postării în HTML utilizabil. Vom sări peste postarea substituentului aici, folosind funcția de ajutor pe care o vom adăuga imediat la pasul următor.

 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), }, } }

Acum trebuie să adăugăm funcția getPostBySlug(slug) în fișierul nostru de ajutor lib/api.js . Este ca getAllPosts() , cu câteva diferențe notabile. Deoarece putem obține numele fișierului postării din slug, nu trebuie să citim mai întâi întregul director. Dacă slug-ul este 'hello-world' , vom citi un fișier numit _posts/hello-world.md . Dacă acel fișier nu există, Next.js va afișa o pagină de eroare 404.

O altă diferență față de getAllPosts() este că de data aceasta, trebuie să citim și conținutul Markdown al postării. Îl putem returna ca HTML pregătit pentru redare în loc de Markdown brut, procesându-l mai întâi cu 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, + } +}

În teorie, am putea folosi funcția getAllPosts() în interiorul getPostBySlug(slug) . Am primi mai întâi toate postările cu el, pe care apoi le-am putea căuta una care se potrivește cu melcul dat. Asta ar însemna că ar trebui să citim întotdeauna toate postările înainte de a putea obține unul singur, ceea ce este o muncă inutilă. De asemenea, getAllPosts() nu returnează conținutul Markdown al postărilor. L-am putea actualiza pentru a face asta, caz în care ar lucra mai mult decât are nevoie în prezent.

Deoarece cele două funcții de ajutor fac lucruri diferite, le vom păstra separate. Astfel, putem concentra funcțiile exact și numai asupra sarcinii pe care trebuie să o facă fiecare dintre ele.

Paginile care utilizează rutarea dinamică pot furniza un getStaticPaths() lângă getStaticProps() . Această funcție îi spune Next.js pentru ce valori ale segmentelor de cale dinamică să creeze pagini. Le putem furniza folosind getAllPosts() și returnând o listă de obiecte care definesc slug -ul fiecărei postări.

 -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, + }, + })), + } +}

Deoarece analizăm conținutul Markdown în getPostBySlug(slug) , îl putem reda în pagină acum. Trebuie să folosim dangerouslySetInnerHTML pentru acest pas, astfel încât Next.js să poată reda HTML-ul din spatele post.body . În ciuda numelui său, este sigur să folosiți proprietatea în acest scenariu. Deoarece avem control deplin asupra postărilor noastre, este puțin probabil ca acestea să injecteze scripturi nesigure.

 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() { … }

Dacă urmăm unul dintre linkurile din prezentarea generală a postării, ajungem acum la pagina proprie a postării respective.

Pagina unei postări individuale.
Putem arăta conținutul postării, dar nu știm încă cine a scris-o. (Previzualizare mare)

Adăugarea de autori

Acum că avem postări conectate, trebuie să repetăm ​​aceiași pași pentru autorii noștri. De data aceasta, vom folosi JSON în loc de Markdown pentru a le descrie. Putem amesteca diferite tipuri de fișiere în același proiect ca acesta oricând are sens. Funcțiile de ajutor pe care le folosim pentru a citi fișierele se ocupă de orice diferențe pentru noi. Paginile pot folosi aceste funcții fără a ști în ce format stocăm conținutul nostru.

Mai întâi, creați un director numit _authors/ și adăugați câteva fișiere de autor la el. Așa cum am făcut cu postările, numiți fișierele după slugul fiecărui autor. Vom folosi asta pentru a căuta autori mai târziu. În fiecare fișier, specificăm numele complet al autorului într-un obiect JSON.

 { "name": "Adrian Webber" }

Deocamdată este suficient să avem doi autori în proiectul nostru.

Pentru a le da mai multă personalitate, să adăugăm și o poză de profil pentru fiecare autor. Vom pune acele fișiere statice în directorul public/ . Prin denumirea fișierelor după același slug, le putem conecta numai folosind convenția implicită. Am putea adăuga calea imaginii la fișierul JSON al fiecărui autor pentru a le lega pe cele două. Prin denumirea tuturor fișierelor de către slugs, putem gestiona această conexiune fără a fi nevoie să o scriem. Obiectele JSON trebuie să conțină doar informații pe care nu le putem construi cu cod.

Când ați terminat, directorul de proiect ar trebui să arate cam așa.

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

La fel ca și în cazul postărilor, acum avem nevoie de funcții de ajutor pentru a citi toți autorii și a obține autori individuali. Noile funcții getAllAuthors() și getAuthorBySlug(slug) merg, de asemenea, în lib/api.js . Ei fac aproape exact la fel ca omologii lor de post. Deoarece folosim JSON pentru a descrie autorii, nu este nevoie să analizăm niciun Markdown cu remark aici. De asemenea, nu avem nevoie gray-matter pentru a analiza materia frontală. În schimb, putem folosi JSON.parse() încorporat în JavaScript pentru a citi conținutul text al fișierelor noastre în obiecte.

 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" }

Cu aceste cunoștințe, funcțiile noastre de ajutor arată astfel:

 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, + } +}

Cu o modalitate de a citi autorii în aplicația noastră, acum putem adăuga o pagină care îi listează pe toți. Crearea unei noi pagini sub pages/authors/index.js ne oferă o pagină /authors pe site-ul nostru.

Funcțiile de ajutor se ocupă de citirea fișierelor pentru noi. Această componentă a paginii nu trebuie să știe că autorii sunt fișiere JSON în sistemul de fișiere. Poate folosi getAllAuthors() fără să știe de unde sau cum își obține datele. Formatul nu contează atâta timp cât funcțiile noastre de ajutor returnează datele într-un format cu care putem lucra. Abstracții ca aceasta ne permit să amestecăm diferite tipuri de conținut în aplicația noastră.

Pagina de index pentru autori seamănă foarte mult cu cea pentru postări. Obținem toți autorii în getStaticProps() , care îi transmite componentei Authors . Această componentă mapează fiecare autor și listează câteva informații despre el. Nu trebuie să construim alte linkuri sau URL-uri din slug. Funcția de ajutor returnează deja autorii într-un format utilizabil.

 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(), }, } }

Dacă vizităm /authors pe site-ul nostru, vedem o listă cu toți autorii cu numele și imaginile lor.

Lista autorilor.
Putem enumera toți autorii, dar nu avem de unde să știm cu câte articole au contribuit. (Previzualizare mare)

Linkurile către profilurile autorilor nu duc încă nicăieri. Pentru a adăuga paginile de profil, creăm un fișier sub pages/authors/[slug].js . Deoarece autorii nu au conținut text, tot ce putem adăuga pentru moment sunt numele și fotografiile lor de profil. De asemenea, avem nevoie de un alt getStaticPaths() pentru a spune Next.js pentru ce slug să construim pagini.

 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, }, })), } }

Cu aceasta, avem acum o pagină de bază de profil de autor care este foarte ușoară în ceea ce privește informațiile.

Pagina de profil a unui autor, care arată numele și fotografia acestuia.
Pagina de profil a unui autor este aproape goală în acest moment. (Previzualizare mare)

În acest moment, autorii și postările nu sunt încă conectați. Vom construi acea punte în continuare, astfel încât să putem adăuga o listă a postărilor fiecărui autor pe paginile lor de profil.

Conectarea postărilor și a autorilor

Pentru a conecta două bucăți de conținut, trebuie să facem referință una în cealaltă. Deoarece identificăm deja postările și autorii după slug-urile lor, le vom face referire cu asta. Am putea adăuga autori la postări și postări la autori, dar o direcție este suficientă pentru a le lega. Deoarece dorim să atribuim postări autorilor, vom adăuga slug-ul autorului în prima pagină a fiecărei postări.

 --- 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, …

Dacă rămânem așa, rularea postării prin gray-matter adaugă câmpul de autor la postare ca șir:

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

Pentru a obține obiectul care reprezintă autorul, putem folosi acel slug și apelăm getAuthorBySlug(slug) cu el.

 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" // }

Pentru a adăuga autorul pe pagina unei singure postări, trebuie să apelăm o dată getAuthorBySlug(slug) în 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), + }, }, } }

Observați cum răspândim ...post într-un obiect numit și post în getStaticProps() . Prin plasarea author după acea linie, ajungem să înlocuim versiunea șir a autorului cu obiectul său complet. Acest lucru ne permite să accesăm proprietățile unui autor prin post.author.name din componenta Post .

Odată cu această modificare, primim acum un link către pagina de profil a autorului, completat cu numele și fotografia acestuia, pe pagina unei postări.

Pagina finală a postării, care include acum numele autorului și fotografia.
Postarea este acum atribuită corect autorului. (Previzualizare mare)

Adăugarea de autori la pagina de prezentare generală a postării necesită o modificare similară. În loc să apelăm o singură dată getAuthorBySlug(slug) , trebuie să mapăm toate postările și să-l apelăm pentru fiecare dintre ele.

 +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), + })), } } }

Asta adaugă autorii la fiecare postare din prezentarea generală a postării:

O listă de postări de blog, inclusiv numele și fotografiile autorilor lor.
Acesta arată ca un blog adevărat acum. (Previzualizare mare)

Nu este nevoie să adăugăm o listă cu postările unui autor în fișierul lor JSON. Pe paginile lor de profil, primim mai întâi toate postările cu getAllPosts() . Putem apoi filtra lista completă pentru cele atribuite acestui autor.

 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() { … }

Aceasta ne oferă o listă de articole pe pagina de profil a fiecărui autor.

Pagina de profil a unui autor, care include acum o listă de link-uri către postările lor.
Acum putem lista și link-uri către postările fiecărui autor. (Previzualizare mare)

Pe pagina de prezentare generală a autorului, vom adăuga doar câte postări au scris pentru a nu aglomera interfața.

 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), + })), } } }

Cu aceasta, pagina de prezentare generală a autorii arată câte postări a contribuit fiecare autor.

Lista autorilor cu numărul lor de postări.
Acum putem pune numărul lor de postări contribuite cu intrarea fiecărui autor. (Previzualizare mare)

Si asta e! Postările și autorii sunt complet conectați acum. Putem ajunge de la o postare la pagina de profil a unui autor și de acolo la celelalte postări ale acestuia.

Rezumat și perspective

În acest articol, am conectat două tipuri de conținut înrudite prin limacurile lor unice. Definirea relației de la post la autor a permis o varietate de scenarii. Acum putem să arătăm autorul pe fiecare postare și să le listăm postările pe paginile de profil.

Cu această tehnică, putem adăuga multe alte tipuri de relații. Fiecare postare poate avea un recenzent pe lângă un autor. Putem configura asta adăugând un câmp de reviewer la subiectul principal al unei postări.

 --- 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, …

Pe sistemul de fișiere, recenzorul este un alt autor din directorul _authors/ . Putem folosi getAuthorBySlug(slug) pentru a obține și informațiile lor.

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

Am putea chiar să susținem coautorii prin numirea a doi sau mai mulți autori pe o postare în loc de doar o singură persoană.

 --- 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, …

În acest scenariu, nu am mai putut căuta un singur autor în getStaticProps() al unei postări. În schimb, am mapa peste această serie de autori pentru a-i obține pe toți.

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

Putem produce și alte tipuri de scenarii cu această tehnică. Permite orice fel de relație unu-la-unu, unu-la-mulți sau chiar multi-la-mulți. Dacă proiectul dvs. include și buletine informative și studii de caz, puteți adăuga și autori la fiecare dintre ele.

Pe un site despre universul Marvel, am putea conecta personajele și filmele în care apar. În sport, am putea conecta jucătorii și echipele pentru care joacă în prezent.

Deoarece funcțiile de ajutor ascund sursa de date, conținutul poate proveni din sisteme diferite. Am putea citi articole din sistemul de fișiere, comentarii dintr-un API și să le îmbinam în codul nostru. Dacă o parte de conținut se referă la un alt tip de conținut, le putem conecta cu acest tipar.

Resurse suplimentare

Next.js oferă mai multe informații despre funcțiile pe care le-am folosit în pagina lor despre Preluarea datelor. Include link-uri către exemple de proiecte care preiau date din diferite tipuri de surse.

Dacă doriți să duceți acest proiect de pornire mai departe, consultați aceste articole:

  • Crearea unei clone de site-uri CSS Tricks cu Strapi și Next.js
    Înlocuiți fișierele de pe sistemul de fișiere local cu un backend alimentat de Strapi.
  • Compararea metodelor de stilare în Next.js
    Explorați diferite moduri de a scrie CSS personalizat pentru a schimba stilul acestui starter.
  • Markdown/MDX cu Next.js
    Adăugați MDX la proiectul dvs., astfel încât să puteți utiliza componentele JSX și React în Markdown.