DEV Community 👩‍💻👨‍💻

Cover image for Setting up a basic Next.js app locally [Building Personal Blog Website Part 2]
Michał Hawełka
Michał Hawełka

Posted on • Updated on

Setting up a basic Next.js app locally [Building Personal Blog Website Part 2]

This is the second part of the "Building Personal Blog Website" series. In the series we'll setup a free CMS to hold our blog content on Railway, create a React app with Next.js' static site generation and TailwindCSS to present the articles and host it on Netlify.

All articles:
Part 1: Hosting free Strapi CMS on Railway
Part 2: Setting up a basic Next.js app locally

Now that the basic CMS is set up on Railway and Cloudinary is configured to handle the images, the next step would be... creating a Next app.

This Next app will consume content provided by the CMS and present it on a webpage. In this part of the series basic homepage with a list of blog posts will be created and set up locally.

So let's just jump into it. First thing you need to create is a new Next.js app. You can do this by this simple command:

npx create-next-app@latest

Enter fullscreen mode Exit fullscreen mode

After running it the prompt will ask for the app's name - provide one and then the project will be created.

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531654/Blog_tutorial_1_498857321b.png

Before you start working on an actual app let's just install some dependencies that will be useful later on - TailwindCSS for styling the app and Apollo Client for working with GraphQL.

First - let's go with TailwindCSS.

yarn add tailwindcss postcss autoprefixer
npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

After running the init command the file tailwind.config.js will be created. You need to make a few changes to the generated one so everything will be working correctly.

Replace an empty content array with:

content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
Enter fullscreen mode Exit fullscreen mode

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531709/Blog_tutorial_2_8ca3d2c219.png

You also need to import TailwindCSS rules to the ./styles/globals.css file:

@tailwind base;
@tailwind components;
@tailwind utilities;

Enter fullscreen mode Exit fullscreen mode

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531983/Blog_tutorial_2_3_8b60adcecb.png

And now you're done with setting up TailwindCSS. Let's move on to the Apollo Client.

yarn add @apollo/client graphql

Enter fullscreen mode Exit fullscreen mode

That's it! You're ready to create your GraphQL-driven blog page.

First you need to set up an Apollo Client, that will work as a "gateway" to your GraphQL API. Create a new file in the root of the project called apollo-client.js (you can name it however you want TBH).

import { ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
    uri: "https://[YOUR-CMS-GRAPHQL-URL]",
    cache: new InMemoryCache(),
});

export default client;

Enter fullscreen mode Exit fullscreen mode

With this in place it's time to use it! You'll start by getting a list of all blog posts to be displayed on the homepage. You'll use the Static Site Generation feature of Next.js - for this the getStaticProps method is needed.


Side note - Why SSG?

As in this tutorial series we are talking about setting up this blog for free - we are limited performance-wise. The CMS set on free Railway tier isn’t exactly a speed demon, Next.js app with SSR set on free Netlify would also be kinda slow. So, SSG seems to be the best option, even though every change on our blog would need an app rebuild. If budget is not a concern to you, then you can think about other options, but that’s not the topic of this series.


Before we go any further let's just add two more fields in your Blog post configuration. Open up a repo for your Strapi CMS and run it in development mode (yarn develop). Log in and go to the Content-Type Builder. First you'll need to create a new type named Tag.

It has an ID and a name that should be unique.

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_4_dd2a8f9c9c.png

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_5_048bf8e3c6.png

When this is done you are ready to "upgrade" your Blog post type. In the Content-Type Builder open the Post and add two new fields. First one is excerpt - the short text that will be presented on your homepage, sneak peak into the blog post. It should be of type string and preferably have some maximum length (250 characters in my case). It also should be required.

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_6_e2c852c029.png

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_7_5b3253ddae.png

Now you need to add Tags. It will be a Relation type field and the relation is Many-to-Many as one Post can have multiple Tags and every Tag can belong to multiple Posts. Do it like this:

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_8_950249e1cc.png

