Create a multi-author blog with Next.js

Create a multi-author blog with Next.js

In this article, we will use Next.js to build a blog that supports two or more authors. We will attribute each post to an author and display their name and photo along with the post. Each author will also have a profile page listing all the articles they have contributed. It will look like this.

We will save all the information in a file on the local file system. Two types of content, posts and authors, will use different types of files. Posts with a lot of text will use Markdown, which makes the editing process easier. Because the information about the author is relatively light, we will put it in a JSON file. Helper functions will make it easier to read different file types and combine their contents.

Next.js allows us to effortlessly read from different sources and different types of data. Due to its dynamic routing and

next/link
, We can quickly build and navigate to the various pages of our website. We can also pass
next/image
Pack free to get image optimization.

By choosing Next.js which "includes battery", we can focus on our application itself. We don't need to spend any time on the repetitive basic work that often occurs in new projects. We can rely on tested and proven frameworks instead of building everything by hand. The large and active community behind Next.js makes it easy for us to get help when we encounter problems during use.

After reading this article, you will be able to add many kinds of content to a Next.js project. You will also be able to build relationships between them. This allows you to connect things like authors and posts, courses and courses, or actors and movies.

This article assumes that you have a basic familiarity with Next.js. If you haven't used it before, you might want to read about how it processes pages and fetches data for them .

In this article, we will not cover styling, but focus on making it all work. You can get the results on GitHub . If you want to keep up with this article, there is also a style sheet that can be put into your project. To get the same framework, including navigation, use this file to replace your

pages/_app.js
.

Set up

1. we use the following method to create a new project

Create a new project and switch to its directory.

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

We will need to read the Markdown file later. To make it easier, we also added some dependencies before starting.

multiauthor-blog $ yarn add gray- matter remark remark-html copy the code

Once the installation is complete, we can run

dev
Script to start our project.

multiauthor-blog $ yarn dev copy the code

We can now explore our website. In your browser, open http://localhost:3000 . You should see the default page added by create-next-app.

Later, we will need a navigation to reach our page. We can be before the page exists

pages/_app.js
Add them.

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

In this article, we will add the missing pages that these navigation points to. Let's first add some posts so that we have something to use on the blog overview page.

Create a post

In order to separate our content from the code, we will put our post in a place called

_posts/
In the directory. To make writing and editing easier, we will create each post as a Markdown file. The file name of each post will be used as the slug in our future routing. For example, the file
_posts/hello-world.md
Will be available in
/posts/hello-world
Next visit.

Some information, such as the full title and short excerpts, will be placed in the preface at the beginning of the document.

--- 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, Copy code

Add a few more such files so that the blog will not be empty at the beginning.

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/ Copy code

You can add your own files or grab these sample articles from the GitHub repository .

List all posts

Now that we have some posts, we need a way to put them on our blog. Let's start by adding a page that lists all posts as an index to our blog.

In Next.js, in

pages/posts/index.js
A file created under will be used on our website as
/posts
. The file must export a function as the main body of the page. The first version of it looked like this.

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

We haven't gotten far because we still don't have a way to read Markdown files. We can already navigate to http://localhost:3000/posts , but we only see the title.

We now need a way to make our posts appear there. Next.js uses a file called

getStaticProps()
The function to pass data to a page component. This function will return the
props
Passed to the component as a prop.

From

getStaticProps()
, We will call the post as a
posts
The props are passed to the component. In this first step, we will hard code two placeholder posts. Starting in this way, we defined the format of the real post to be received in the future. If a helper function returns in this format, we can switch to it without changing the component.

The post overview does not show the full text of the post. For this page, the title, abstract, permalink and date of each post are sufficient.

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

In order to check the connection, we can grab the post from the props and check the

Posts
They are displayed in the component. We will include the title, creation date, excerpts and a link to a post. Now, this link will not point anywhere.

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

After reloading the page in the browser, these two posts are now displayed.

We don t want to hardcode all blog posts forever in

getStaticProps()
. After all, this is what we were
_posts/
The reason for creating all these files in the directory. We now need a way to read these files and pass their contents to the page components.

There are several ways to do this. We can

getStaticProps()
Directly read these files. Because this function runs on the server, not the client, we can access local Node.js modules in it, such as
fs
. We can read, convert, and even manipulate local files in the same file that retains the page components.

In order to keep the file short and focus on one task, we will move these functions into a separate file. Thus,

Posts
The component only needs to display the data, and does not need to read the data by itself. This adds some separation and organization to our project.

By convention, we will put the function of reading data in a function called

lib/api.js
In the file. This file will hold all the functions that grab content for the components that display the content.

