Build a static content website using Nextjs React
Originally published on iHateReading
Hello and welcome to the new blog
In today's story, we will go through the way to build the simplest static blog website
Nextjs and React are JavaScript-based frameworks mainly dependent on JavaScript, and that brings some slowness to them on the browser or website built using the same are a bit slow.
But a static content website with Nextjs is fast and gives some powerful tools like React, Server Side API, if needed, dynamic APIs and a lot more, and that is why for a static content website I'll still prefer Nextjs and React
Last month, I launched this new website, gettemplate.website, and for this, I need to quickly launch a blog post.
One good way is to build an admin with an editor to write blogs and a dashboard to view and manage blogs, but it takes of lot of CRUD operations and style management, which need time, and our deadline was about a few hours to quickly put a few blogs live on the website.
Creativity comes when limitations arise, so I was thinking of some quick, cheap way to just write markdown content in a file and simply render the file content using Nextjs server-side API, or a simple fs module can also be used to read the file content.
Here is our way,
- First, store or write all blogs in markdown format in the content directory or folder
- Inside the content directory, I've put a folder named blogs (contains a markdown file)
- Each file name will be the same as the blog URL: gettemplate-introduction is gettemplate-introduction.md file
- Read each file content using the fs module, file the file name similar to the title and extract the content and render the content using the server-side API
Don't worry if it's too confusing, I'll bring some code samples below, along witha code repository to quickly get started
A few packages we will be needing because we are reading markdown content and parsing it into HTML
npm i remark gray-matter remark-html
Once installed, we are ready. Inside the pages directory, create a folder named blogs and inside it, add [slug].js file
This will serve as a nested route meaning blogs/{name-of-the-blog} will be the served URL
Inside this slug.js file, we need to fetch the content, read the file to get markdown content, convert it into HTML and pass it to React to render it.
import path from "path";
import matter from "gray-matter";
import { remark } from "remark";
import html from "remark-html";
import fs from "fs";
import Head from "next/head";
import SingleBlog from "../../components/SingleBlog";
import { blogs } from "../../content/blogs/blogs";
const blogsDir = path.join(process.cwd(), "content/blogs");
export async function getStaticPaths() {
const files = fs.readdirSync(blogsDir);
const paths = files
.filter((file) => file.endsWith(".md"))
.map((file) => ({
params: { slug: file.replace(/\.md$/, "") },
}));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
const { slug } = params;
const filePath = path.join(blogsDir, `${slug}.md`);
const fileContents = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(fileContents);
const processedContent = await remark().use(html).process(content);
const contentHtml = processedContent.toString();
// Find blog metadata from blogs.js
const blogData = blogs.find((blog) => {
const blogSlug = blog.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return blogSlug === slug;
});
return {
props: {
data: data,
contentHtml: contentHtml,
blogData: blogData,
slug: slug,
},
};
}
export default function BlogPage({ data, contentHtml, blogData, slug }) {
const title = blogData?.title || slug.replace(/-/g, " ");
const description = blogData?.description || "Read our latest blog post";
const bannerImage = blogData?.banner || "/logo.png";
const tags = blogData?.tags || [];
const date = blogData?.date || data?.date;
return (
<>
<SingleBlog data={data} contentHtml={contentHtml} />
</>
);
}
Below, you will find how we are rendering SingleBlog content
import React from "react";
import { useRouter } from "next/router";
import { blogs } from "../content/blogs/blogs";
import { ArrowLeft } from "lucide-react";
const SingleBlog = ({ data, contentHtml }) => {
const router = useRouter();
const slug = router.query.slug;
const blogData = blogs.find((blog) => {
const blogSlug = blog.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return blogSlug === slug;
});
return (
<div className="prose p-4 max-w-7xl mx-auto border-l border-r border-dashed border-zinc-200 min-h-screen">
<div className="pt-20 max-w-4xl mx-auto">
<div
className="w-fit flex items-center gap-1 mb-8 hover:border border-dashed border-zinc-200 p-1 cursor-pointer"
onClick={() => router.push("/blogs")}
>
<ArrowLeft className="w-4 h-4" />
<span className="text-zinc-900 hover:text-zinc-700 transition-all text-sm whitespace-nowrap">
Blogs
</span>
</div>
{blogData?.banner && (
<img
src={blogData.banner}
alt={blogData.title}
className="w-full h-full border border-zinc-100 border-dashed object-contain rounded-md mb-4"
/>
)}
<h1 className="text-4xl font-bold mb-4">
{router.query.slug.replace(/-/g, " ")}
</h1>
<p className="text-gray-500 text-sm mb-6">{blogData.description}</p>
<p className="text-gray-500 text-sm mb-6">{data.date}</p>
<div className="flex flex-wrap gap-2 my-2">
{blogData.tags.map((tag) => (
<span
key={tag}
className="border border-zinc-200 border-dashed text-zinc-800 text-xs px-2 py-1 rounded-full"
>
{tag}
</span>
))}
</div>
<div
dangerouslySetInnerHTML={{ __html: contentHtml }}
className="my-2 [&_p]:my-4 [&_h1]:my-4 [&_h2]:my-4 [&_h3]:my-4 [&_h4]:my-4 [&_h5]:my-4 [&_h6]:my-4 [&_a]:text-black [&_a:hover]:text-black [&_a]:underline [&_a]:cursor-pointer [&_li]:list-disc [&_li]:list-inside"
/>
</div>
</div>
);
};
export default SingleBlog;
In this way, when we open blogs/{name-of-the-blog} URL, Nextjs will run getServerSide, which uses the name-of-the-blog as params to read the exact file content and finally pass it to the render method
Lastly, to manage new blogs, we need to put the entire blog inside one JSON, and I've used the same content directory in the root directory. Inside it, add a file named blogs.json. This json file contains all the blog details as given below
export const blogs = [
{
title: "20 Portfolio website templates for developers and designers",
description:
"Discover the best portfolio website templates for developers and designers. These templates are built with Next.js and Tailwind CSS and are designed to be responsive and easy to customize.",
banner:
"https://b4fcijccdw.ufs.sh/f/mVUSE925dTRYeccOGphRC8d70OUPuc2IwtYrqSm6zavokBX3",
tags: ["Next.js", "React", "Templates", "Portfolio"],
date: "October 8, 2025",
},
{
title: "Welcome gettemplate",
description:
"Discover the new UI, premium templates, and future roadmaps of GetTemplate.website, your go-to for React and Next.js code templates.",
banner:
"https://b4fcijccdw.ufs.sh/f/mVUSE925dTRYEo9UUDgbos21nV4U8LqPX9y0aifKzZCQRjhM",
tags: ["Next.js", "React", "Templates", "Updates"],
date: "September 30, 2025",
},
];
Consider this JSON as our database; one can even migrate it to Firebase, Supabase, or MongoDB later on if you want to scale the application or use some authentication service
One more thing regarding files such as images, for images, you can integrate Firebase or Supabase storage or AWS storage as well, you can even use Uploadthing to upload images and get a public URL and then use as banner image and markdown images
We are using Nextjs server-side API, which has costs, so take care of it if your application scales up to 2k visitors per month.
This is by far the quickest method to create your own content website instantly. This is still not the static content as we are serving content finally using JavaScript API whereas I want to render the final HTML stored in the code repository to make it instant fast and SEO optimsed at the same time.
But for the time being this is good, that's it for today, see you in the next one
Shrey
Top comments (0)