Open In App

Creating a Blog Webpage using Next.js

Last Updated : 11 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we will delve into the process of setting up a blog, with NextJS, a known React framework. The blog will come equipped with features that enable users to compose, view, edit, and remove blog entries. We’ll make use of NextJS capabilities to construct a speedy and search-engine-optimized site.

Output Preview: Let us have a look at how the final output will look like.

ezgif-2-fb9949c815

Final Output

Prerequisites:

Approach to Create a Blog Webpage with NextJS:

  • Posts Handling (posts.ts): Fetches posts data from markdown files. Parses metadata using gray-matter. Converts markdown content to HTML using remark.
  • Date Formatting (getFormattedDate.ts): Formats date strings using Intl.DateTimeFormat.
  • List Item Component (ListItem.tsx): Displays individual post titles and dates. Links to respective post pages.
  • Profile Picture Component (MyProfilePic.tsx): Displays a profile picture using Next.js Image component.
  • Navbar Component (Navbar.tsx): Navigation bar with site title and links. Utilizes React icons for social media links.
  • Layout Component (layout.tsx): Sets up the overall layout including navbar and profile picture. Allows for global styling and structure.
  • Post Page (page.tsx): Fetches and displays individual post content. Provides links back to the homepage.
  • Homepage (index.tsx): Displays a greeting message and lists posts using the Posts component.

Steps to Create a NeaxtJS App and Installing Module:

Step 1: Create a New NextJS Project Using Command.

npx create-next-app@latest --ts

Step 2: Change the current directory using Command.

cd <<Name_of_project>>

Step 3: Run the Server by following command.

npm run dev

Step 3: Remove all the boilerplate code from the page.tsx file

Step 4: Remove the page.module.css file from the app directory and clear all the code from globals.css file

Step 5: Install Tailwind by following command

npm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p

Step 6: Configure Tailwind by adding the below code to the tailwind.config.js file in content section

"./app/**/*.{js,ts,jsx,tsx,mdx}",    "./pages/**/*.{js,ts,jsx,tsx,mdx}",    "./components/**/*.{js,ts,jsx,tsx,mdx}",

Project Structure:

projectStructure

Step 7: Create the Markdown Files and add the following code

To start make a main folder named “blogposts” (different, from pages/posts) in your main directory. Within the posts folder generate two files; one called pre-rendering.md and another named ssg-ssr.md. Next paste the provided code into the file.

Javascript




// Pre-rendering.md
---
title: 'Server-side Rendering'
date: '2024-02-29'
---
 
Hello: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
 
- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
 
Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.


Javascript




//ssg-ssr.md
---
title: 'Static Generation'
date: '2024-03-01'
---
 
We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
 
You can use Static Generation for many types of pages, including:
 
- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation
 
You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
 
On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
 
In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.


You may have observed that every markdown document includes a metadata segment, with the title and date. 
This section is referred to as YAML Front Matter. It can be interpreted using a tool named gray-matter.

Step 8: Install the gray-matter

npm install gray-matter

Step 9: Create a Utility function which will read the file-system

Lets move on to developing a tool that can help us extract information from the file system. This tool will allow us to:

  • In each document extract the title, date and file name (to serve as the post URL id).
  • List the data on the page organized by date.

Begin by setting up a folder named lib in the main directory. Next within the lib folder establish a document titled posts.ts and getFormattedDate.ts then insert the following code snippet;

Step 10: Write the following code in different files with the file structure. Create a components folder in your app directory and add this four following files:

  • Listitem.tsx
  • MyProfilePic.txt
  • Navbar.tsx
  • Posts.tsx

Step 11: Create a sub folder in posts directory called [postid] and add these files following the structure

  • not-found.tsx
  • page.tsx [this will be in your sub folder]
  • layout.tsx
  • page.tsx

Javascript




// posts.ts
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { remark } from 'remark'
import html from 'remark-html'
 
const postsDirectory = path.join(process.cwd(),
    'blogposts')
 
export function getSortedPostsData() {
    // Get file names under /posts
    const fileNames = fs.readdirSync(postsDirectory);
    const allPostsData = fileNames.map((fileName) => {
        // Remove ".md" from file name to get id
        const id = fileName.replace(/\.md$/, '');
 
        // Read markdown file as string
        const fullPath = path.join(postsDirectory, fileName);
        const fileContents = fs.readFileSync(fullPath, 'utf8');
 
        // Use gray-matter to parse the post metadata section
        const matterResult = matter(fileContents);
 
        const blogPost: BlogPost = {
            id,
            title: matterResult.data.title,
            date: matterResult.data.date,
        }
 
        // Combine the data with the id
        return blogPost
    });
    // Sort posts by date
    return allPostsData.sort(
        (a, b) => a.date < b.date ? 1 : -1);
}
 
export async function getPostData(id: string) {
    const fullPath = path.join(postsDirectory, `${id}.md`);
    const fileContents = fs.readFileSync(fullPath, 'utf8');
 
    // Use gray-matter to parse the post metadata section
    const matterResult = matter(fileContents);
 
    const processedContent = await remark()
        .use(html)
        .process(matterResult.content);
 
    const contentHtml = processedContent.toString();
 
    const blogPostWithHTML: BlogPost & {
        contentHtml: string
    } = {
        id,
        title: matterResult.data.title,
        date: matterResult.data.date,
        contentHtml,
    }
 
    // Combine the data with the id
    return blogPostWithHTML
}


Javascript




// getFormattedDate.ts
export default function getFormattedDate(dateString: string): string {
    return new Intl.DateTimeFormat('en-US',
        { dateStyle: 'long' }).format(new Date(dateString))
}


Javascript




// Listitem.tsx
import Link from "next/link"
import getFormattedDate from "@/lib/getFormattedDate"
 
type Props = {
    post: BlogPost
}
 
export default function ListItem({ post }: Props) {
    const { id, title, date } = post
    const formattedDate = getFormattedDate(date)
 
    return (
        <li className="mt-4 text-2xl dark:text-black/90">
            <Link
                className="underline hover:text-black/70
            dark:hover:text-green"
                href={`/posts/${id}`}>{title}</Link>
            <br />
            <p className="text-sm mt-1">
                {formattedDate}
            </p>
        </li>
    )
}


Javascript




// MyProfilePic.tsx
import Image from "next/image"
 
export default function MyProfilePic() {
    return (
        <section className="w-full mx-auto">
            <Image
                className="border-4 border-black dark:border-green-500
                drop-shadow-xl shadow-black rounded-full mx-auto mt-8"
                src="/images/icons8-geeksforgeeks-600.png"
                width={200}
                height={200}
                alt="Geek"
                priority={true}
            />
        </section>
    )
}


Javascript




// Navbar.tsx
import Link from "next/link"
import {
    FaYoutube,
    FaTwitter,
    FaGithub,
    FaLaptop
} from "react-icons/fa"
 
export default function Navbar() {
    return (
        <nav className="bg-green-600 p-4 sticky
                        top-0 drop-shadow-xl z-10">
            <div className="prose prose-xl
            mx-auto flex justify-between
            flex-col sm:flex-row">
                <h1 className="text-3xl font-bold
                text-black grid place-content-center
                 mb-2 md:mb-0">
                    <Link href="/" className="text-black/90
                    no-underline hover:text-white">
                        GeeksForGeeks Blog
                    </Link>
                </h1>
                <div className="flex flex-row justify-center
                 sm:justify-evenly align-middle
                 gap-4 text-black text-4xl lg:text-5xl">
 
                </div>
            </div>
        </nav>
    )
}


Javascript




// Posts.tsx
import { getSortedPostsData } from "@/lib/posts"
import ListItem from "./ListItem"
 
export default function Posts() {
    const posts = getSortedPostsData()
 
    return (
        <section className="mt-6 mx-auto max-w-2xl">
            <h2 className="text-4xl font-bold
            dark:text-black/90">
                Blog
            </h2>
            <ul className="w-full">
                {posts.map(post => (
                    <ListItem key={post.id} post={post} />
                ))}
            </ul>
        </section>
    )
}


Javascript




import Link from "next/link"
import getFormattedDate from "@/lib/getFormattedDate"
 
type Props = {
    post: BlogPost
}
 
export default function ListItem({ post }: Props) {
    const { id, title, date } = post
    const formattedDate = getFormattedDate(date)
 
    return (
        <li className="mt-4 text-2xl dark:text-black/90">
            <Link className="underline hover:text-black/70
             dark:hover:text-green" href={`/posts/${id}`}>
                {title}
            </Link>
            <br />
            <p className="text-sm mt-1">{formattedDate}</p>
        </li>
    )
}


Javascript




// layout.tsx
import './globals.css'
import Navbar from './components/Navbar'
import MyProfilePic from './components/MyProfilePic'
 
export const metadata = {
    title: "Geeks Blog",
    description: 'Created by Geek Blogger',
}
 
export default function RootLayout({
    children,
}: {
    children: React.ReactNode
}) {
    return (
        <html lang="en">
            <body className="dark:bg-white-800">
                <Navbar />
                <MyProfilePic />
                {children}
            </body>
        </html>
    )
}


Javascript




// not-found.tsx
export default function NotFound() {
    return (
        <h1>The requested post does not exist.</h1>
    )
}


Javascript




// page.tsx ([postid])
import getFormattedDate from "@/lib/getFormattedDate"
import {
    getSortedPostsData,
    getPostData
} from "@/lib/posts"
import { notFound } from "next/navigation"
import Link from "next/link"
 
export function generateStaticParams() {
    const posts = getSortedPostsData()
 
    return posts.map((post) => ({
        postId: post.id
    }))
}
 
export function generateMetadata({ params }:
    { params: { postId: string } }) {
 
    const posts = getSortedPostsData()
    const { postId } = params
 
    const post = posts.find(post => post.id === postId)
 
    if (!post) {
        return {
            title: 'Post Not Found'
        }
    }
 
    return {
        title: post.title,
    }
}
 
export default async function Post({ params }:
    { params: { postId: string } }) {
 
    const posts = getSortedPostsData()
    const { postId } = params
 
    if (!posts.find(post => post.id === postId)) notFound()
 
    const { title, date, contentHtml } = await getPostData(postId)
 
    const pubDate = getFormattedDate(date)
 
    return (
        <main className="px-6 prose prose-xl prose-green  mx-auto">
            <h1 className="text-3xl mt-4 mb-0">{title}</h1>
            <p className="mt-0">
                {pubDate}
            </p>
            <article>
                <section dangerouslySetInnerHTML={{
                    __html: contentHtml
                }} />
                <p>
                    <Link href="/">← Back to home</Link>
                </p>
            </article>
        </main>
    )
}


Javascript




// page.tsx
import Posts from "./components/Posts"
 
export default function Home() {
    return (
        <main className="px-6 mx-auto">
            <p className="mt-12 mb-12 text-3xl
                  text-center dark:text-black">
                Hello Geeks
                <span className="whitespace-nowrap">
                </span>
            </p>
            <Posts />
        </main>
    )
}


Run the below command to start the server

npm run dev

Output:

ezgif-2-fb9949c815

Final Output



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads