Erstellen eines Multi-Autoren-Blogs mit Next.js

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ Dieser Artikel erklärt, wie wir verschiedene Arten von Inhalten in einer Next.js-Anwendung verbinden können. Mit dieser Technik können wir unseren Projekten jede Art von Eins-zu-Eins-, Eins-zu-Viele- oder sogar Viele-zu-Viele-Beziehungen hinzufügen.

In diesem Artikel werden wir mit Next.js einen Blog erstellen, der zwei oder mehr Autoren unterstützt. Wir ordnen jedem Beitrag einen Autor zu und zeigen seinen Namen und sein Bild mit seinen Beiträgen. Jeder Autor erhält außerdem eine Profilseite, auf der alle von ihm beigesteuerten Beiträge aufgelistet sind. Es wird in etwa so aussehen:

Links: der fertige Blog-Index, den wir bauen werden. Rechts: die Seite eines einzelnen Beitrags, die auf die Profilseite des Autors verlinkt.
Links: der fertige Blog-Index, den wir bauen werden. Rechts: die Seite eines einzelnen Beitrags, die auf die Profilseite des Autors verlinkt. (Große Vorschau)
Die Profilseite eines Autors, die auf alle seine Beiträge verlinkt.
Die Profilseite eines Autors, die auf alle seine Beiträge verlinkt. (Große Vorschau)

Wir werden alle Informationen in Dateien auf dem lokalen Dateisystem speichern. Die beiden Arten von Inhalten, Beiträge und Autoren, verwenden unterschiedliche Dateitypen. Die textlastigen Posts verwenden Markdown, was einen einfacheren Bearbeitungsprozess ermöglicht. Da die Informationen zu Autoren leichter sind, behalten wir diese in JSON-Dateien bei. Hilfsfunktionen erleichtern das Lesen verschiedener Dateitypen und das Kombinieren ihrer Inhalte.

Mit Next.js können wir mühelos Daten aus verschiedenen Quellen und unterschiedlichen Typen lesen. Dank dynamischem Routing und next/link können wir die verschiedenen Seiten unserer Website schnell erstellen und zu ihnen navigieren. Mit dem next/image Paket erhalten wir auch eine Bildoptimierung kostenlos.

Indem wir „Batterien enthalten“ Next.js auswählen, können wir uns auf unsere Anwendung selbst konzentrieren. Wir müssen keine Zeit für die sich wiederholende Vorarbeit aufwenden, die neue Projekte oft mit sich bringen. Anstatt alles von Hand zu bauen, können wir uns auf das erprobte und bewährte Framework verlassen. Die große und aktive Community hinter Next.js macht es einfach, Hilfe zu bekommen, wenn wir unterwegs auf Probleme stoßen.

Nachdem Sie diesen Artikel gelesen haben, können Sie einem einzelnen Next.js-Projekt viele Arten von Inhalten hinzufügen. Sie können auch Beziehungen zwischen ihnen herstellen. So können Sie Dinge wie Autoren und Beiträge, Kurse und Lektionen oder Schauspieler und Filme verlinken.

Dieser Artikel setzt grundlegende Vertrautheit mit Next.js voraus. Wenn Sie es noch nie verwendet haben, sollten Sie sich zuerst darüber informieren, wie es mit Seiten umgeht und Daten für sie abruft.

Wir werden das Styling in diesem Artikel nicht behandeln und uns stattdessen darauf konzentrieren, dass alles funktioniert. Das Ergebnis erhalten Sie auf GitHub. Es gibt auch ein Stylesheet, das Sie in Ihr Projekt einfügen können, wenn Sie diesem Artikel folgen möchten. Um denselben Frame einschließlich der Navigation zu erhalten, ersetzen Sie Ihre pages/_app.js durch diese Datei.

Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Aufstellen

Wir beginnen damit, ein neues Projekt mit create-next-app einzurichten und in sein Verzeichnis zu wechseln:

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

Wir müssen Markdown-Dateien später lesen. Um dies zu vereinfachen, fügen wir auch einige weitere Abhängigkeiten hinzu, bevor wir beginnen.

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

Sobald die Installation abgeschlossen ist, können wir das Entwicklungsskript ausführen, um dev Projekt zu starten:

 multiauthor-blog$ yarn dev

Wir können jetzt unsere Website erkunden. Öffnen Sie in Ihrem Browser https://localhost:3000. Sie sollten die von create-next-app hinzugefügte Standardseite sehen.

Die von create-next-app erstellte Standardseite.
Wenn Sie dies sehen, funktioniert Ihr Setup. (Große Vorschau)

In Kürze benötigen wir eine Navigation, um unsere Seiten zu erreichen. Wir können sie in pages/_app.js , noch bevor die Seiten existieren.

 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 diesem Artikel fügen wir diesen fehlenden Seiten die Navigationspunkte hinzu. Lassen Sie uns zuerst einige Posts hinzufügen, damit wir etwas haben, mit dem wir auf einer Blog-Übersichtsseite arbeiten können.

Beiträge erstellen

Um unseren Inhalt vom Code getrennt zu halten, legen wir unsere Posts in einem Verzeichnis namens _posts/ . Um das Schreiben und Bearbeiten zu vereinfachen, erstellen wir jeden Beitrag als Markdown-Datei. Der Dateiname jedes Beitrags dient später als Slug in unseren Routen. Die Datei _posts/hello-world.md wird beispielsweise unter /posts/hello-world zugänglich sein.

Einige Informationen, wie der vollständige Titel und ein kurzer Auszug, stehen im Vorspann am Anfang der Datei.

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

Fügen Sie ein paar weitere Dateien wie diese hinzu, damit der Blog nicht leer beginnt:

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

Sie können Ihre eigenen hinzufügen oder diese Beispielbeiträge aus dem GitHub-Repository abrufen.

Alle Beiträge auflisten

Jetzt, wo wir ein paar Posts haben, brauchen wir eine Möglichkeit, sie in unseren Blog zu bekommen. Beginnen wir damit, eine Seite hinzuzufügen, die sie alle auflistet und als Index unseres Blogs dient.

In Next.js wird eine unter pages/posts/index.js erstellte Datei als /posts auf unserer Website zugänglich sein. Die Datei muss eine Funktion exportieren, die als Hauptteil dieser Seite dient. Seine erste Version sieht in etwa so aus:

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

Wir kommen nicht sehr weit, weil wir noch keine Möglichkeit haben, die Markdown-Dateien zu lesen. Wir können bereits zu https://localhost:3000/posts navigieren, sehen aber nur die Überschrift.

Eine leere Seite mit der Überschrift „Beiträge“.
Wir können auf unsere Seite zugreifen und anfangen, sie mit Leben zu füllen. (Große Vorschau)

Wir brauchen jetzt einen Weg, um unsere Posts dorthin zu bringen. Next.js verwendet eine Funktion namens getStaticProps() , um Daten an eine Seitenkomponente zu übergeben. Die Funktion übergibt die props im zurückgegebenen Objekt als Props an die Komponente.

Von getStaticProps() wir die Posts als Prop namens posts an die Komponente. Wir werden in diesem ersten Schritt zwei Platzhalter-Posts fest codieren. Damit legen wir fest, in welchem ​​Format wir später die eigentlichen Posts erhalten wollen. Wenn eine Hilfsfunktion sie in diesem Format zurückgibt, können wir darauf umschalten, ohne die Komponente zu ändern.

Die Beitragsübersicht zeigt nicht den vollständigen Text der Beiträge. Für diese Seite reichen Titel, Auszug, Permalink und Datum jedes Beitrags aus.

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

Um die Verbindung zu überprüfen, können wir die Posts von den Requisiten greifen und sie in der Posts -Komponente anzeigen. Wir fügen den Titel, das Erstellungsdatum, den Auszug und einen Link zum Beitrag hinzu. Im Moment führt dieser Link noch nirgendwo hin.

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

Nach dem Neuladen der Seite im Browser zeigt er nun diese beiden Beiträge:

Eine Liste unserer zwei Platzhalter-Beiträge.
Die Verbindung funktioniert. Jetzt können wir daran arbeiten, hier echte Beiträge zu veröffentlichen. (Große Vorschau)

Wir wollen nicht alle unsere Blogbeiträge für immer in getStaticProps() . Schließlich haben wir deshalb all diese Dateien zuvor im Verzeichnis _posts/ erstellt. Wir brauchen jetzt eine Möglichkeit, diese Dateien zu lesen und ihren Inhalt an die Seitenkomponente zu übergeben.

Es gibt ein paar Möglichkeiten, wie wir das tun könnten. Wir könnten die Dateien direkt in getStaticProps() lesen. Da diese Funktion auf dem Server und nicht auf dem Client ausgeführt wird, haben wir Zugriff auf native Node.js-Module wie fs darin. Wir könnten lokale Dateien in derselben Datei lesen, transformieren und sogar manipulieren, in der wir die Seitenkomponente aufbewahren.

Um die Datei kurz zu halten und sich auf eine Aufgabe zu konzentrieren, verschieben wir diese Funktionalität stattdessen in eine separate Datei. Auf diese Weise muss die Posts -Komponente die Daten nur anzeigen, ohne diese Daten auch selbst lesen zu müssen. Dies fügt unserem Projekt eine gewisse Trennung und Organisation hinzu.

Per Konvention werden wir Funktionen, die Daten lesen, in eine Datei namens lib/api.js . Diese Datei enthält alle Funktionen, die unseren Inhalt für die Komponenten erfassen, die ihn anzeigen.

Für die Posts-Übersichtsseite wollen wir eine Funktion, die alle Posts liest, verarbeitet und zurückgibt. Wir nennen es getAllPosts() . Darin verwenden wir zuerst path.join() , um den Pfad zum Verzeichnis _posts/ zu erstellen. Wir verwenden dann fs.readdirSync() , um dieses Verzeichnis zu lesen, wodurch wir die Namen aller darin enthaltenen Dateien erhalten. Durch Zuordnung dieser Namen lesen wir dann jede Datei der Reihe nach.

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

Nach dem Lesen der Datei erhalten wir ihren Inhalt als langen String. Um die Frontmatter vom Text des Beitrags zu trennen, führen wir diese Zeichenfolge durch gray-matter . Wir werden uns auch den Slug jedes Beitrags schnappen, indem wir die .md vom Ende seines Dateinamens entfernen. Wir brauchen diesen Slug, um die URL zu erstellen, über die später auf den Beitrag zugegriffen werden kann. Da wir für diese Funktion den Markdown-Body der Posts nicht benötigen, können wir den restlichen Inhalt ignorieren.

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

Beachten Sie, wie wir hier ...data in das zurückgegebene Objekt verteilen. Dadurch können wir später auf Werte aus seiner Frontmatter als {post.title} statt als {post.data.title} {post.data.title} .

Zurück auf unserer Beitragsübersichtsseite können wir nun die Platzhalterbeiträge durch diese neue Funktion ersetzen.

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

Nach dem Neuladen des Browsers sehen wir jetzt unsere echten Beiträge statt der Platzhalter, die wir vorher hatten.

Eine Liste unserer echten Blogbeiträge.
Dank der Hilfsfunktion zeigt diese Seite jetzt unsere echten Beiträge. (Große Vorschau)

Hinzufügen einzelner Beitragsseiten

Die Links, die wir jedem Beitrag hinzugefügt haben, führen noch nirgendwo hin. Es gibt noch keine Seite, die auf URLs wie /posts/hello-world reagiert. Mit dynamischem Routing können wir eine Seite hinzufügen, die allen Pfaden wie dieser entspricht.

Eine als pages/posts/[slug].js erstellte Datei stimmt mit allen URLs überein, die wie /posts/abc aussehen. Der Wert, der anstelle von [slug] in der URL erscheint, steht der Seite als Abfrageparameter zur Verfügung. Wir können das in getStaticProps() der entsprechenden Seite als params.slug , um eine Hilfsfunktion aufzurufen.

