Let's build a fullstack blog using Strapi and Next.js.
This article is based on Digital Ocean's Tech Talk by Chris Sev.
To get the ball rolling, let's setup our Strapi backend by running the following command in the terminal
npx create-strapi-app blog-strapi --quickstart
This command creates a Strapi project named blog-strapi and automatically opens the admin panel in our default browser.
Let's go ahead and enter credentials and click LET'S START. We should see the Strapi dashboard as shown below
Let's create our collection. On the dashboard, click on CREATE YOUR YOUR FIRST CONTENT-TYPE. Let's have Post as display name and click Continue. Select Text as the collection type and name the field title and leave the Short text type selected. Click on Add another field and select Text collection type and name it slug. Let's add one more field by clicking Add another field, select Rich text and name it content and click finish and save. Our collection now looks as below
To create posts, go to the left panel under Collection types, click on Posts and hit the Add New Posts button. Let's add the title as My Wins This Week, slug as my-wins-this-week, content as Won the golf tournament and click save and publish. Head over to the browser at http://localhost:1337/posts. We should be getting a Forbidden error because the API is secured by default. To change that, let's get back to the Strapi dashboard and click on settings. Under USERS AND PERMISSIONS PLUGIN, click on Roles then public and select find, findone, create and click save. Let's get back to our browser and refresh http://localhost:1337/posts. We should see our post as shown below
Our post looks good so far but we have no idea who the author is. To be able to add an author, we need to add another field of type relation to the Post collection type. On the Strapi dashboard, click on Content-Type Builder, select Post, click on Add another field and select Relation. On the File dropdown select User(from-users-permiss...) and select a User has many Posts relation. Let's rename users_permissions_user field name to simply user and click finish and save.
Let's go ahead and create a user. Under collection types, select Users and click on Add New Users. Input user credentials and toggle Confirmed button to set it to ON. Under Posts(0), let's assign My Wins This Week post to this user(in my case daka) because why not? Click save and refresh the browser at http://localhost:1337/posts. You should see the user object included in our response as shown below
Go ahead and create a couple of more posts and assign them users so that we have enough data to work with on the frontend.
Let's create our Next.js frontend by running the following command in the terminal
npx create-next-app blog-next
The above command creates a new Next.js app. Once the setup is done, run the following commands
cd blog-next
npm run dev
This will start our Next.js app server and the app can be accessed at http://localhost:3000/.
Let's open up our app in a Text Editor(I'm using VS Code), go to index.js
and replace everything in the return block with <div>We are here!</div>
and save. Head over to the browser at http://localhost:3000 and we should see the output as shown below
Let's go above the Home component and get our API data using getStaticProps function as shown below.
export async function getStaticProps() {
//get posts from API
const res = await fetch('http://localhost:1337/posts');
const posts = await res.json();
return {
props: { posts }
};
}
Now, in our Home component we can receive the posts as props and loop over them as shown below
export default function Home({ posts }) {
return (
<div className={styles.container}>
{/* Loop over posts and show them */}
{posts &&
posts.map(post => (
<div key={post.id} className={styles.post}>
<h1>{post.title}</h1>
<p>{post.content}</p>
<em>By {post.user.username}</em>
</div>
))}
</div>
);
}
Let's also add a bit of styling. In the styles
directory, go to Home.module.css
and replace all that is in there with the CSS code below.
.container {
display: grid;
grid-template-columns: auto auto auto;
margin: 5%;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.post {
padding-bottom: 10%;
}
When we save this, we should see the posts as shown below(of course you could do better styling).
Now let's handle routing by creating a file called [slug].js
in the pages directory. This is a way that Next.js dynamically generates a bunch of files. There are two main things we need to do in [slug].js
:
- Tell Next.js how many pages there are and
- For each individual page; get the data for that page.
We can achieve these with the help of getStaticPaths and getStaticProps functions respectively as shown in the snippet below
//Tell Next.js how many pages there are
export async function getStaticPaths() {
const res = await fetch('http://localhost:1337/posts');
const posts = await res.json();
const paths = posts.map(post => ({
params: { slug: post.slug }
}));
return {
paths,
fallback: false
};
}
//For each individual page; get the data for that page
export async function getStaticProps({ params }) {
const { slug } = params;
const res = await fetch(`http://localhost:1337/posts?slug=${slug}`);
const data = await res.json();
const post = data[0];
return {
props: { post }
};
}
fallback
in getStaticPaths
is set to false because we have a small number of paths to pre-render - so they are all statically generated during build time. In getStaticProps
, const post
is assigned data[0]
because the response we get is an array and we are only interested in the first element.
We can now add a Post component in [slug].js
and pass the props as shown below
export default function Post({ post }) {
return <div>{post.title}</div>;
}
Now when we route to http://localhost:3000/my-wins-this-week, we get a single post as shown below:
Awesome right?
So, to be able to generate our post files, we need to add the following script to our package.json
"export": "next build && next export"
Let's stop the server and run it
npm run export
This generates HTML files from all the posts we have in Strapi.
These files are in the out
folder and are minified and ready to be hosted on a static server.
Now we will add provision to navigate between the posts page and the single post page using the Next.js Link
. Edit the return block of the Home component as shown below
<div className={styles.container}>
{/* Loop over posts and show them */}
{posts &&
posts.map(post => (
<Link href={`/${post.slug}`} key={post.id} className={styles.post}>
<a>
<h1>{post.title}</h1>
<p>{post.content}</p>
<em>By {post.user.username}</em>
</a>
</Link>
))}
</div>
and the return block for the Post component in[slug].js
as follows
<div>
<Link href="/">
<a>Home</a>
</Link>
<h1>{post.title}</h1>
</div>
Be sure to import Link
from "next/link" in both cases.
Now, when we start the server, we should be able to navigate through our site.
The last thing we want to do is creating a post. For that we need an API exploration tool, in my case I'm using Postman. Let's create a POST request with the following body:
{
"title": "The New Normal",
"slug": "the-new-normal",
"content": "The new normal is here",
"user": {
"id": 1
}
}
When we hit send, below is the post response we are getting
When we head over to our Strapi dashboard, we can see our new post added to the list of posts and when we click on it, we see the information passed as shown below.
That's it, our basic fullstack app is done.
Happy coding!
Top comments (3)
Hi Gerald,
I am currently watching Chris Talk as well and am loving it.
But I am having a hard time understanding what is happening around minute 31.
At this point, all the data being displayed to the clientside has been rendered at built time by using getStaticProps.
He then adds a new post, and this post is being displayed on the client-side - without rebuilding the app?
How does this work? Have I misunderstood what getStaticProps does?
Amazing work!
Thanks, Chris. You are my inspiration