Scaffolding the Next.js App

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

In this article, we cover

  • How to configure the storybook
  • Configure the TailwindCSS
  • Add SEO friendly _document.js
  • Create basic components

Configure Storybook

In our last article, we install and set up the storybook in our Next.js app. The installation process creates a folder name stories. And, under the folder, there are JavaScript files shipped during the installation process. Delete all the files under the stories folder. It'll break the storybook at 6006 port.

Install the following:

npm install @storybook/addon-postcss
npm install storybook-css-modules-preset

Go to the .storybook folder and open the main.js file and, modify the content of the files as follows:

module.exports = {
  "stories": [
    "../stories/**/*.stories.mdx",
    "../stories/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
      
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-postcss",
    "storybook-css-modules-preset"
  ],
  webpackFinal: async config => {
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
    });
    return config
  }
}

Open the preview.js file and update the content as follows:

import '../styles/globals.css'
export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}

Configure TailwindCSS

In our last article, we install and configure the TailwindCSS. Let's quickly add a new theme.

open tailwind.config.js and modify the theme as

theme: {
  extend: {
    colors: {
      'light-blue': colors.lightBlue,
      cyan: colors.cyan,
    }
  },

Now, the content of the file looks like,

const colors = require('tailwindcss/colors')
module.exports = {
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {
      colors: {
        'light-blue': colors.lightBlue,
        cyan: colors.cyan,
      }
    },
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

Create _document.js

Under the pages folder, create a new file _document.js and add the following content.

import Document from "next/document";
import {Html, Head, Main, NextScript} from "next/document";
class AppDocument extends Document {
    render () {
        return (
            <Html lang="en">
                <Head>
                    <meta httpEquiv="Cache-control" content="public" />
                    <meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
                    <meta name="keywords" content="Perl, Python, Mojolicious tutorial, Javascript, Next.js" />
                </Head>
                <body>
                    <Main></Main>
                    <NextScript></NextScript>
                </body>
            </Html>
        )
    }
}
export default AppDocument

You can modify the meta-name as per your preference. It's a sample only.

If you don't know what _document.js is, then follow this link to understand more about it.

Components

We'll create few components and then use those components in our pages. We'll start with the following:

  • HeaderComponent
  • NavigationComponent
  • CategoriesComponent
  • CategoriesListComponent
  • BlogComponent
  • BlogListComponent
  • ButtonComponent

We'll create the button component but, we'll use it in the forthcoming articles.

I prefer to divide the app into smaller components. Hence, it's easier to manage and debug it. It has another advantage we'll use it across the application without disturbing the UI.

Create new directory components. And, under the components folder, Create Header, Footer, Blog, Button, Categories folder.

Under the Header folder, create two javascript files:

  • HeaderComponent.js
  • NavigationComponent.js

Open the NavigationComponent.js and add the following content:

function NavigationComponent() {
    return (
        <div className="bg-gradient-to-r from-cyan-400 to-light-blue-500 rounded shadow-lg">
            <nav className="flex flex-wrap items-center justify-between p-4  rounded">
                <div className="block lg:hidden">
                    <button
                        className="navbar-burger flex items-center py-2 px-3 text-indigo-500 rounded border border-indigo-500">
                        <svg className="fill-current h-3 w-3" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                            <title>Menu</title>
                            <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
                        </svg>
                    </button>
                </div>
                <div className="navbar-menu hidden lg:flex lg:flex-grow lg:items-center w-full lg:w-auto">
                    <div className="lg:mx-auto">
                        <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>
                    </div>
                </div>
            </nav>
        </div>
    )
}
export default NavigationComponent

Now, open the HeaderComponent.js file and use the NagivationComponent in it.

import NavigationComponent from "./NavigationComponent";
function HeaderComponent() {
    return (
        <div>
            <NavigationComponent />
        </div>
    )
}
export default HeaderComponent;

And that's all to your HeaderComponent.

And to verify if your header component is working fine. Create a story inside the stories folder.

Create a new file Header.stories.js and add the following:

import HeaderComponent from "../components/Header/HeaderComponent";
export default {
    title: 'App/AppHeader',
    component: HeaderComponent
}
const Template = args => <HeaderComponent />
// New instance of component
 export const Default = Template.bind({})

We are not passing any arguments to the story because it's unnecessary at present. Maybe we'll add in the forthcoming articles.

Now, run

yarn storybook

In the storybook URL, you'll see AppHeader under the App folder. When you click on the AppHeader, it'll look like the below screenshot.

Categories Components

Create folder categories under the components folder.

We will create two components primary component always returns the list of the categories (CategoriesItem.js file). We'll also create another component that uses the categories-item component. Let's do that quickly.

Create a file CategoriesItem.js and add the following code:

import Link from "next/link";
function CategoriesItem() {
    const Categories = ['Perl', 'Python', 'Data Analysis', 'Mojolicious', 'NextJs']
    return (
        <div className="h-full-screen shadow-lg rounded">
            <ul className="ml-4 mb-4 max-w-full mr-4">
                <li>/#> ls</li>
                {
                    Categories.map(event =>
                        <div key={event}>
                            <li className="mt-2 mb-2 mr-4 py-4 px-4" id={event} >
                                - <a className="hover:underline hover:text-cyan-600" href="#"> {event} </a>
                            </li>
                            <hr />
                        </div>
                    )
                }
            </ul>
        </div>
    )
}
export default CategoriesItem

Now we'll create CategoriesComponent.js and add the following code:

import CategoriesItem from "./CategoriesItem";
function CategoriesComponent() {
    return (
        <div className="ml-10 mt-4">
            <CategoriesItem />
        </div>
    )
}

Now, it completes our categories component. Let's update the story for it.

Under the stories, creates a file Categories.stories.js. Add the following code:

import CategoriesComponent from "../components/Categories/CategoriesComponent";
export default {
    title: 'App/Categories',
    component: CategoriesComponent
}
const Template = args => <CategoriesComponent />
// New instance of component
 export const Default = Template.bind({})

If the storybook is already running, then you'll see Categories under the APP.

Button Component

We'll not use the Button component in this article. However, we need it in our upcoming sections/articles.

Let's create a primary button now.

Create a folder Button under the components directory.

And add the file Button.js. Update the file with the following:

const Button = ({size, children}) => {
    return(
        <button className={` bg-gradient-to-r from-cyan-400 to-light-blue-500 font-bold text-white px-6 py-2 rounded hover:bg-green-500 ${size} `}>
            {children}
        </button>
    )
}
export default Button;

Update the story now.

Create a file Button.stories.js under the stories. And add the following code:

import Button from "../components/Button/Button";

export default {
    title: 'UI/Control/Buttons'
}

const Template = args => <Button {...args} />

export const Primary = Template.bind({})

Primary.args ={
    size: "h-10 w-56",
    children: "Primary Button"
}

Open the storybook and, you'll see the button under the UI/Control/Buttons.

Blog Components

So far, we have created a basic navigation header, categories for our app. It's time to add the layout of the Blog component.

Create a folder Blog under the components folder. Add BlogComponent.js and BlogList.js file.

In the BlogList.js file add dummy content and update the file with the following:

function BlogList() {
    return (
        <div className="ml-4">
            <div className="border-b shadow rounded-md hover:shadow-lg hover:border">
                <div className="ml-4 mt-4">
                    <h1 className="font-bold text-2xl">It's All About (The) Next.js api routes</h1>
                </div>
                {/*Posted Date*/}
                <div className="ml-4 mt-4 flex">
                    <p>
                        <span className="text-gray-400">Posted On </span>
                        <small className="hover:underline hover:text-cyan-500">May 22, 2021</small>
                    </p>
                    {/*Categories Belongs to*/}
                    <div className="ml-10">
                        <span className="inline-block bg-cyan-200 text-cyan-800 text-xs px-2 rounded-full uppercase font-semibold tracking-wide mr-2">
                            Perl
                        </span>
                        <span className="inline-block bg-cyan-200 text-cyan-800 text-xs px-2 rounded-full uppercase font-semibold tracking-wide mb-10">
                            Nextjs
                        </span>
                    </div>
                </div>
            </div>
            <div className="border-b shadow rounded-md hover:shadow-lg hover:border">
                <div className="ml-4 mt-4">
                    <h1 className="font-bold text-2xl">Develop a blog in Next.js and Mojolicious - (Part - 1)</h1>
                </div>
                {/*Posted Date*/}
                <div className="ml-4 mt-4 flex">
                    <p>
                        <span className="text-gray-400">Posted On </span>
                        <small className="hover:underline hover:text-cyan-500">May 22, 2021</small>
                    </p>
                    {/*Categories Belongs to*/}
                    <div className="ml-10">
                        <span className="inline-block bg-cyan-200 text-cyan-800 text-xs px-2 rounded-full uppercase font-semibold tracking-wide mr-2">
                            Perl
                        </span>
                        <span className="inline-block bg-cyan-200 text-cyan-800 text-xs px-2 rounded-full uppercase font-semibold tracking-wide mb-10">
                            Nextjs
                        </span>
                    </div>
                </div>
            </div>
            <div className="border-b shadow rounded-md hover:shadow-lg hover:border">
                <div className="ml-4 mt-4">
                    <h1 className="font-bold text-2xl">It's All About (The) Next.js Api routes (Part-2)</h1>
                </div>
                {/*Posted Date*/}
                <div className="ml-4 mt-4 flex">
                    <p>
                        <span className="text-gray-400">Posted On </span>
                        <small className="hover:underline hover:text-cyan-500">May 22, 2021</small>
                    </p>
                    {/*Categories Belongs to*/}
                    <div className="ml-10">
                        <span className="inline-block bg-cyan-200 text-cyan-800 text-xs px-2 rounded-full uppercase font-semibold tracking-wide mr-2">
                            Perl
                        </span>
                        <span className="inline-block bg-cyan-200 text-cyan-800 text-xs px-2 rounded-full uppercase font-semibold tracking-wide mb-10">
                            Nextjs
                        </span>
                    </div>
                </div>
            </div>
        </div>
    )
}

export default BlogList

In the BlogComponent.js file, use the BlogList.js component.

import BlogList from "./BlogList";
function BlogComponent() {
    return (
        <div className="ml-4 mt-4 w-1/2 container mr-4">
            <BlogList />
        </div>
    )
}
export default BlogComponent

Update index.js

In the last section, we need to use those components on our home page. Open index.js under the pages folder and replace the contents with:

import Head from 'next/head'
import CategoriesComponent from "../components/Categories/CategoriesComponent";
import BlogComponent from "../components/Blog/BlogComponent";
export default function Home() {
  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 />
          <div></div>
      </div>
    </div>
  )
}

Execute the following:

yarn dev

As of now, anchor links are not working as we only create the basic layout of the app. We'll keep adding on the components as we progress in this series.

Thanks for reading upto here. Stay tuned for the next article.

  • The theme (cyan) of this blog is taken from TailwindCSS.com
  • Excerpts of the storybook button and Header is from the storybook.com documentation.