Als Gegenstück zu getAllPosts() nennen wir diese getPostBySlug(slug) . Anstelle aller Posts wird ein einzelner Post zurückgegeben, der mit dem Slug übereinstimmt, den wir übergeben. Auf der Seite eines Beitrags müssen wir auch den Markdown-Inhalt der zugrunde liegenden Datei anzeigen.

Die Seite für einzelne Beiträge sieht aus wie die für die Beitragsübersicht. Anstatt posts an die Seite in getStaticProps() zu übergeben, übergeben wir nur einen einzigen post . Lassen Sie uns zuerst die allgemeine Einrichtung vornehmen, bevor wir uns ansehen, wie der Markdown-Text des Beitrags in verwendbares HTML umgewandelt wird. Wir werden den Platzhalter-Post hier überspringen und die Hilfsfunktion verwenden, die wir im nächsten Schritt sofort hinzufügen werden.

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

Wir müssen nun die Funktion getPostBySlug(slug) zu unserer lib/api.js . Es ist wie getAllPosts() , mit einigen bemerkenswerten Unterschieden. Da wir den Dateinamen des Beitrags aus dem Slug erhalten können, müssen wir nicht zuerst das gesamte Verzeichnis lesen. Wenn der Slug 'hello-world' ist, lesen wir eine Datei namens _posts/hello-world.md . Wenn diese Datei nicht vorhanden ist, zeigt Next.js eine 404-Fehlerseite an.

Ein weiterer Unterschied zu getAllPosts() besteht darin, dass wir dieses Mal auch den Markdown-Inhalt des Beitrags lesen müssen. Wir können es als renderfähiges HTML anstelle von rohem Markdown zurückgeben, indem wir es zuerst mit remark verarbeiten.

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

Theoretisch könnten wir die Funktion getAllPosts() in getPostBySlug(slug) . Wir würden zuerst alle Posts damit bekommen, die wir dann nach einem suchen könnten, der zu dem angegebenen Slug passt. Das würde bedeuten, dass wir immer alle Posts lesen müssten, bevor wir einen einzigen bekommen könnten, was unnötige Arbeit ist. getAllPosts() gibt auch nicht den Markdown-Inhalt der Posts zurück. Wir könnten es aktualisieren, um das zu tun, in diesem Fall würde es mehr Arbeit leisten, als es derzeit benötigt.

Da die beiden Hilfsfunktionen unterschiedliche Dinge tun, werden wir sie getrennt halten. Auf diese Weise können wir die Funktionen genau und nur auf die Arbeit konzentrieren, für die wir sie benötigen.

Seiten, die dynamisches Routing verwenden, können ein getStaticPaths() neben ihrem getStaticProps() . Diese Funktion teilt Next.js mit, für welche Werte der dynamischen Pfadsegmente Seiten erstellt werden sollen. Wir können diese bereitstellen, indem getAllPosts() verwenden und eine Liste von Objekten zurückgeben, die den slug jedes Beitrags definieren.

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

Da wir den Markdown-Inhalt in getPostBySlug(slug) parsen, können wir ihn jetzt auf der Seite rendern. Für diesen Schritt müssen wir dangerouslySetInnerHTML verwenden, damit Next.js den HTML-Code hinter post.body rendern kann. Trotz ihres Namens ist die Verwendung der Eigenschaft in diesem Szenario sicher. Da wir die volle Kontrolle über unsere Beiträge haben, ist es unwahrscheinlich, dass sie unsichere Skripte einschleusen.

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

Wenn wir einem der Links aus der Beitragsübersicht folgen, gelangen wir nun auf die eigene Seite des Beitrags.

Die Seite eines einzelnen Beitrags.
Wir können den Inhalt des Beitrags zeigen, wissen aber noch nicht, wer ihn geschrieben hat. (Große Vorschau)

Autoren hinzufügen

Jetzt, da wir Posts verdrahtet haben, müssen wir die gleichen Schritte für unsere Autoren wiederholen. Dieses Mal verwenden wir JSON anstelle von Markdown, um sie zu beschreiben. Wann immer es sinnvoll ist, können wir auf diese Weise verschiedene Dateitypen im selben Projekt mischen. Die Hilfsfunktionen, die wir zum Lesen der Dateien verwenden, kümmern sich für uns um etwaige Unterschiede. Seiten können diese Funktionen nutzen, ohne zu wissen, in welchem ​​Format wir unsere Inhalte speichern.

Erstellen Sie zunächst ein Verzeichnis namens _authors/ und fügen Sie einige Autorendateien hinzu. Benennen Sie die Dateien wie bei den Posts nach dem Slug jedes Autors. Wir werden das verwenden, um später nach Autoren zu suchen. In jeder Datei geben wir den vollständigen Namen eines Autors in einem JSON-Objekt an.

 { "name": "Adrian Webber" }

Fürs Erste reicht es aus, zwei Autoren in unserem Projekt zu haben.

Um ihnen mehr Persönlichkeit zu verleihen, fügen wir auch ein Profilbild für jeden Autor hinzu. Wir legen diese statischen Dateien im Verzeichnis public/ ab. Indem wir die Dateien nach demselben Slug benennen, können wir sie allein mit der impliziten Konvention verbinden. Wir könnten den Pfad des Bildes zur JSON-Datei jedes Autors hinzufügen, um die beiden zu verknüpfen. Indem wir alle Dateien nach den Slugs benennen, können wir diese Verbindung verwalten, ohne sie ausschreiben zu müssen. Die JSON-Objekte müssen nur Informationen enthalten, die wir nicht mit Code erstellen können.

Wenn Sie fertig sind, sollte Ihr Projektverzeichnis in etwa so aussehen.

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

Genau wie bei den Beiträgen brauchen wir jetzt Hilfsfunktionen, um alle Autoren zu lesen und einzelne Autoren zu erhalten. Die neuen Funktionen getAllAuthors() und getAuthorBySlug(slug) gehen auch in lib/api.js . Sie machen fast genau dasselbe wie ihre Post-Pendants. Da wir JSON verwenden, um Autoren zu beschreiben, müssen wir hier keinen Markdown mit remark parsen. Wir brauchen auch keine gray-matter , um Frontmatter zu analysieren. Stattdessen können wir das in JavaScript integrierte JSON.parse() , um den Textinhalt unserer Dateien in Objekte einzulesen.

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

Mit diesem Wissen sehen unsere Hilfsfunktionen so aus:

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

Mit einer Möglichkeit, Autoren in unsere Anwendung einzulesen, können wir jetzt eine Seite hinzufügen, die sie alle auflistet. Durch das Erstellen einer neuen Seite unter pages/authors/index.js wir eine /authors -Seite auf unserer Website.

Die Hilfsfunktionen übernehmen das Einlesen der Dateien für uns. Diese Seitenkomponente muss nicht wissen, dass Autoren JSON-Dateien im Dateisystem sind. Es kann getAllAuthors() verwenden, ohne zu wissen, wo oder wie es seine Daten erhält. Das Format spielt keine Rolle, solange unsere Hilfsfunktionen ihre Daten in einem Format zurückgeben, mit dem wir arbeiten können. Abstraktionen wie diese ermöglichen es uns, verschiedene Arten von Inhalten in unserer Anwendung zu mischen.

Die Indexseite für Autoren sieht derjenigen für Beiträge sehr ähnlich. Wir erhalten alle Autoren in getStaticProps() , das sie an die Authors -Komponente übergibt. Diese Komponente bildet jeden Autor ab und listet einige Informationen über ihn auf. Wir müssen keine anderen Links oder URLs aus dem Slug erstellen. Die Hilfsfunktion gibt die Autoren bereits in einem brauchbaren Format zurück.

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

Wenn wir /authors auf unserer Seite besuchen, sehen wir eine Liste aller Autoren mit ihren Namen und Bildern.

