DEV Community

Cover image for Building a simple blog site with Next.js + Strapi API
Carlo Miguel Dy
Carlo Miguel Dy

Posted on

Building a simple blog site with Next.js + Strapi API

Introduction

We'll learn how we can build a blog site with Next.js and Strapi as our Headless CMS. With these two technologies combined, you can already have a blog up and ready as quickly as you can. So if you have opened this article so I assume you understand or familiar with the basics of React / Next js. With that all said, let's get started.

File Structure

This is going to be a monorepo so we can easily navigate through our backend (Strapi) and the frontend (Next.js).

Let's just have this simple file structure

- backend
- frontend
README.md
Enter fullscreen mode Exit fullscreen mode

Installation

Open your terminal and create a directory

$ mkdir nextjs-strapi-blog
Enter fullscreen mode Exit fullscreen mode

Then navigate into that directory and install Strapi and Next.js. For now let's put the --quickstart flag, this basically just selects the database which will be SQLite and other default configurations just to setup our Strapi backend quick.

And of course, we can use any other SQL databases with Strapi.

$ npx create-strapi-app backend --quickstart
Enter fullscreen mode Exit fullscreen mode

It will then take awhile for the Strapi installation so wait up for about 5 minutes maximum or less. Once that is done it will launch a page and asks you to create an admin account.

image

Just create a simple account that is easy to remember, for example:

First Name: Admin
Last Name: Admin
Email: admin@admin.com
Password: Password123!
Enter fullscreen mode Exit fullscreen mode

Once that is done, the Strapi Admin dashboard should be opened by now.

image

Then up next will be to create our Next.js app

$ npx create-next-app frontend
Enter fullscreen mode Exit fullscreen mode

After installing Next.js let's add TypeScript for our Next.js

$ touch tsconfig.json
Enter fullscreen mode Exit fullscreen mode

Then run the app and it should throw us an error in the CLI and will ask us to install the following

# If you’re using npm
$ npm install --save-dev typescript @types/react @types/node

# If you’re using Yarn
$ yarn add --dev typescript @types/react @types/node
Enter fullscreen mode Exit fullscreen mode

Once that is done we can run our Next.js server again and it should be ready. Then all of our files will end with .tsx so we can use TypeScript in writing code and it will be much easier for us to write code for the application.

Creating a Post Collection

For a single post in our blog application, we'll have the following fields like title, and content. So that's all we have for now we'd like to keep it simple since this is just a simple blog application.

For our TypeScript datamodel, we'll have something like

export interface Post {
  id: number;
  title: string;
  content: string;
  created_at: any;
  updated_at: any;
  published_at: any;
}
Enter fullscreen mode Exit fullscreen mode

The other fields like id, created_at and published_at are being generated by Strapi.

So let's proceed to creating a Collection Type in Strapi. Now on the side menu / sidebar, hover over the "Content-Types Builder" and click it and it should navigate us to this page.

image

Once you are already in that page, then click on "Create new collection type"

image

A modal should then open with a field labelled as "Display Name", then just put "Post",

image

We want it to be in a form of a singular word than plural because Strapi will then read this as plural word when generating API endpoints. So basically if we have a Collection named as "Post" then our RESTful API endpoints that are generated will have /posts, and /posts/:id.

Click "Continue" to proceed.

While we only have two fields for this Collection, we simply want "Text" for our title field and "Rich Text" for the content.

image

Once that is done, click on "Save"

image

And after that, we already have a REST API that was generated by Strapi itself! We will also have the following CRUD feature up and ready, so let's visit the page under Strapi dashboard.

image

Then we can create a few posts then we'll test our API.

Creating Posts

Click the "Create" button on the top right portion and you should then navigate into this page with the form.

image

Click "Save" when done, then wait a bit and finally click "Publish" so we can see this getting returned from the REST API when we are requesting the data.

Allow read access to Public

Before anything else, we will have to allow reads. To do that, navigate into "Settings" page and click on the "Roles" tab under "Users & Permissions Plugin" section. Then on the table click on the row "Public" then we can allow reads publicly.

image

Once that is done be sure to click "Save", and we can proceed to testing our API manually in the browser or you can do it using Insomnia. Whichever you prefer.

Testing

Just to make it quick and easy because it's just basically the same thing. Open this in a new tab http://localhost:1337/posts and it should return an array of objects.

image

Frontend

We can setup our frontend and make it read the posts that is created from Strapi. But before that I'll want to use axios for HTTP calls.

So to install on a new fresh terminal and make sure you are under frontend directory

$ cd frontend
Enter fullscreen mode Exit fullscreen mode

Then install the package

