Scaffolding the Next.js App (Part - 2)

In this series, I am rewriting the code of my blog ashutosh.dev in Next.js and Mojolicious as backend. This is the third article of the series.

To do this, we downloaded all of our articles from the dev web app (JSON file is under the data folder). You can use your file or download it from the GitHub repository under the data folder.

Create and Update next.config.js file

If not created, please create the next.config.js file and update it with the following code:

module.exports = {
    webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
        config.node = {
            fs: 'empty', // This is required
        }
        return config
    }
}

This file is responsible for setting up the configuration for Next.js. In this, we are setting up fs (file system) to empty. So, it will not create any issues while reading the file.

Fixing the Header

In the Header, we added an anchor link for the Home, Contact, and About pages. It's not the Next.js way of handling the anchor tags. Let's fix that.

Open the NavigationComponent.js file and replace

<a className="block lg:inline-block mt-4 lg:mt-0 mr-10 font-bold text-white hover:underline"
    href="/">
         Home
</a>
<a className="block lg:inline-block mt-4 lg:mt-0 mr-10 font-bold text-white hover:underline"
href="/about">
     About
</a>
<a className="block lg:inline-block mt-4 lg:mt-0 text-white font-bold hover:underline"
    href="/contact">
        Contact
</a>

with

<Link href="/">
    <a className="block lg:inline-block mt-4 lg:mt-0 mr-10 font-bold text-white hover:underline">
        Home
    </a>
</Link>
<Link href="/about">
    <a className="block lg:inline-block mt-4 lg:mt-0 mr-10 font-bold text-white hover:underline">
        About
    </a>
</Link>
<Link  href="/contact">
    <a className="block lg:inline-block mt-4 lg:mt-0 text-white font-bold hover:underline">
        Contact
    </a>
</Link>

And add the following at the top of the file.

import Link from "next/link"; 

Fixing the Categories Component

We need to replace the anchor tags in the Categories component also. Let's fix it too.

<a className="hover:underline hover:text-cyan-600" href="#"> {event} </a>

with

<Link href={'/?categories=' + event}>
    <a className="hover:underline hover:text-cyan-600" > {event} </a>
</Link>

Modifying index.js

So far, we have added the articles.json file to the root of our application. It's time to read it and pass it to the components.

The idea is to read the file and convert it to the JSON object and then pass it further to the components. Reading the file is not possible directly. We need to add getStaticProps() function to our index.js file. If you are new and not aware of the getStaticProps(), you can read it on this link.

export async function getStaticProps(context) {
    let blog = new Array
    const fileToRead = path.join(process.cwd(), '/data/articles.json')
    const data = JSON.parse(await fs.readFileSync(fileToRead))
    for (let index in data) {
        const catList = data[index].cached_tag_list
        blog.push({
            title: data[index].title,
            date: data[index].published_at,
            body: data[index].body_markdown,
            description: data[index].description,
            categories: catList
        });
    }
    // Reverse an Array
    const reverseData = blog.slice(0).reverse()
    return  {
        props: {
            sample_blogs: reverseData
        }
    }
}

We need to reverse the Array because the JSON file is not sorted with the recent blogs first.

We also need to modify the Home() function.

<div className="flex flex-auto">
    <CategoriesComponent />
        <BlogComponent Blogs={props} />
        <div></div>
 </div>

And don't forget to import

import fs from 'fs';
import path from 'path';

Now the file will look like

import Head from 'next/head'
import CategoriesComponent from "../components/Categories/CategoriesComponent";
import BlogComponent from "../components/Blog/BlogComponent";
import fs from 'fs'
import path from 'path'

function Home(props) {
  return (
    <div>
      <Head>
        <title>Ashutosh.dev - A place to learn Programming</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="flex flex-auto">
          <CategoriesComponent />
          <BlogComponent Blogs={props} />
          <div></div>
      </div>
    </div>
  )
}
export async function getStaticProps(context) {
    let blog = new Array();
    const fileToRead = path.join(process.cwd(), '/data/articles.json')
    const data = JSON.parse(await fs.readFileSync(fileToRead))
    for (let index in data) {
        const catList = data[index].cached_tag_list
        blog.push({
            title: data[index].title,
            date: data[index].published_at,
            body: data[index].body_markdown,
            description: data[index].description,
            categories: catList
        });
    }
    // Reverse an Array
    const reverseData = blog.slice(0).reverse()
    return  {
        props: {
            sample_blogs: reverseData
        }
    }
}
export default Home

Refactor BlogComponent.js and BlogList.js

From index.js, we passed props to the BlogComponent. Now, we need to use the Props in our BlogComponent.

Open the file and modify the code as:

import BlogList from "./BlogList";

function BlogComponent(props) {
    return (
        <div className="ml-4 mt-4 w-1/2 container mr-4">
            <BlogList Blogs={props.Blogs} />
        </div>
    )
}

export default BlogComponent

Open the BlogList.js file and update the following code:

import Link  from "next/link";
import IndividualBlogDetails from "./IndividualBlogDetails";
function BlogList(props) {
    const Blogs  = props.Blogs.sample_blogs
    return (
        <div className="ml-4">
            {
                Blogs.map( event =>
                    <IndividualBlogDetails details={event} />
                )
            }
        </div>
    )
}
export default BlogList

Create IndividualBlogComponent

We'll further break down the components into another component.

Create a new file IndiviudalBlogComponent.js, under the blog folder.

import Link from "next/link";

function IndividualBlogDetails(props) {
    const event = props.details
    const cat = (event.categories).split(", ");
    return (
        <div key={event.slug} className={`border-b shadow rounded-md hover:shadow-lg hover:border`}>
            <div className={`ml-4 mt-4`}>
                <Link href={`/blog/${encodeURIComponent(event.slug)}` }>
                    <a className={`hover:text-cyan-500`}>
                        <h1 className={`font-bold text-2xl`}>{event.title}</h1>
                    </a>
                </Link>
            </div>
            <p className={`ml-4 mt-4`}>{event.description}</p>
             {/*Posted Date*/}
            <div className="ml-4 mt-8 flex">
                <p>
                    <span className={`text-gray-400`}>Published: </span>
                    <small>
                        <time className={`hover:underline hover:text-cyan-500`}>{humanReadableDate(event.date)}</time>
                    </small>
                </p>
                {/*Categories Belongs to*/}
                <div className={`ml-10 mb-4`}>
                    {
                        cat.map
                        ( event =>
                            <span key={cat} className={`inline-block bg-cyan-200 text-cyan-800 text-xs px-2 rounded-full uppercase font-semibold tracking-wide mr-2`}>
                                <Link href={`/?categories=${encodeURIComponent(event)}` }>
                                    <a className={`hover:underline`}>{event}</a>
                                </Link>
                            </span>
                        )
                    }
                </div>
            </div>
        </div>
    )
}
function humanReadableDate(date) {
    return new Date(date).toLocaleDateString(
        undefined,
        {year: "numeric", month: "long", day: "numeric"}
    )
}

export default IndividualBlogDetails

The date in the JSON is the timestamp, so we have converted the data into a readable format.

Open the browser, and you will see the home page like the below illustration.

So far, we have created a home page where we see all the blog lists. But we also need to click on the blog page to go over the details of the blog. Create [slug].js file inside the blog folder under the pages directory.

function IndividualBlogPage(props) {
    return (
        <div>
            Individual blog page
        </div>
    )
}

export default IndividualBlogPage

If you click, then you'll see the blog page details. We'll update the details of the individual blog in our next article of the series.