For the post overview page, we need a function to read, process and return all posts. We call it

getAllPosts()
. In this function, we first use
path.join()
To build a path to
_posts/
The path of the directory. Then we use
fs.readdirSync()
Read this directory, which gives us the names of all the files in it. Map these names, and then we read each file in turn.

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

After reading the file, we get that its content is a long string. In order to separate the foreword from the text of the article, we pass

gray-matter
. We will also delete the
.md
To get the lug of each post. We need this lug to create a URL so that we can access this post in the future. Since we don't need the Markdown body of the post with this feature, we can ignore the rest.

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

Notice how we

...data
To the returned object. This allows us to get the value from its positive content in the future, namely
{post.title}
Instead of
{post.data.title}
.

Back to our post overview page, we can now replace placeholder posts with this new function.

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

After reloading the browser, we can now see the actual post instead of the previous placeholder.

Add a single post page

The link we added to each post does not point to any place. It's not like yet

/posts/hello-world
Pages that respond to URLs like that. With dynamic routing , we can add a page that matches all paths, like this.

One created as

pages/posts/[slug].js
The file will match all that looks like
/posts/abc
URL. Instead of appearing in the URL
[slug]
The value of will be provided to the page as a query parameter. We can go to the corresponding page
getStaticProps()
As
params.slug
, Call a helper function.

As

getAllPosts()
Corresponding function, we will call the auxiliary function
getPostBySlug(slug)
. It will return a post that matches the lug we passed to it, not all posts. On the page of a post, we also need to display the Markdown content of the base file.

The single post page looks like the post overview page. in

getStaticProps()
Instead of putting
posts
, We only pass one
post
. Before we study how to convert the Markdown content of a post into usable HTML, let's do the general setup first. We will skip the placeholder post here and use the helper function that we will add immediately in the next step.

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

We are now going to put the function

getPostBySlug(slug)
Add to our auxiliary file
lib/api.js
. It and
getAllPosts()
, There are some obvious differences. Because we can get the file name of the post from the slug, we don't need to read the entire directory first. If the slug is
'hello-world'
, We will read a file called
_posts/hello-world.md
document. If the file does not exist, Next.js will display a 404 error page.

versus

getAllPosts()
Another difference is that this time we also need to read the Markdown content of the post. We can use
remark
To process it and return it as renderable HTML instead of raw Markdown.

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

In theory, we can

getPostBySlug(slug)
Functions used in
getAllPosts()
. We will first get all the posts with it, and then we can search for posts that match the given lug. This will mean that we always need to read all the posts before getting a post, which is unnecessary work.
getAllPosts()
Also did not return the Markdown content of the post. We can update it to do this, in which case it will do more work than it is currently needed.

Because these two auxiliary functions do different things, we have to separate them. In this way, we can focus these functions on the work we need each of them to do, and they are the only work.

Pages that use dynamic routing can be

getStaticProps()
Provide a
getStaticPaths()
. This function tells Next.js to create a page for which dynamic path segment values. We can use
getAllPosts()
, And return a definition of each post
slug
A list of objects to provide these.

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

Because we are

getPostBySlug(slug)
The content of Markdown has been parsed in, and we can now render it on the page. We need to use in this step
dangerouslySetInnerHTML
, So that Next.js can be in
post.body
The HTML is rendered later. Despite its name, it is safe to use this attribute in this case. Because we have complete control over our posts, they are unlikely to inject insecure scripts.

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

If we find a link from the post overview, we can now enter the post's own page.

Add author

Now that we have the post, we need to repeat the same steps for our author. This time, we will use JSON instead of Markdown to describe them. As long as it makes sense, we can mix different types of files in the same project. The helper function we use to read the file handles any discrepancies for us. The page can use these functions without knowing what format we use to store the content.

1. create a file called

_authors/
And add some author files in it. Just like we did for the post, name these files with the entry of each author. We will use it to find the author in the future. In each file, we specify the full name of an author in a JSON object.

{ "name": "Adrian Webber" } Copy code

Now, it is enough to have two authors in our project.

In order to make them more individual, we also add a profile picture for each author. We will put these static files in

public/
Directory. By naming these files with the same slug, we can connect them using only implicit conventions. We can add the path of the picture in the JSON file of each author to connect the two. By naming all files with slugs, we can manage this connection without writing it out. JSON objects only need to store information that we cannot build with code.

When you are done, your project directory should look like this.

multi-author-blog/ _authors/ adrian-webber.json megan-carter.json _posts/ pages/ public/ adrian-webber.jpg megan-carter.jpg Copy code

