Dynamic Blog with Next.js & HeroUI Components

Creating a fast, scalable blog is easier with the right tools. Next.js and HeroUI offer everything you need. This guide shows you how to build a dynamic blog using both technologies with TypeScript. We’ll cover setup, routing, components, and styling.

Step 1: Set Up Your Next.js Project

Step 2: Create Blog Post Structure

Directory structure

dynamic-blog/
├── content/
│   └── posts/
│       ├── my-first-post.md
│       ├── learning-nextjs.md
│       └── another-great-article.md
├── app/
│   ├── blog/
│       └── [slug]/
│           └── page.tsx
│       └── page.tsx
├── components/
│   └── posts.ts
├── content/
│   ├── posts/
│       └── my-first-post.md
├── ... other files ...

Define the blog post type in types/post-data.ts.

export interface PostData {
  slug: string;
  title: string;
  date: string;
  contentHtml: string;
  [key: string]: any; // Allow other frontmatter fields
}

Add mock blog data in content/posts/my-first-post.md.

---
title: "My First Blog Post"
date: "2025-05-01"
author: "John Doe"
tags: ["introduction", "first post"]
---

Hello and welcome to my brand new blog! I'm so excited to start sharing my thoughts and experiences with you all.

This is just the beginning of what I hope will be a long and informative journey. In this first post, I wanted to briefly introduce myself and what you can expect to find here in the future.

I plan to write about a variety of topics, including technology, travel, and personal development. Feel free to leave a comment below and let me know what you're interested in reading about!

Stay tuned for more updates soon.

---

**Key takeaways from this post:**

* Welcome to the blog!
* Introduction of the author.
* Overview of future content.m

Add mock blog data in content/posts/learning-nextjs.md.

---
title: "Learning Next.js: A Beginner's Guide"
date: "2025-05-05"
author: "Jane Smith"
tags: ["nextjs", "react", "frontend"]
---

Next.js has quickly become one of the most popular frameworks for building modern web applications with React. Its features like server-side rendering (SSR), static site generation (SSG), and the new App Router make it a powerful tool for developers.

In this guide, I'll walk you through some of the fundamental concepts of Next.js, including:

