Basic Next.js app - blog post page [Building Personal Blog Website Part 3]

hwlkdev

Michał Hawełka

Posted on August 29, 2022

Basic Next.js app - blog post page [Building Personal Blog Website Part 3]

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:

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

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.

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,
  };
}
Enter fullscreen mode Exit fullscreen mode

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.

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],
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

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

export default function Post({ postData }) {
  return <div>{postData.attributes.content}</div>;
}
Enter fullscreen mode Exit fullscreen mode

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

Blog-tutorial-3-2.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:

yarn add react-markdown @tailwindcss/typography
Enter fullscreen mode Exit fullscreen mode

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

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

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

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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:

<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>
Enter fullscreen mode Exit fullscreen mode

Now rebuild your app and then start the development mode.

yarn build
yarn dev
Enter fullscreen mode Exit fullscreen mode

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

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!

💖 💪 🙅 🚩
hwlkdev
Michał Hawełka

Posted on August 29, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related