Die Liste der Autoren.
Wir können alle Autoren auflisten, wissen aber nicht, wie viele Artikel sie beigetragen haben. (Große Vorschau)

Die Links zu den Profilen der Autoren führen noch nirgendwo hin. Um die Profilseiten hinzuzufügen, erstellen wir eine Datei unter pages/authors/[slug].js . Da Autoren keinen Textinhalt haben, können wir vorerst nur ihre Namen und Profilbilder hinzufügen. Wir brauchen auch ein weiteres getStaticPaths() , um Next.js mitzuteilen, für welche Slugs Seiten erstellt werden sollen.

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

Damit haben wir jetzt eine grundlegende Autorenprofilseite, die sehr wenig Informationen enthält.

Die Profilseite eines Autors, die seinen Namen und sein Porträt zeigt.
Die Profilseite eines Autors ist derzeit größtenteils leer. (Große Vorschau)

Zu diesem Zeitpunkt sind Autoren und Beiträge noch nicht verbunden. Als Nächstes bauen wir diese Brücke, damit wir eine Liste der Beiträge jedes Autors zu seinen Profilseiten hinzufügen können.

Beiträge und Autoren verbinden

Um zwei Inhalte zu verbinden, müssen wir aufeinander verweisen. Da wir Beiträge und Autoren bereits anhand ihrer Slugs identifizieren, werden wir sie damit referenzieren. Wir könnten Autoren zu Beiträgen und Beiträge zu Autoren hinzufügen, aber eine Richtung reicht aus, um sie zu verlinken. Da wir Beiträge Autoren zuordnen möchten, fügen wir den Slug des Autors zur Titelzeile jedes Beitrags hinzu.

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

Wenn wir es dabei belassen, fügt das Ausführen des Beitrags durch gray-matter das Autorenfeld als Zeichenfolge zum Beitrag hinzu:

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

Um das Objekt zu erhalten, das den Autor darstellt, können wir diesen Slug verwenden und getAuthorBySlug(slug) aufrufen.

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

Um den Autor zur Seite eines einzelnen Beitrags hinzuzufügen, müssen wir getAuthorBySlug(slug) einmal 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), + }, }, } }

Beachten Sie, wie wir ...post post getStaticProps() in ein Objekt verteilen, das auch post genannt wird. Indem wir author nach dieser Zeile platzieren, ersetzen wir am Ende die String-Version von author durch sein vollständiges Objekt. Dadurch können wir über post.author.name in der Post -Komponente auf die Eigenschaften eines Autors zugreifen.

Mit dieser Änderung erhalten wir jetzt auf der Seite eines Beitrags einen Link zur Profilseite des Autors, komplett mit seinem Namen und Bild.

Die fertige Beitragsseite, die jetzt den Namen und das Porträt des Autors enthält.
Der Beitrag wird nun korrekt dem Autor zugeordnet. (Große Vorschau)

Das Hinzufügen von Autoren zur Beitragsübersichtsseite erfordert eine ähnliche Änderung. Anstatt getAuthorBySlug(slug) einmal aufzurufen, müssen wir alle Posts abbilden und für jeden von ihnen aufrufen.

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

Das fügt die Autoren zu jedem Beitrag in der Beitragsübersicht hinzu:

Eine Liste von Blogbeiträgen, einschließlich der Namen und Portraits ihrer Autoren.
Das sieht jetzt aus wie ein richtiger Blog. (Große Vorschau)

Wir müssen keine Liste der Beiträge eines Autors zu seiner JSON-Datei hinzufügen. Auf ihren Profilseiten holen wir uns zunächst alle Posts mit getAllPosts() . Wir können dann die vollständige Liste nach denen filtern, die diesem Autor zugeordnet sind.

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

Dadurch erhalten wir eine Liste von Artikeln auf der Profilseite jedes Autors.

Die Profilseite eines Autors, die jetzt eine Liste mit Links zu seinen Beiträgen enthält.
Wir können jetzt die Beiträge jedes Autors auflisten und verlinken. (Große Vorschau)