As with posts, we now need helper functions to read all authors and get individual authors. New function

getAllAuthors()
with
getAuthorBySlug(slug)
Also put
lib/api.js
in. Their role is almost exactly the same as the corresponding function of the post. Because we use JSON to describe the author, we don t need to use
remark
To parse any Markdown. We don't need
gray-matter
To parse frontmatter. Instead, we can use JavaScript s built-in
JSON.parse()
Let's read the text content of our file into objects.

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

With this knowledge, our auxiliary function looks like this.

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

With a way to read authors in our application, we can now add a page that lists all authors. in

pages/authors/index.js
Create a new page, our website will have
/authors
page.

The helper function handles the problem of reading files for us. This page component does not need to know that the author is a JSON file in the file system. It can use

getAllAuthors()
Without needing to know where or how to get the data. As long as our helper functions return data in a format that we can use, the format is not important. Abstractions like this allow us to mix different types of content in our applications.

The author's index page looks a lot like the index page of the post. we are at

getStaticProps()
Get all the authors in and pass them to
Authors
Components. This component maps each author and lists some of their information. We do not need to establish any other links or URLs from the slug. The help function already returns the author in a usable format.

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

If we visit on our website

/authors
, We will see a list of all authors, including their names and pictures.

The link to the author's profile does not point to any place yet. In order to increase the profile page, we are

pages/authors/[slug].js
Create a file under. Because the author does not have any text content, all we can add now is their name and profile picture. We need another one
getStaticPaths()
, Tell Next.js which slugs to create a page for.

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

With these, we now have a basic author profile page with very little information.

At this point, the author and the post have not yet been connected. In the next step we will build this bridge so that we can add each author's list of posts to their profile page.

Link posts and authors

To connect two pieces of content, we need to reference one in the other piece of content. Since we have identified the post and the author by the slug, we will use it to cite them. We can add authors to posts and posts to authors, but one direction is enough to connect them. Since we want to attribute the post to the author, we have to add the author's slug to the preface of each post.

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

If we keep this state, through

gray-matter
To add the author field as a string to the post.

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

In order to obtain an object representing the author, we can use the lug and call

getAuthorBySlug(slug)
.

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

To add the author to the page of a single post, we need to

getStaticProps()
Call once in
getAuthorBySlug(slug)
.

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

Please note how we

...post
Spread to
getStaticProps()
One of them is also called
post
Of the objects. By putting
author
After this line, we finally replace the author's string version with its complete object. This allows us to pass
Post
In the component
post.author.name
To access the attributes of an author.

With this change, we can now get a link to the author's profile page on the post page, which includes their name and photo.

Adding authors to the article overview page requires a similar change. We need to map all posts and call for each post

getAuthorBySlug(slug)
Instead of calling it once.

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

This allows you to add authors for each post in the post overview.

We don't need to add the author's post list in the author's JSON file. On their profile page, we first get all the

getAllPosts()
s post. Then we can filter out articles belonging to that author in the complete list.

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

This way we get a list of articles on each author's profile page.

On the author overview page, we only add how many articles they have written to avoid confusion.

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

With this, the author overview page will show how many articles each author has contributed.

That's it! Now, the post and the author are completely connected. We can go from one post to an author s profile page, and from there to their other posts.

Summary and outlook

In this article, we are connected by two unique slugs of related types of content. Defines the relationship from the post to the author, and realizes the application of various scenarios. We can now display the author on each post and list their posts on their profile page.

With this technique, we can add many other types of relationships. Each post may have a commenter above the author. We can add a

reviewer
Field to set this.

--- 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, Copy code

In the file system, the reviewer is

_authors/
Another author in the directory. We can also use
getAuthorBySlug(slug)
To get their information.

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

We can even support co-authors, naming two or more authors on a post instead of just naming one person.

--- 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, Copy code

In this case, we can no longer post

getStaticProps()
Find a single author in. Instead, we will map this author array to get all of them.

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

We can also use this technique to generate other types of scenes. It can realize any type of one-to-one, one-to-many, and even many-to-many relationships. If your project has newsletters and case studies, you can also add them to each author.

On a website about the Marvel Universe, we can associate characters with the movies in which they appear. In sports, we can connect players with the teams they currently play for.

Since the auxiliary function hides the data source, the content can come from different systems. We can read articles from the file system, read comments from the API, and merge them into our code. If some content is related to another type of content, we can use this mode to connect them.

More resources

Next.js provides more background on the functions we use in its page on data acquisition . It includes links to sample projects that obtain data from different types of sources.

If you want to learn more about this startup project, please check these articles.