$ npm install axios
Enter fullscreen mode Exit fullscreen mode

For the look, let's use Chakra UI. To install it,

$ npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4
Enter fullscreen mode Exit fullscreen mode

Then make the following changes of your Next application if you haven't already.

Change _app.js to _app.tsx then add the AppProps type on the first destructured parameter.

Then the index.js page to index.tsx.

The moving back under _app.tsx file, wrap the <Component {...pageProps} /> around the component ChakraProvider

It should then look like this when done correctly.

import { ChakraProvider } from "@chakra-ui/react";
import { AppProps } from "next/dist/next-server/lib/router/router";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

Then the index.tsx file, remove everything from there and replace the following code:

import { GetServerSideProps, GetStaticProps } from "next";
import axios from "axios";
import { Box, Heading } from "@chakra-ui/layout";

interface Post {
  id: number;
  title: string;
  content: string;
  created_at: any;
  updated_at: any;
  published_at: any;
}

interface PostJsonResponse {
  data: Post[];
}

export const getStaticProps: GetStaticProps = async () => {
  const response = await axios.get("http://localhost:1337/posts", {
    headers: {
      Accept: "application/json",
    },
  });
  const data: Post[] = response.data;

  return {
    props: {
      data,
    },
  };
};

const IndexView = ({ data }: PostJsonResponse) => {
  return (
    <>
      <Box height="100vh" padding="10">
        <Heading>My Blog</Heading>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </Box>
    </>
  );
};

export default IndexView;
Enter fullscreen mode Exit fullscreen mode

To break it down for you. Under index.tsx file, that is our main view and the route path is /, basically this is the first page.

We created an interface of Post from the one I mentioned above and a PostJsonResponse as we'll provide that type to the first parameter of our React component which is the props.

We also used getStaticProps for fetching data from our Strapi backend. While this is just a simple blog application and there's not many posts to create we'll use getStaticProps as it will pre generate these data during build time as JSON files. Basically making reads blazing fast.

And on the template, we used the Box component from Chakra UI just for the layout and providing us padding and a height of 100vh.

Then just to see the JSON data we called it in the template <pre>{JSON.stringify(data, null, 2)}</pre> and the pre tag just to make it look "pretty" and easier to read the JSON format.

So that's about it. So this is how it looks as of the moment.

image

Creating a PostCard component

Just to make things look better, let's create a PostCard component that will have an onClick prop so whenever we click on the card it will redirect us to a Post detail view to read more of the contents from each of our posts that we created from Strapi.

To do that, create a directory under frontend directory and name it as components then create the file called PostCard.tsx.

Then the code would be as follows

import { Button } from "@chakra-ui/button";
import { Box, Heading, Text } from "@chakra-ui/layout";

export type PostCardProps = {
  title: string;
  publishedAt: string;
  onClick: VoidFunction;
};

const PostCard = ({ title, publishedAt, onClick }: PostCardProps) => {
  return (
    <>
      <Box
        padding="30px"
        width="500px"
        shadow="lg"
        borderRadius="md"
        marginBottom="30px"
        onClick={onClick}
      >
        <Box display="flex" justifyContent="space-between">
          <Text fontWeight="bold" fontSize="24px">
            {title}
          </Text>
          <Button colorScheme="facebook">Read</Button>
        </Box>
        <Text size="10px">Published at {new Date(publishedAt).toLocaleDateString()}</Text>
      </Box>
    </>
  );
};

export default PostCard;
Enter fullscreen mode Exit fullscreen mode

Use the PostCard component

Then head on over back to our index.tsx file and update that code that will be using the newly created dumb component. It is a dumb component since it doesn't handle any state, only receiving input props from a parent component.

import { GetServerSideProps, GetStaticProps } from "next";
import { Box, Center, Heading, VStack } from "@chakra-ui/layout";
import { useRouter } from "next/router";
import axios from "axios";
import PostCard from "../components/PostCard";

interface Post {
  id: number;
  title: string;
  content: string;
  created_at: any;
  updated_at: any;
  published_at: any;
}

interface PostJsonResponse {
  data: Post[];
}

export const getStaticProps: GetStaticProps = async () => {
  const response = await axios.get("http://localhost:1337/posts", {
    headers: {
      Accept: "application/json",
    },
  });
  const data: Post[] = response.data;

  return {
    props: {
      data,
    },
  };
};