Now go to your Terminal and commit all of the changes that were made in your config files by Strapi. And then push your changes to rebuild the CMS hosted on Railway:

git push

Enter fullscreen mode Exit fullscreen mode

When the app on Railway is rebuilt - log in and change Public user permissions so you'll be able to query Tags.

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_9_3838f0c00a.png

Now you can fetch the data in getStaticProps in index.js file of your Next.js app. Add this at the bottom of pages/index.js

export async function getStaticProps() {
  const { data } = await client.query({
    query: gql`
      query Posts {
        posts(
          sort: "publishedAt:desc"
          pagination: { limit: 5 }
          filters: { publishedAt: { notNull: true } }
        ) {
          data {
            attributes {
              title
              slug
              cover {
                data {
                  attributes {
                    url
                  }
                }
              }
              excerpt
              tags {
                data {
                  attributes {
                    tagId
                    name
                  }
                }
              }
            }
          }
        }
      }
    `,
  });

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

Enter fullscreen mode Exit fullscreen mode

What this does? On every build (yarn build) Next will perform this query before "rendering" the page/creating the static page output. This means that you will not need any loading indicators on your webpage, because the data will already be fetched. Downside of this is of course that you will have to rebuild the app on every change of content in the CMS. But for personal blogs this is a pretty good and cheap solution.

You are downloading only the most important data that will be shown on the homepage. So let's just create a simple homepage then.

You'll start with creating a new component - a card to preview the Blog Post. Create a new file components/BlogPostPreview.jsx (if there's no component folder - create it).

import Image from "next/image";
import React from "react";

const BlogPostPreview = ({ post }) => {
  return (
    <div className="max-w-sm rounded overflow-hidden shadow-lg">
      <a href="#">
        <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>
      <div className="px-6 pt-4 pb-2">
        {post.attributes.tags.data.map((tag) => (
          <a href="#" key={tag.attributes.tagId}>
            <span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">
              {tag.attributes.name}
            </span>
          </a>
        ))}
      </div>
    </div>
  );
};

export default BlogPostPreview;

Enter fullscreen mode Exit fullscreen mode

You get one prop here - post. It has all the fetched information inside. Then we create a simple card with a cover image, title, excerpt and a list of tags. End result will look something like that:

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531986/Blog_tutorial_2_10_053405e47c.png

As you can see there are some empty a tags in the code - in the later stages of this tutorial series you'll make a use of them, don't worry.

Now it's finally the time to showcase your blog posts. In pages/index.js make use of a newly created component.

export default function Home({ posts }) {
  return (
    <div className="flex flex-col items-center">
      <h1 className="text-3xl uppercase font-serif font-bold my-8">
        My personal blog
      </h1>
      <section className="grid grid-cols-3 gap-4">
        {posts.map((post) => (
          <BlogPostPreview post={post} key={post.attributes.slug} />
        ))}
      </section>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

One more thing before you build this - if you use Cloudinary for the images (as was done in the second part of this series) then you will need to add Cloudinary domain to the next.config.js. This is needed so you'll be able to use next/image component instead of img tag.

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531984/Blog_tutorial_2_11_449746a2de.png

Wow, that was quite a ride. Let's finally build this! Run
yarn build and wait for this process to finish. If everything goes well - run yarn develop and open your site in the browser http://localhost:3000/. You should see something like this:

https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531988/Blog_tutorial_2_12_55394fe0c9.png

Of course don't forget to create a few tags and posts (you'll need to create a tag before you'll be able to use it in a post). Also as you added some new required fields - update existing posts so they have every needed information filled.

And that's it. That's the end of the second part of this tutorial series. In the next one you'll create a Blog Post Page. See you then!

Top comments (0)

Become a Moderator Can you help us make DEV a better place?

Fill out this survey and help us by becoming a tag moderator here at DEV.