Créer un blog multi-auteur avec Next.js
Publié: 2022-03-10Dans cet article, nous allons créer un blog avec Next.js qui prend en charge deux auteurs ou plus. Nous attribuerons chaque publication à un auteur et montrerons son nom et sa photo avec ses publications. Chaque auteur reçoit également une page de profil, qui répertorie tous les messages auxquels il a contribué. Cela ressemblera à ceci :
Nous allons conserver toutes les informations dans des fichiers sur le système de fichiers local. Les deux types de contenu, publications et auteurs, utiliseront différents types de fichiers. Les publications contenant beaucoup de texte utiliseront Markdown, ce qui facilitera le processus d'édition. Comme les informations sur les auteurs sont plus légères, nous les conserverons dans des fichiers JSON. Les fonctions d'assistance faciliteront la lecture de différents types de fichiers et la combinaison de leur contenu.
Next.js nous permet de lire sans effort des données provenant de différentes sources et de différents types. Grâce à son routage dynamique et à son next/link
, nous pouvons rapidement construire et naviguer vers les différentes pages de notre site. Nous obtenons également une optimisation d'image gratuite avec le package next/image
.
En choisissant les "piles incluses" Next.js, nous pouvons nous concentrer sur notre application elle-même. Nous n'avons pas besoin de passer du temps sur le travail préparatoire répétitif que les nouveaux projets accompagnent souvent. Au lieu de tout construire à la main, nous pouvons compter sur le cadre testé et éprouvé. La communauté importante et active derrière Next.js permet d'obtenir facilement de l'aide si nous rencontrons des problèmes en cours de route.
Après avoir lu cet article, vous pourrez ajouter de nombreux types de contenu à un seul projet Next.js. Vous pourrez également créer des relations entre eux. Cela vous permet de lier des éléments tels que des auteurs et des publications, des cours et des leçons, ou des acteurs et des films.
Cet article suppose une connaissance de base de Next.js. Si vous ne l'avez jamais utilisé auparavant, vous voudrez peut-être lire comment il gère les pages et récupère les données pour eux en premier.
Nous ne couvrirons pas le style dans cet article et nous nous concentrerons plutôt sur le fait que tout fonctionne. Vous pouvez obtenir le résultat sur GitHub. Il existe également une feuille de style que vous pouvez déposer dans votre projet si vous souhaitez suivre cet article. Pour obtenir le même cadre, y compris la navigation, remplacez vos pages/_app.js
par ce fichier.
Installer
Nous commençons par configurer un nouveau projet en utilisant create-next-app
et en changeant son répertoire :
$ npx create-next-app multiauthor-blog $ cd multiauthor-blog
Nous aurons besoin de lire les fichiers Markdown plus tard. Pour faciliter cela, nous ajoutons également quelques dépendances supplémentaires avant de commencer.
multiauthor-blog$ yarn add gray-matter remark remark-html
Une fois l'installation terminée, nous pouvons exécuter le dev
de développement pour démarrer notre projet :
multiauthor-blog$ yarn dev
Nous pouvons maintenant explorer notre site. Dans votre navigateur, ouvrez https://localhost:3000. Vous devriez voir la page par défaut ajoutée par create-next-app.
Dans quelques instants, nous aurons besoin d'une navigation pour accéder à nos pages. Nous pouvons les ajouter dans pages/_app.js
avant même que les pages n'existent.
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> </> ) }
Tout au long de cet article, nous ajouterons ces pages manquantes vers lesquelles pointe la navigation. Commençons par ajouter quelques articles afin d'avoir quelque chose avec quoi travailler sur une page de présentation du blog.
Création de messages
Pour garder notre contenu séparé du code, nous placerons nos messages dans un répertoire appelé _posts/
. Pour faciliter l'écriture et l'édition, nous allons créer chaque article sous forme de fichier Markdown. Le nom de fichier de chaque publication servira de slug dans nos routes plus tard. Le fichier _posts/hello-world.md
sera accessible sous /posts/hello-world
, par exemple.
Certaines informations, comme le titre complet et un court extrait, figurent en première ligne au début du fichier.
--- 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, …
Ajoutez quelques fichiers supplémentaires comme celui-ci pour que le blog ne démarre pas vide :
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/ └─ …
Vous pouvez ajouter les vôtres ou récupérer ces exemples de publications dans le référentiel GitHub.
Liste de tous les messages
Maintenant que nous avons quelques articles, nous avons besoin d'un moyen de les publier sur notre blog. Commençons par ajouter une page qui les répertorie toutes, servant d'index de notre blog.
Dans Next.js, un fichier créé sous pages/posts/index.js
sera accessible en tant que /posts
sur notre site. Le fichier doit exporter une fonction qui servira de corps de cette page. Sa première version ressemble à ceci :
export default function Posts() { return ( <div className="posts"> <h1>Posts</h1> {/* TODO: render posts */} </div> ) }
Nous n'allons pas très loin car nous n'avons pas encore de moyen de lire les fichiers Markdown. Nous pouvons déjà accéder à https://localhost:3000/posts, mais nous ne voyons que l'en-tête.
Nous avons maintenant besoin d'un moyen d'afficher nos messages là-bas. Next.js utilise une fonction appelée getStaticProps()
pour transmettre des données à un composant de page. La fonction transmet les props
de l'objet renvoyé au composant en tant qu'accessoires.
À partir de getStaticProps()
, nous allons transmettre les posts au composant sous la forme d'un accessoire appelé posts
. Nous allons coder en dur deux messages fictifs dans cette première étape. En commençant de cette façon, nous définissons dans quel format nous voulons recevoir plus tard les vrais messages. Si une fonction d'assistance les renvoie dans ce format, nous pouvons basculer vers celui-ci sans changer le composant.
L'aperçu des publications n'affichera pas le texte intégral des publications. Pour cette page, le titre, l'extrait, le lien permanent et la date de chaque publication suffisent.
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", + } + ] + } + } +}
Pour vérifier la connexion, nous pouvons saisir les messages des accessoires et les afficher dans le composant Posts
. Nous inclurons le titre, la date de création, un extrait et un lien vers le message. Pour l'instant, ce lien ne mènera nulle part encore.
+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() { … }
Après avoir rechargé la page dans le navigateur, elle affiche maintenant ces deux articles :
Nous ne voulons pas coder en dur tous nos articles de blog dans getStaticProps()
pour toujours. Après tout, c'est pourquoi nous avons créé tous ces fichiers dans le _posts/
plus tôt. Nous avons maintenant besoin d'un moyen de lire ces fichiers et de transmettre leur contenu au composant de page.
Il y a plusieurs façons de le faire. Nous pourrions lire les fichiers directement dans getStaticProps()
. Comme cette fonction s'exécute sur le serveur et non sur le client, nous avons accès aux modules Node.js natifs comme fs
. Nous pourrions lire, transformer et même manipuler des fichiers locaux dans le même fichier que nous gardons le composant de la page.
Pour que le fichier reste court et concentré sur une tâche, nous allons plutôt déplacer cette fonctionnalité vers un fichier séparé. De cette façon, le composant Posts
n'a besoin que d'afficher les données, sans avoir à lire ces données elles-mêmes. Cela ajoute une certaine séparation et organisation à notre projet.
Par convention, nous allons mettre des fonctions lisant des données dans un fichier appelé lib/api.js
. Ce fichier contiendra toutes les fonctions qui récupèrent notre contenu pour les composants qui l'affichent.
Pour la page de présentation des publications, nous voulons une fonction qui lit, traite et renvoie toutes les publications. Nous l'appellerons getAllPosts()
. Dans celui-ci, nous utilisons d'abord path.join()
pour construire le chemin vers le _posts/
. Nous utilisons ensuite fs.readdirSync()
pour lire ce répertoire, ce qui nous donne les noms de tous les fichiers qu'il contient. En cartographiant ces noms, nous lisons ensuite chaque fichier à tour de rôle.
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 }) }
Après avoir lu le fichier, nous obtenons son contenu sous la forme d'une longue chaîne. Pour séparer le frontmatter du texte du message, nous passons cette chaîne à travers gray-matter
. Nous allons également saisir le slug de chaque article en supprimant le .md
à la fin de son nom de fichier. Nous avons besoin de ce slug pour créer l'URL à partir de laquelle le message sera accessible plus tard. Comme nous n'avons pas besoin du corps Markdown des messages pour cette fonction, nous pouvons ignorer le contenu restant.
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}`, + } }) }
Notez comment nous répartissons ...data
dans l'objet renvoyé ici. Cela nous permet d'accéder aux valeurs de son frontmatter en tant que {post.title}
au lieu de {post.data.title}
plus tard.
De retour dans notre page d'aperçu des publications, nous pouvons désormais remplacer les publications fictives par cette nouvelle fonction.
+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(), } } }
Après avoir rechargé le navigateur, nous voyons maintenant nos vrais messages au lieu des espaces réservés que nous avions auparavant.
Ajout de pages de publication individuelles
Les liens que nous avons ajoutés à chaque message ne mènent nulle part encore. Il n'y a pas encore de page qui réponde aux URL comme /posts/hello-world
. Avec le routage dynamique, nous pouvons ajouter une page qui correspond à tous les chemins comme celui-ci.
Un fichier créé en tant que pages/posts/[slug].js
correspondra à toutes les URL qui ressemblent à /posts/abc
. La valeur qui apparaît à la place de [slug]
dans l'URL sera disponible pour la page en tant que paramètre de requête. Nous pouvons l'utiliser dans getStaticProps()
de la page correspondante en tant que params.slug
pour appeler une fonction d'assistance.
En contrepartie de getAllPosts()
, nous appellerons cette fonction d'assistance getPostBySlug(slug)
. Au lieu de tous les messages, il renverra un seul message qui correspond au slug que nous lui transmettons. Sur la page d'un article, nous devons également afficher le contenu Markdown du fichier sous-jacent.
La page des publications individuelles ressemble à celle de la vue d'ensemble des publications. Au lieu de transmettre des posts
à la page dans getStaticProps()
, nous ne transmettons qu'une seule post
. Faisons d'abord la configuration générale avant de voir comment transformer le corps Markdown de la publication en HTML utilisable. Nous allons ignorer le message d'espace réservé ici, en utilisant la fonction d'assistance que nous ajouterons immédiatement à l'étape suivante.
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), }, } }
Nous devons maintenant ajouter la fonction getPostBySlug(slug)
à notre fichier d'aide lib/api.js
. C'est comme getAllPosts()
, avec quelques différences notables. Comme nous pouvons obtenir le nom de fichier du message à partir du slug, nous n'avons pas besoin de lire tout le répertoire en premier. Si le slug est 'hello-world'
, nous allons lire un fichier appelé _posts/hello-world.md
. Si ce fichier n'existe pas, Next.js affichera une page d'erreur 404.
Une autre différence avec getAllPosts()
est que cette fois, nous devons également lire le contenu Markdown du message. Nous pouvons le renvoyer sous forme de HTML prêt pour le rendu au lieu de Markdown brut en le traitant d'abord avec une 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, + } +}
En théorie, nous pourrions utiliser la fonction getAllPosts()
à l'intérieur getPostBySlug(slug)
. Nous obtiendrions d'abord tous les messages avec, que nous pourrions ensuite rechercher celui qui correspond au slug donné. Cela signifierait que nous aurions toujours besoin de lire tous les messages avant de pouvoir en obtenir un seul, ce qui est un travail inutile. getAllPosts()
ne renvoie pas non plus le contenu Markdown des publications. Nous pourrions le mettre à jour pour le faire, auquel cas il ferait plus de travail que nécessaire actuellement.
Parce que les deux fonctions d'assistance font des choses différentes, nous allons les garder séparées. De cette façon, nous pouvons concentrer les fonctions sur exactement et uniquement le travail dont nous avons besoin pour chacune d'entre elles.
Les pages qui utilisent le routage dynamique peuvent fournir un getStaticPaths()
à côté de leur getStaticProps()
. Cette fonction indique à Next.js les valeurs des segments de chemin dynamique pour lesquels créer des pages. Nous pouvons les fournir en utilisant getAllPosts()
et en renvoyant une liste d'objets qui définissent le slug
de chaque publication.
-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, + }, + })), + } +}
Puisque nous analysons le contenu Markdown dans getPostBySlug(slug)
, nous pouvons le rendre sur la page maintenant. Nous devons utiliser dangerouslySetInnerHTML
pour cette étape afin que Next.js puisse rendre le HTML derrière post.body
. Malgré son nom, il est sûr d'utiliser la propriété dans ce scénario. Parce que nous avons un contrôle total sur nos messages, il est peu probable qu'ils injectent des scripts dangereux.
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() { … }
Si nous suivons l'un des liens de l'aperçu de la publication, nous arrivons maintenant à la propre page de cette publication.
Ajout d'auteurs
Maintenant que nous avons des messages câblés, nous devons répéter les mêmes étapes pour nos auteurs. Cette fois, nous utiliserons JSON au lieu de Markdown pour les décrire. Nous pouvons mélanger différents types de fichiers dans le même projet comme celui-ci chaque fois que cela a du sens. Les fonctions d'assistance que nous utilisons pour lire les fichiers s'occupent de toutes les différences pour nous. Les pages peuvent utiliser ces fonctions sans savoir dans quel format nous stockons notre contenu.
Tout d'abord, créez un répertoire appelé _authors/
et ajoutez-y quelques fichiers d'auteur. Comme nous l'avons fait avec les messages, nommez les fichiers par le slug de chaque auteur. Nous l'utiliserons pour rechercher des auteurs plus tard. Dans chaque fichier, nous spécifions le nom complet d'un auteur dans un objet JSON.
{ "name": "Adrian Webber" }
Pour l'instant, avoir deux auteurs dans notre projet suffit.
Pour leur donner un peu plus de personnalité, ajoutons également une photo de profil pour chaque auteur. Nous placerons ces fichiers statiques dans le répertoire public/
. En nommant les fichiers par le même slug, nous pouvons les connecter en utilisant uniquement la convention implicite. Nous pourrions ajouter le chemin de l'image au fichier JSON de chaque auteur pour lier les deux. En nommant tous les fichiers par les slugs, nous pouvons gérer cette connexion sans avoir à l'écrire. Les objets JSON n'ont besoin que de contenir des informations que nous ne pouvons pas construire avec du code.
Lorsque vous avez terminé, votre répertoire de projet devrait ressembler à ceci.
multi-author-blog/ ├─ _authors/ │ ├─ adrian-webber.json │ └─ megan-carter.json ├─ _posts/ │ └─ … ├─ pages/ │ └─ … └─ public/ ├─ adrian-webber.jpg └─ megan-carter.jpg
Comme pour les messages, nous avons maintenant besoin de fonctions d'assistance pour lire tous les auteurs et obtenir des auteurs individuels. Les nouvelles fonctions getAllAuthors()
et getAuthorBySlug(slug)
vont également dans lib/api.js
. Ils font presque exactement la même chose que leurs homologues postaux. Parce que nous utilisons JSON pour décrire les auteurs, nous n'avons pas besoin d'analyser un Markdown avec une remark
ici. Nous n'avons pas non plus besoin gray-matter
pour analyser la matière première. Au lieu de cela, nous pouvons utiliser JSON.parse()
à JavaScript pour lire le contenu textuel de nos fichiers dans des objets.
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" }
Avec cette connaissance, nos fonctions d'assistance ressemblent à ceci :
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, + } +}
Avec un moyen de lire les auteurs dans notre application, nous pouvons maintenant ajouter une page qui les répertorie tous. La création d'une nouvelle page sous pages/authors/index.js
nous donne une page /authors
sur notre site.
Les fonctions d'assistance s'occupent de lire les fichiers pour nous. Ce composant de page n'a pas besoin de savoir que les auteurs sont des fichiers JSON dans le système de fichiers. Il peut utiliser getAllAuthors()
sans savoir où ni comment il obtient ses données. Le format n'a pas d'importance tant que nos fonctions d'assistance renvoient leurs données dans un format avec lequel nous pouvons travailler. Des abstractions comme celle-ci nous permettent de mélanger différents types de contenu dans notre application.
La page d'index des auteurs ressemble beaucoup à celle des publications. Nous obtenons tous les auteurs dans getStaticProps()
, qui les transmet au composant Authors
. Ce composant cartographie chaque auteur et répertorie certaines informations à leur sujet. Nous n'avons pas besoin de créer d'autres liens ou URL à partir du slug. La fonction d'assistance renvoie déjà les auteurs dans un format utilisable.
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(), }, } }
Si nous visitons /authors
sur notre site, nous voyons une liste de tous les auteurs avec leurs noms et photos.
Les liens vers les profils des auteurs ne mènent nulle part encore. Pour ajouter les pages de profil, nous créons un fichier sous pages/authors/[slug].js
. Comme les auteurs n'ont pas de contenu textuel, tout ce que nous pouvons ajouter pour l'instant, ce sont leurs noms et leurs photos de profil. Nous avons également besoin d'un autre getStaticPaths()
pour indiquer à Next.js pour quels slugs créer des pages.
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, }, })), } }
Avec cela, nous avons maintenant une page de profil d'auteur de base qui contient très peu d'informations.
À ce stade, les auteurs et les messages ne sont pas encore connectés. Nous allons ensuite construire ce pont afin de pouvoir ajouter une liste des publications de chaque auteur à leurs pages de profil.
Connecter les messages et les auteurs
Pour connecter deux contenus, nous devons référencer l'un dans l'autre. Puisque nous identifions déjà les publications et les auteurs par leurs slugs, nous les référencerons avec cela. On pourrait ajouter des auteurs aux posts et des posts aux auteurs, mais une seule direction suffit pour les lier. Puisque nous voulons attribuer des publications à des auteurs, nous allons ajouter le slug de l'auteur à l'avant-propos de chaque publication.
--- 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, …
Si nous en restons là, l'exécution du message à travers gray-matter
ajoute le champ auteur au message sous forme de chaîne :
const post = getPostBySlug("hello-world") const author = post.author console.log(author) // "adrian-webber"
Pour obtenir l'objet représentant l'auteur, nous pouvons utiliser ce slug et appeler getAuthorBySlug(slug)
avec.
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" // }
Pour ajouter l'auteur à la page d'un seul article, nous devons appeler getAuthorBySlug(slug)
une fois dans 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), + }, }, } }
Notez comment nous avons réparti ...post
dans un objet également appelé post
dans getStaticProps()
. En plaçant l' author
après cette ligne, nous finissons par remplacer la version chaîne de l'auteur par son objet complet. Cela nous permet d'accéder aux propriétés d'un auteur via post.author.name
dans le composant Post
.
Avec ce changement, nous obtenons maintenant un lien vers la page de profil de l'auteur, avec son nom et sa photo, sur la page d'un article.
L'ajout d'auteurs à la page de présentation des publications nécessite un changement similaire. Au lieu d'appeler getAuthorBySlug(slug)
une fois, nous devons cartographier tous les messages et l'appeler pour chacun d'eux.
+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), + })), } } }
Cela ajoute les auteurs à chaque article dans l'aperçu de l'article :
Nous n'avons pas besoin d'ajouter une liste des publications d'un auteur à son fichier JSON. Sur leurs pages de profil, nous obtenons d'abord tous les messages avec getAllPosts()
. Nous pouvons ensuite filtrer la liste complète pour ceux attribués à cet auteur.
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() { … }
Cela nous donne une liste d'articles sur la page de profil de chaque auteur.
Sur la page de présentation des auteurs, nous n'ajouterons que le nombre de messages qu'ils ont écrits pour ne pas encombrer l'interface.
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), + })), } } }
Ainsi, la page de présentation des auteurs indique le nombre de publications auxquelles chaque auteur a contribué.
Et c'est tout! Les articles et les auteurs sont désormais complètement liés. Nous pouvons passer d'un article à la page de profil d'un auteur, et de là à ses autres articles.
Résumé et perspectives
Dans cet article, nous avons connecté deux types de contenu liés via leurs slugs uniques. Définir la relation entre la publication et l'auteur a permis une variété de scénarios. Nous pouvons maintenant afficher l'auteur sur chaque article et répertorier ses articles sur ses pages de profil.
Avec cette technique, nous pouvons ajouter de nombreux autres types de relations. Chaque message peut avoir un critique au-dessus d'un auteur. Nous pouvons configurer cela en ajoutant un champ de révision au reviewer
d'un message.
--- 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, …
Sur le système de fichiers, le relecteur est un autre auteur du _authors/
. Nous pouvons également utiliser getAuthorBySlug(slug)
pour obtenir leurs informations.
export function getStaticProps({ params }) { const post = getPostBySlug(params.slug) return { props: { post: { ...post, author: getAuthorBySlug(post.author), + reviewer: getAuthorBySlug(post.reviewer), }, }, } }
Nous pourrions même soutenir les co-auteurs en nommant deux auteurs ou plus sur un article au lieu d'une seule personne.
--- 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, …
Dans ce scénario, nous ne pouvions plus rechercher un seul auteur dans getStaticProps()
d'un article. Au lieu de cela, nous cartographierions ce tableau d'auteurs pour les obtenir tous.
export function getStaticProps({ params }) { const post = getPostBySlug(params.slug) return { props: { post: { ...post, - author: getAuthorBySlug(post.author), + authors: post.authors.map(getAuthorBySlug), }, }, } }
Nous pouvons également produire d'autres types de scénarios avec cette technique. Il permet tout type de relation un à un, un à plusieurs ou même plusieurs à plusieurs. Si votre projet comporte également des newsletters et des études de cas, vous pouvez également ajouter des auteurs à chacun d'entre eux.
Sur un site consacré à l'univers Marvel, nous pourrions connecter les personnages et les films dans lesquels ils apparaissent. Dans le sport, nous pourrions connecter les joueurs et les équipes pour lesquelles ils jouent actuellement.
Étant donné que les fonctions d'assistance masquent la source de données, le contenu peut provenir de différents systèmes. Nous pourrions lire des articles du système de fichiers, des commentaires d'une API et les fusionner dans notre code. Si un élément de contenu se rapporte à un autre type de contenu, nous pouvons les connecter avec ce modèle.
Autres ressources
Next.js offre plus d'informations sur les fonctions que nous avons utilisées dans leur page sur la récupération de données. Il comprend des liens vers des exemples de projets qui récupèrent des données à partir de différents types de sources.
Si vous souhaitez approfondir ce projet de démarrage, consultez ces articles :
- Construire un clone de site Web CSS Tricks avec Strapi et Next.js
Remplacez les fichiers sur le système de fichiers local par un backend alimenté par Strapi. - Comparaison des méthodes de style dans Next.js
Explorez différentes manières d'écrire du CSS personnalisé pour modifier le style de ce démarreur. - Markdown/MDX avec Next.js
Ajoutez MDX à votre projet afin de pouvoir utiliser les composants JSX et React dans votre Markdown.