Building a simple blog site with Next.js + Strapi API
Tags: nextjs, strapi, api, react
Published: 2021-04-12 23:54:50

# 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
```

# Installation

Open your terminal and create a directory

```bash
$ mkdir nextjs-strapi-blog
```

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.

```bash
$ npx create-strapi-app backend --quickstart
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rqmxspg5l1clhllp6431.png) 

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

```
First Name: Admin
Last Name: Admin
Email: admin@admin.com
Password: Password123!
```

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4i8nlnt1z88kc6m0wlej.png) 

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

```bash
$ npx create-next-app frontend
```

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

```bash
$ touch tsconfig.json
```

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

```bash
# 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
```

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

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

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntgovcu9mxy9ruwzai2u.png)

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a92xjfrl1y18vandzd28.png)
  
A modal should then open with a field labelled as "Display Name", then just put "Post", 

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y46nki15g6rmv8bmdg85.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0fk831wf0mzntw7nkt8c.png)

Once that is done, click on "Save"

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p52pu5e98qkwtxwu24ao.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6eoduj6tnh5jb7wwkri8.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wudpd3y4p9r6brdjfg5.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyin92vhe1tatbowco9g.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yl0a4uucrdr76439uzgi.png)
 
# 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

```bash
$ cd frontend
```

Then install the package

```bash
$ npm install axios
```

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

```bash
$ npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4
```

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.

```tsx
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;
```

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

```tsx
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;
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrjujxq5t2w7x2kbe96u.png)

# 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

```tsx
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;
``` 

# 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.

```tsx
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;
```

And our application will look like this by now.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3821t39clnz1qie9nnng.png)

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,

```tsx
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;
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6omexd7fhj4jj8mvskf4.png)

# 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](https://github.com/carlomigueldy/nextjs-strapi-simple-blog).