Auf der Autorenübersichtsseite fügen wir nur hinzu, wie viele Beiträge sie geschrieben haben, um die Benutzeroberfläche nicht zu überladen.

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

Damit zeigt die Autoren-Übersichtsseite, wie viele Beiträge jeder Autor beigetragen hat.

Die Liste der Autoren mit ihrer Anzahl von Beiträgen.
Wir können jetzt die Anzahl der beigetragenen Posts mit dem Eintrag jedes Autors angeben. (Große Vorschau)

Und das ist es! Beiträge und Autoren sind nun vollständig verlinkt. Wir können von einem Beitrag zur Profilseite eines Autors und von dort zu seinen anderen Beiträgen gelangen.

Zusammenfassung und Ausblick

In diesem Artikel haben wir zwei verwandte Arten von Inhalten durch ihre einzigartigen Slugs verbunden. Die Definition der Beziehung vom Beitrag zum Autor ermöglichte eine Vielzahl von Szenarien. Wir können jetzt den Autor in jedem Beitrag anzeigen und seine Beiträge auf seinen Profilseiten auflisten.

Mit dieser Technik können wir viele andere Arten von Beziehungen hinzufügen. Jeder Beitrag kann über einem Autor einen Rezensenten haben. Wir können dies einrichten, indem wir der Titelseite eines Beitrags ein reviewer -Feld hinzufügen.

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

Auf dem Dateisystem ist der Reviewer ein anderer Autor aus dem _authors/ . Wir können auch getAuthorBySlug(slug) verwenden, um ihre Informationen zu erhalten.

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

Wir könnten sogar Co-Autoren unterstützen, indem wir statt nur einer Person zwei oder mehr Autoren auf einem Beitrag nennen.

 --- 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 diesem Szenario konnten wir in getStaticProps() eines Beitrags nicht mehr nach einem einzelnen Autor suchen. Stattdessen würden wir diese Reihe von Autoren abbilden, um sie alle zu erhalten.

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

Wir können mit dieser Technik auch andere Arten von Szenarien erstellen. Es ermöglicht jede Art von Eins-zu-Eins-, Eins-zu-Viele- oder sogar Viele-zu-Viele-Beziehungen. Wenn Ihr Projekt auch Newsletter und Fallstudien umfasst, können Sie auch jedem von ihnen Autoren hinzufügen.

Auf einer Seite rund um das Marvel-Universum könnten wir Charaktere und die Filme, in denen sie vorkommen, miteinander verbinden. Im Sport könnten wir Spieler und die Teams, für die sie derzeit spielen, verbinden.

Da Hilfsfunktionen die Datenquelle verbergen, können Inhalte aus unterschiedlichen Systemen stammen. Wir könnten Artikel aus dem Dateisystem und Kommentare aus einer API lesen und sie in unseren Code einfügen. Wenn sich ein Inhalt auf eine andere Art von Inhalt bezieht, können wir ihn mit diesem Muster verbinden.

Weitere Ressourcen

Next.js bietet mehr Hintergrundinformationen zu den Funktionen, die wir auf ihrer Seite zum Abrufen von Daten verwendet haben. Es enthält Links zu Beispielprojekten, die Daten aus verschiedenen Arten von Quellen abrufen.

Wenn Sie dieses Starterprojekt weiterführen möchten, lesen Sie diese Artikel:

  • Erstellen eines CSS-Tricks-Website-Klons mit Strapi und Next.js
    Ersetzen Sie die Dateien auf dem lokalen Dateisystem durch ein Backend mit Strapi-Unterstützung.
  • Vergleich von Styling-Methoden in Next.js
    Erkunden Sie verschiedene Möglichkeiten, benutzerdefiniertes CSS zu schreiben, um das Styling dieses Starters zu ändern.
  • Markdown/MDX mit Next.js
    Fügen Sie Ihrem Projekt MDX hinzu, damit Sie JSX- und React-Komponenten in Ihrem Markdown verwenden können.