Basic Next.js app - blog post page [Building Personal Blog Website Part 3]
Tags: strapi, nextjs, tutorial, javascript
Published: 2022-08-29 11:35:00

You can see all your posts already on your homepage as cards. But now let’s go a step further - create a subpage for every post.

Before you create a subpage for a specific blog post - let’s just do a small adjustment in your current app. You’ll make changes in the `_app.js` file. Markup in this file is preserved between pages and works as a kind of a page layout. By default it renders just a page component. Now you’ll move the header from your homepage there. Use this code in `_app.js`:

```html
function MyApp({ Component, pageProps }) {
  return (
    <div className="flex flex-col items-center">
      <h1 className="text-3xl uppercase font-serif font-bold my-8">
        My personal blog
      </h1>
      <Component {...pageProps} />
    </div>
  );
}
```

And remove the same markup from your `index.js` file:

```html
export default function Home({ posts }) {
  return (
    <section className="grid grid-cols-3 gap-4">
      {posts.map((post) => (
        <BlogPostPreview post={post} key={post.attributes.slug} />
      ))}
    </section>
  );
}
```

Now whenever you’ll create a new subpage - it’ll always have the header *“My personal blog”*. Of course it would be best to add a navigation bar and footer here, but that’s not the main focus in this article series, so we’ll go back to this in the future, when there will be something to navigate to.

The next step would be creating a page for every blog post. Of course you’d want it to be generic so with every new post you’d get a new `/post/slug-of-blogpost` page. You can easily achieve that in *Next.js* using **dynamic routes**. Dynamic routes allow you to create a template page and then during site generation all the needed information are figured out by *Next.js*. Let’s just see how this works.

In `pages` directory create a new folder `post`. This will correspond to a new route `/post`. Inside create a file `[slug].js` - **square brackets are important** - they indicate that this is a dynamic route. In our case it will allow us to get a specific route for each blog post (for example `/post/test-post` or `/post/my-first-post`).

![Blog-tutorial-3-1.png](https://res.cloudinary.com/dvrqjaeju/image/upload/v1661580192/Blog_tutorial_3_1_ffb6c9af5b.png)

As you are creating a statically generated site you need to tell *Next.js* which routes it should generate. One way to provide this information is by creating a `getStaticPaths` function in your `[slug].js` file. This function will get slugs of all the blog posts and based on that it will inform *Next.js* which routes are needed.

```jsx
export async function getStaticPaths() {
  const { data } = await client.query({
    query: gql`
      query Posts {
        posts(
          filters: { publishedAt: { notNull: true } }
        ) {
          data {
            attributes {
              slug
            }
          }
        }
      }
    `,
  });
  return {
    paths: data.posts.data.map((item) => ({
      params: { slug: item.attributes.slug },
    })),
    fallback: false,
  };
}
```

The `filters` in the query ensure that you will get only published posts.

Now you have the list of slugs and it’s time to use it to fetch every post data. Let’s create a `getStaticProps` method in the same file.

```jsx
export async function getStaticProps({ params }) {
  const { data } = await client.query({
    query: gql`
      query Posts {
        posts(
          sort: "publishedAt:desc"
          pagination: { limit: 1 }
          filters: { slug: { eq: "${params.slug}" } }
        ) {
          data {
            attributes {
              title
              slug
              content
              cover {
                data {
                  attributes {
                    url
                  }
                }
              }
              author {
                data {
                  attributes {
                    username
                  }
                }
              }
              tags {
                data {
                  attributes {
                    tagId
                    name
                  }
                }
              }
            }
          }
        }
      }
    `,
  });

  return {
    props: {
      postData: data.posts.data[0],
    },
  };
}
```

You’ll get all the data needed to properly display the post on your page. You query the *GraphQL* for a `post` with a specific `slug`. Unfortunately *Strapi API* does not allow you to get one specific post using the slug. It’s only possible using `post ID`, but you want to have more descriptive links than for example `/post/2`. `/post/a-blog-post` looks definitely better. That’s why you’ll make a standard `Posts` query with a filter to get the one with a specific slug. And the returned data is put into `props` of `BlogPost` component which you’ll create next.

Now the time has come to create your blog post page. Still in [slug].js add a new component (place it over the getStaticPaths and getStaticProps methods).

```bash
export default function Post({ postData }) {
  return <div>{postData.attributes.content}</div>;
}
```

But wait! Your content is written in *Markdown* - so the end result would look like this:

![Blog-tutorial-3-2.png](https://res.cloudinary.com/dvrqjaeju/image/upload/v1661580194/Blog_tutorial_3_2_f82162b2cd.png)

That’s not exactly what we want! To help you display *Markdown* properly I suggest using two additional libraries - `react-markdown` and `TailwindCSS Typography`. First one help format markdown properly, second one is a plugin for *TailwindCSS* which provides basic styling for articles. Let’s install both:

```bash
yarn add react-markdown @tailwindcss/typography
```

Now update your `tailwind.config.js` so it’s aware of the newly installed plugin:

```bash
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [require("@tailwindcss/typography")],
};
```

With this out of the way you’ll be able display your blog posts properly:

```bash
export default function Post({ postData }) {
  return (
    <ReactMarkdown className="prose prose-sm md:prose-lg lg:prose-xl xl:prose-2xl prose-slate w-11/12 mx-4 my-8">
      {postData.attributes.content}
    </ReactMarkdown>
  );
}
```

You have one more thing to do before you’ll be able to see your blog posts in action. You need to hook Blog Post Page to the Blog Post Preview - so when the user clicks on the preview he/she will go to the full post. 

Use `next/link` which is better suited for routing in *Next.js* apps. Go to the `BlogPostPreview.jsx` and replace existing `<a href=”#”>` and its content with:

```bash
<Link href={`/post/${post.attributes.slug}`}>
  <a>
    <Image
      className="w-full"
      src={post.attributes.cover.data.attributes.url}
      alt=""
      width={1000}
      height={480}
      objectFit="cover"
    />
    <div className="px-6 py-4">
      <div className="font-bold text-xl mb-2">
        {post.attributes.title}
      </div>
      <p className="text-gray-700 text-base">{post.attributes.excerpt}</p>
    </div>
  </a>
</Link>
```

Now rebuild your app and then start the development mode. 

```bash
yarn build
yarn dev
```

Open `http://localhost:3000` and click on any of the previews - you should be redirected to the blog post page:

![Blog-tutorial-3-3.png](https://res.cloudinary.com/dvrqjaeju/image/upload/v1661580194/Blog_tutorial_3_3_ffff866bf1.png)

That’s better! Of course feel free to experiment with the styling of the whole page - I’m not focusing on this here as this is not a course on css and layouts.

And that’s it! In the next part of this guide you’ll finally deploy the page on _Netlify_, so stay tuned!