const IndexView = ({ data }: PostJsonResponse) => {
  const router = useRouter();
  const toPostView = (id: number) => router.push(`/posts/${id}`);
  const posts = data.map((post) => (
    <PostCard
      key={post.id}
      title={post.title}
      publishedAt={post.published_at}
      onClick={() => toPostView(post.id)}
    />
  ));

  return (
    <>
      <Box height="100vh" padding="10">
        <Heading>My Blog</Heading>

        <Center>
          <VStack>{posts}</VStack>
        </Center>
      </Box>
    </>
  );
};

export default IndexView;
Enter fullscreen mode Exit fullscreen mode

And our application will look like this by now.

image

You may notice I have imported the useRouter() hook from next/router and I have put an on click handler on to the button "Read" and that it should navigate into the post detail view. When you click on it now, it will return you a 404 error.

So let's create that view.

Post detail view

Create a new folder under pages directory and name it as posts then create a file and name it as [id].tsx where the brackets will make this view to render with dynamic route parameters. This way we can handle different Post IDs.

Then have the following code,

import { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";
import { Post } from "../../models/Post";
import { Button } from "@chakra-ui/button";
import { Box, Divider, Heading, Text } from "@chakra-ui/layout";
import axios from "axios";

export type PostDetailViewProps = {
  data: Post;
};

export const getStaticPaths: GetStaticPaths = async () => {
  const response = await axios.get("http://localhost:1337/posts");
  const posts: Post[] = await response.data;

  const paths = posts.map((post) => {
    return {
      params: { id: String(post.id) },
    };
  });

  return {
    paths,
    fallback: false,
  };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const { data } = await axios.get(`http://localhost:1337/posts/${params.id}`);

  return {
    props: {
      data,
    },
  };
};

const PostDetailView = ({ data }: PostDetailViewProps) => {
  const router = useRouter();

  return (
    <>
      <Box padding="10">
        <Button onClick={() => router.back()}>Back</Button>
        <Heading>{data.title}</Heading>
        <Text>{data.published_at}</Text>
        <Divider marginTop="10" marginBottom="10"></Divider>
        <Text>{data.content}</Text>
      </Box>
    </>
  );
};

export default PostDetailView;
Enter fullscreen mode Exit fullscreen mode

To break it down for you. We used getStaticPaths to fetch all of the posts and map it down to shape as a path that next knows about. Since getStaticPaths and getStaticProps will be executed during build time and generates static content therefore it should make sense by now having called all posts inside a post detail view on getStaticPaths.

We then used getStaticProps and we have our first argument as the context but destructured it to only retrieve the params property which have access to the parameters of the current route. That's how we retrieve the id from the [id].tsx file name. While we have that we can make a call to a specific post.

Then on the template, we just added a "Back" button so we imported useRouter from next/router, next is we display the title field, published_at field and then the content. But for now I just didn't install a react markdown. Typically you should use react-markdown or any similar library to display the markdown contents properly.

This is how it looks by the way.

image

Summary

We learned how to build a blog using Strapi and Next.js and also understand some of the concepts Next.js has regarding getStaticProps and getStaticPaths for static site generation. By now you should be able to build out a simple blog on your own or you might a blog but has other use cases but simple CRUD functionalities are mostly required then usnig Strapi would definitely be a good pick. Otherwise if the project requires some customization, then consult Strapi's official documentation to understand/learn how you will implement it using Strapi.

If you've ever reached here at the bottom part of this article, then thank you so much for taking the time to read. Cheers and have a good day!

Full source code can be found from the repository.

Top comments (8)

Collapse
 
devbewill profile image
Stefano Perelli

Hi, ty for ur guide but i have this error:
Server Error
TypeError: data.map is not a function

const data is not an array i think, how is possible?
I copy\paste ur code..

Collapse
 
devbewill profile image
Stefano Perelli

Solved by myself, the problem was the new strapi api are different, the have data.attributes

Collapse
 
itsari profile image
itsari

how did you fixed it??

Thread Thread
 
devbewill profile image
Stefano Perelli • Edited

forum.strapi.io/t/why-am-i-getting...

I don’t remember the problem but i solves with thai post

Collapse
 
nikhilkamboj256 profile image
nikhilkamboj256 • Edited

Thanks for this article, but i have 1 doubt.
I have a blog application which have certain elements which are dynamic like reads, shares, etc. so how will i use these data to show dynamically by using getStaticProps as it makes the blog at build time only.

Please help me out here.

Collapse
 
adeleke5140 profile image
Kehinde Adeleke • Edited

I believe this should help you out
youtube.com/watch?v=Sklc_fQBmcs

You'd probably need getServerSideProps

Collapse
 
benparr64 profile image
BenParr64

Hi when I try to test the endpoints in a browser, I am getting 404 notfound error, any ideas?

Collapse
 
baxuu profile image
baxuu

api/posts