* **File-based routing:** How Next.js uses your directory structure to define routes.
* **Components:** Building reusable UI elements.
* **Data fetching:** Exploring different strategies like `getServerSideProps`, `getStaticProps`, and the new data fetching in Server Components within the App Router.
* **Styling:** Integrating CSS and using libraries like Tailwind CSS (which we're using in this blog!).

We'll also touch upon the exciting new features introduced in Next.js 13 and 14, such as Server Components and the App Router, which significantly change how we build Next.js applications.

This is just an introductory post, and we'll dive deeper into specific topics in future articles. Keep an eye out!

Step 3: Create a Blog List Page

Create app/blog/page.tsx to show all posts.

npm install date-fns
// src/app/blog/page.tsx
import Link from 'next/link';
import { format } from 'date-fns';
import { getSortedPostsData } from '@/components/posts';

export default async function BlogIndex() {
  const allPostsData = await getSortedPostsData();

  return (
    <div className="py-12 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
      <h1 className="text-3xl font-bold text-gray-900 mb-8">Latest Articles</h1>
      <ul>
        {allPostsData.map((post :any) => (
          <li key={post.slug} className="mb-6">
            <Link href={`/blog/${post.slug}`} className="block hover:bg-gray-100 rounded-md p-4">
              <h2 className="text-xl font-semibold text-gray-800">{post.title}</h2>
              <p className="text-sm text-gray-500">
                Published on {format(new Date(post.date), 'MMMM dd, yyyy')}
              </p>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Step 4: Add Dynamic Routing for Blog Posts

Create the folder app/blog/[slug]/page.tsx.

// src/app/blog/[slug]/page.tsx

import { notFound } from "next/navigation";
import { format } from "date-fns";

import { getAllPostSlugs, getPostData } from "@/components/posts";

interface Props {
  params: {
    slug: string;
  };
}

export async function generateStaticParams() {
  const slugs = await getAllPostSlugs();

  return slugs;
}

export async function generateMetadata({ params }: Props) {
  const { slug } = await params;
  const postData = await getPostData(slug);

  if (!postData) {
    return {
      title: "Blog Post Not Found",
    };
  }

  return {
    title: postData.title,
    description: "...",
  };
}

export default async function Post({ params }: Props) {
  // Await the params object
  const { slug } = await params;

  const postData = await getPostData(slug);

  if (!postData) {
    notFound();
  }

  return (
    <>
      <div className="py-12 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
        <article className="bg-white shadow-lg rounded-lg p-8">
          <h1 className="text-3xl font-bold text-gray-900 mb-4">
            {postData.title}
          </h1>
          <p className="text-sm text-gray-500 mb-4">
            Published on {format(new Date(postData.date), "MMMM dd, yyyy")}
          </p>
          <div
            dangerouslySetInnerHTML={{ __html: postData.contentHtml }}
            className="prose prose-lg"
          />
        </article>
      </div>
    </>
  );
}

This enables dynamic blog pages using id.

Create the folder component/post.ts.

// src/lib/posts.ts
import fs from "fs/promises";
import path from "path";
import matter from "gray-matter";
import { remark } from "remark";
import html from "remark-html";
import { PostData } from "@/types/post-data";

const postsDirectory = path.join(process.cwd(), "content/posts");

export async function getSortedPostsData(): Promise<
  Omit<PostData, "contentHtml">[]
> {
  const fileNames = await fs.readdir(postsDirectory);
  const allPostsData = await Promise.all(
    fileNames.map(async (fileName) => {
      const slug = fileName.replace(/\.md$/, "");
      const fullPath = path.join(postsDirectory, fileName);
      const fileContents = await fs.readFile(fullPath, "utf8");
      const matterResult = matter(fileContents);
      return {
        slug,
        ...(matterResult.data as { title: string; date: string }),
      };
    })
  );
  return allPostsData.sort((a, b) => (a.date < b.date ? 1 : -1));
}

export async function getAllPostSlugs(): Promise<
  { params: { slug: string } }[]
> {
  const fileNames = await fs.readdir(postsDirectory);
  return fileNames.map((fileName) => ({
    params: {
      slug: fileName.replace(/\.md$/, ""),
    },
  }));
}

export async function getPostData(slug: string): Promise<PostData> {
  const fullPath = path.join(postsDirectory, `${slug}.md`);
  const fileContents = await fs.readFile(fullPath, "utf8");
  const matterResult = matter(fileContents);
  const processedContent = await remark()
    .use(html)
    .process(matterResult.content);
  const contentHtml = processedContent.toString();

  return {
    slug,
    contentHtml,
    ...(matterResult.data as { title: string; date: string }),
  };
}

Run the application.

> npm run dev
> [email protected] dev
> next dev --turbopack

   ▲ Next.js 15.0.4 (Turbopack)
   - Local:        http://localhost:3000

 ✓ Starting...
 ✓ Ready in 1478ms
http://localhost:3000/blog
Blog
http://localhost:3000/blog
Post

Step 5: Improve Blog SEO

Using the metadata export

import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My Page Title',
  description: 'This is the description of my page. It should be concise and informative.',
};

Dynamic Description

import type { Metadata } from 'next';

export async function generateMetadata(): Promise<Metadata> {
  // Fetch data to construct the description
  const post = await getPostFromDatabase();
  const description = `${post.excerpt} Learn more about ${post.title} on our site.`;

  return {
    title: post.title,
    description: description,
  };
}

Use the generateMetadata from Next.js for meta tags.

interface Props {
  params: {
    slug: string;
  };
}

export async function generateMetadata({ params }: Props) {
  const { slug } = await params;
  const postData = await getPostData(slug);
  if (!postData) {
    return {
      title: 'Blog Post Not Found',
    };
  }
  return {
    title: postData.title,
    description: "...",
  };
}

Also, add Open Graph and Twitter metadata later to improve sharing.

Default Metadata site.ts

export type SiteConfig = typeof siteConfig;

export const siteConfig = {
  name: "Next.js + HeroUI",
  description: "Make beautiful websites regardless of your design experience.",
....

Step 6: Style Blog Posts

You can use Tailwind classes to improve typography. Wrap content with styled divs.

Modify app/blog/[slug]/page.tsx

return (
  <>
    <div className="py-12 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
      <article className="bg-white shadow-lg rounded-lg p-8">
        <h1 className="text-4xl font-extrabold text-gray-900 tracking-tight mb-6">
          {postData.title}
        </h1>
        <p className="text-base text-gray-500 mb-4">
          Published on {format(new Date(postData.date), "MMMM dd, yyyy")}
        </p>
        <div
          dangerouslySetInnerHTML={{ __html: postData.contentHtml }}
          className="prose prose-lg prose-headings:text-gray-900 prose-headings:font-semibold prose-p:text-gray-700 prose-a:text-blue-600 hover:prose-a:text-blue-800 prose-a:transition-colors prose-a:no-underline leading-relaxed"
        />
      </article>
    </div>
  </>
);

This plugin makes your blog posts look clean and professional.

Conclusion

With Next.js and HeroUI, you can build a modern blog quickly. TypeScript adds safety and better tooling. HeroUI helps you deliver great UI fast. Tailwind ensures your blog stays stylish and responsive.

Building a Blog with Next.js Infographic

A visual guide to the key steps for creating a dynamic blog using Next.js, HeroUI, and Tailwind CSS.

1. Project Setup

Initialize Next.js project and define your content structure using TypeScript and `PostData`.

2. Create Content

Write blog posts in Markdown (`.mdx`) format, including frontmatter for metadata.

3. Build Blog List

Create the main page that lists all blog posts, fetching and displaying `PostData`.

4. Dynamic Routing

Set up dynamic routes to generate a unique page for each blog post using `generateStaticParams`.

5. Styling & SEO

Style your content with Tailwind CSS and use `generateMetadata` for per-page SEO.

Final Result

A fast, scalable, and stylish blog ready for production.

This article was originally published on Medium.

1 thought on “Dynamic Blog with Next.js & HeroUI Components”

  1. I don’t even know how I ended up here, but I thought thiis post was great.
    I don’t know who you are but definitely you are going to a
    famous blogger if you are not already 😉 Cheers!

    Here is my web sitre … boyarka

Leave a Comment

Your email address will not be published. Required fields are marked *