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.
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:
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.
// 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.
|
//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
// 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
} |
// getFormattedDate.ts export default function getFormattedDate(dateString: string): string {
return new Intl.DateTimeFormat( 'en-US' ,
{ dateStyle: 'long' }).format( new Date(dateString))
} |
// 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>
)
} |
// 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>
)
} |
// 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>
)
} |
// 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>
)
} |
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>
)
} |
// 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>
)
} |
// not-found.tsx export default function NotFound() {
return (
<h1>The requested post does not exist.</h1>
)
} |
// 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>
)
} |
// 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: