Building a Comment System in a Next.js Blog with BCMS

How to build comment system for blog using BCMS.jpeg
By Juliet Ofoegbu
Read time 5 min
Posted on 2 Apr 2025

When you create a blog, you don’t just want users to be able to read your blog. You’ll want them to be able to make comments; whether it’s a thought-provoking discussion, feedback, or appreciation, a comment system allows readers to interact with your content and each other. So a great blog isn’t just about publishing content. Adding a comment system to your blog enhances user engagement and allows readers to share their thoughts. 

In this guide, I’ll walk you through building a comment system in a Next.js blog using BCMS. BCMS provides a powerful API-based CMS solution that makes it easy to manage and store comments efficiently. I'll start by setting up the project using the open source BCMS starter template, creating API routes to handle comments, and then implementing a front-end component for users to submit and display comments.

By the end of this guide, you'll have a fully functional comment section where users can add their thoughts to your blog posts.

Setting Up the Project with BCMS for the best comment system

Before we dive into building the comment system, you need a Next.js project integrated with BCMS. The easiest way to get started is to use the BCMS Next.js starter template, which comes with pre-configured settings for fetching blog data.

To set up the project, run the following command in your terminal:

npx @thebcms/cli create next simple-blog

After running this command, you’ll be prompted to install some packages, which you’ll go ahead to do by typing “y” in your terminal. This will initialize the BCMS project in your system and provide a link right there in your terminal, which you’ll click to take you to a web browser for you to log in to your BCMS dashboard. If you’ve not created a BCMS account, now would be a great time to do so.

After a successful login, go back to your terminal and name your project, e.g., “my-bcms-blog”. This will then clone the starter GitHub repo into your project with the Next.js project along with a sample blog layout.

Feel free to navigate to your project dashboard by clicking the link that’ll be provided in the terminal and explore the dashboard to see the template and entries added by default.

Now go back to your terminal, navigate into the project, and install the necessary dependencies using these commands:

cd my-bcms-blognpm install

What this does:

  1. Clones the BCMS Next.js starter template into a new folder called my-bcms-blog.

  2. Installs all required dependencies so that the project is ready to use.

After this, you’ll have a Next.js project connected to BCMS, allowing you to retrieve and display blog posts dynamically.

Create New Comment Template

In your BCMS dashboard, navigate to the Templates page and click the Create new template button. Give the template a name: Comment.

When you create a new template in BCMS, two properties, title and slug, will be automatically added. Now add other properties by dragging the string property to the middle area and naming it From and Comment Text. Also, add an Entry Pointer Array and select the “Blog” template as the entry so that the comment template points to the blog template. 

Your new template should look like this now:

Comment Template

Learn more about the different properties in a template by visiting the BCMS properties doc page.

Granting Access Permissions

When you’re fetching requests from a CMS like BCMS, you’d have to grant access permission for your API keys to be able to fetch data from the backend to the frontend. 

By default, the API keys for the BCMS Next.js blog project from the starter code will have the GET access checkbox ticked. This is how the app can get the blog details from the BCMS dashboard and display them on the frontend.

To grant permission for the Comment template you created, toggle the Administration dropdown, click the Settings option, and click the API key tab. When you’re on the API key page, hit the Auto Generated Key rectangular shape. You should now see the API key details on the right-hand side of that page. Those API keys will be located in the .env file of your code and they’re meant to be private.

Now update the permission for the newly created Comment template to enable a GET and CREATE functionality by ticking the Can get and Can create checkboxes.

Template Permissions

Click the Update key button to save these settings. That’s all you need to do in the dashboard. If these permissions aren’t granted, it won’t be possible to get data from the template or add entries to the template.

Let’s move on to the codebase.

Using the BCMS Next.js starter command installed the necessary dependencies we needed, set up a Next.js project for us, and gave us a simple blog layout. To go into the main work of implementing a comment functionality in our blog, follow the steps below.

Structuring the API Folder

Since we need API endpoints to handle fetching blogs and managing comments, we’ll create an api folder inside the app directory. The folder structure looks like this:

api/
┣ blog/
┃ ┗ route.ts
┣ comment/
┃ ┣ create/
┃ ┃ ┗ route.ts
┃ ┗ get-all/
┃   ┗ route.ts
┗ route.ts

This structure keeps our API routes well organized:

  • The api/blog/route.ts fetches blog posts.

  • The api/comment/create/route.ts handles comment submissions.

  • The api/comment/get-all/route.ts retrieves all comments.

Now, let's implement these API routes one by one.

Fetching Blog Posts

The first API route I’ll create is api/blog/route.ts, one that fetches all blog posts from BCMS.

import { bcms } from "@/app/bcms-client";
import { BlogEntry, BlogEntryMetaItem } from "@bcms-types/types/ts";
import { NextResponse } from "next/server";

export async function GET() {
   try {
       const blogs = (await bcms.entry.getAll('blog')) as BlogEntry[];

       const items = blogs.map((blog) => {
           return blog.meta.en as BlogEntryMetaItem;
       });
       return NextResponse.json({ success: true, data: items });
   } catch (error) {
       console.error("Failed to fetch support requests:", error);
       return NextResponse.json({ success: false, error: "Failed to fetch support requests" }, { status: 500 });
   }
}

How this works:

  1. Retrieves all blog posts from BCMS using bcms.entry.getAll('blog').

  2. Extracts metadata (title, slug, etc.) using .map((blog) => blog.meta.en as BlogEntryMetaItem).

  3. Returns the blog posts as JSON in the API response.

  4. Handles errors, logs them, and returns a 500 status if something goes wrong.

This API route makes blog data available to the front end, where it can be displayed dynamically.

Creating Comments

Next, I’ll implement api/comment/create/route.ts, that will handle new comment submissions to create new comments in BCMS.

import { NextResponse } from "next/server";
import { bcms } from "@/app/bcms-client";

export async function POST(request: Request) {
   try {
       // Parse the request body
       const body = await request.json();

       // Validate required fields
       if (!body.title || !body.slug || !body.from || !body.blog?.entryId || !body.blog?.templateId) {
           return NextResponse.json(
               { success: false, error: "Missing required fields (title, slug, from, blog.entryId, blog.templateId)" },
               { status: 400 }
           );
       }

       // Create the comment using the provided data
       const newComment = await bcms.entry.create("comment", {
           content: [],
           statuses: [],
           meta: [
               {
                   lng: "en",
                   data: {
                       title: body.title,
                       slug: body.slug,
                       from: body.from,
                       comment_text: body.comment_text,
                       blog: {
                           entryId: body.blog.entryId,
                           templateId: body.blog.templateId,
                       },
                   },
               },
           ],
       });

       return NextResponse.json({ success: true, data: newComment });
   } catch (error) {
       console.error("Error creating comment:", error);
       return NextResponse.json(
           { success: false, error: "Failed to create comment" },
           { status: 500 }
       );
   }
}

How it works:

  • It receives a POST request with the following required fields:

    • title: Title of the comment.

    • slug: Auto-generated unique identifier for the comment.

    • from: The name of the person submitting the comment.

    • comment_text: The actual comment text.

    • blog.entryId & blog.templateId: Identifiers linking the comment to a specific blog post.

  • The request body is validated to ensure all required fields are present.

  • The BCMS entry creation method (bcms.entry.create) is used to store the comment.

  • If successful, it returns a success response with the created comment; otherwise, it returns an error response.

This API enables users to submit comments to your blog posts.

Fetching All Comments

To display comments under each blog post, we need an API to fetch them. Here’s the code for the api/comment/get-all/route.ts file:

import { NextResponse } from "next/server";
import { bcms } from "@/app/bcms-client";
import { CommentEntry, CommentEntryMetaItem } from "@bcms-types/types/ts";

export async function GET() {
   try {
       const comments = (await bcms.entry.getAll('comment')) as CommentEntry[];

       const items = comments.map((blog) => {
           return blog.meta.en as CommentEntryMetaItem;
       });
       return NextResponse.json({ success: true, data: items });
   } catch (error) {
       console.error("Failed to fetch support requests:", error);
       return NextResponse.json({ success: false, error: "Failed to fetch support requests" }, { status: 500 });
   }
}

How it works:

  1. A GET request retrieves all comments from BCMS using bcms.entry.getAll('comment').

  2. The response is mapped to extract only the English (meta.en) fields for each comment.

  3. The filtered comments are returned in JSON format for easy use on the frontend. 

  4. It then returns comments successfully or logs an error if the request fails.

Root API Endpoint (api/route.ts)

This serves as a basic test endpoint for the API. The GET request returns a simple success message indicating whether the API is working or not.

Now on to the frontend!

Creating React Components

To display and submit comments, we’ll create two components inside the components/ folder:

Comment Form (components/CommentForm.tsx)

This component contains a form that will allow users to submit comments:

"use client";
import React, { useState } from "react";

interface CommentFormProps {
   blogEntryId: string;
   blogTemplateId: string;
}

const CommentForm = ({ blogEntryId, blogTemplateId }: CommentFormProps) => {
   const [title, setTitle] = useState("");
   const [from, setFrom] = useState("");
   const [commentText, setCommentText] = useState("");

   // Function to generate slug from title
   const generateSlug = (title: string) => {
       return title.toLowerCase().replace(/\s+/g, "-");
   };

   // Handle form submission
   const handleSubmit = async (e: React.FormEvent) => {
       e.preventDefault();

       // Generate slug from title
       const slug = generateSlug(title);

       // Prepare the comment data
       const commentData = {
           title,
           slug,
           from,
           comment_text: commentText,
           blog: {
               entryId: blogEntryId,
               templateId: blogTemplateId,
           },
       };

       // Log the comment data to the console 
       console.log("Comment Data:", commentData);

       try {
           // Send the comment data to the API
           const response = await fetch("/api/comment/create", {
               method: "POST",
               headers: {
                   "Content-Type": "application/json",
               },
               body: JSON.stringify(commentData),
           });

           if (!response.ok) {
               throw new Error("Failed to submit comment");
           }

           const result = await response.json();
           console.log("Comment created successfully:", result);

           // Reset form fields
           setTitle("");
           setFrom("");
           setCommentText("");
           location.reload();
       } catch (error) {
           console.error("Error submitting comment:", error);
       }
   };

   return (
       <div className="mt-20">
           <h3 className="text-xl font-semibold leading-none tracking-[-0.24px] mb-8 md:mb-12 md:text-2xl">
               Add a Comment
           </h3>
           <form onSubmit={handleSubmit} className="space-y-6">
               <div>
                   <label htmlFor="title" className="block text-sm font-medium text-gray-700">
                       Title
                   </label>
                   <input
                       type="text"
                       id="title"
                       value={title}
                       onChange={(e) => setTitle(e.target.value)}
                       className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
                       required
                   />
               </div>
               <div>
                   <label htmlFor="from" className="block text-sm font-medium text-gray-700">
                       Your Name
                   </label>
                   <input
                       type="text"
                       id="from"
                       value={from}
                       onChange={(e) => setFrom(e.target.value)}
                       className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
                       required
                   />
               </div>
               <div>
                   <label htmlFor="comment_text" className="block text-sm font-medium text-gray-700">
                       Comment
                   </label>
                   <textarea
                       id="comment_text"
                       value={commentText}
                       onChange={(e) => setCommentText(e.target.value)}
                       className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
                       rows={4}
                       required
                   />
               </div>
               <div>
                   <button
                       type="submit"
                       className="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
                   >
                       Submit Comment
                   </button>
               </div>
           </form>
       </div>
   );
};

export default CommentForm;

How it works:

  • Uses the useState hook to store title, from (user's name), and commentText.

  • Converts the title into a lowercase, hyphen-separated slug.

  • The handleSubmit gathers input data and constructs a comment object.

  • It then sends a POST request to the API (/api/comment/create).

  • If successful, it resets the form fields and reloads the page to display the comment in the blog post’s comment section.

Comments List (components/CommentsList.tsx)

This component fetches and displays a list of comments related to a specific blog post.

"use client";

import React, { useEffect, useState } from "react";
import type { Comment } from "@/types";

interface CommentsListProps {
   slug: string;
}

const CommentsList = ({ slug }: CommentsListProps) => {
   const [comments, setComments] = useState<Comment[]>([]);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState<string | null>(null);

   useEffect(() => {
       const fetchComments = async () => {
           try {
               const response = await fetch(`${process.env.NEXT_PUBLIC_APP_URL}/api/comment/get-all`, {
                   method: "GET",
                   headers: {
                       "Content-Type": "application/json",
                   },
               });

               if (!response.ok) {
                   throw new Error("Failed to fetch comments");
               }

               const result = await response.json();
               const allComments = result.data as Comment[];
               console.log("All Comments", allComments)

               // Filter comments for the current blog
               const filteredComments = allComments.filter((comment) => {
                   const blogSlug = comment?.blog?.meta?.en?.slug;
                   return blogSlug === slug;
               });

               setComments(filteredComments);
           } catch (error) {
               console.error("Error fetching comments:", error);
               setError("Failed to load comments. Please try again later.");
           } finally {
               setLoading(false);
           }
       };

       fetchComments();
   }, [slug]);

   if (loading) {
       return <div>Loading comments...</div>;
   }

   if (error) {
       return <div className="text-red-500">{error}</div>;
   }

   return (
       <div className="mt-20">
           <h3 className="text-xl font-semibold leading-none tracking-[-0.24px] mb-8 md:mb-12 md:text-2xl">
               Comments
           </h3>
           <div className="grid grid-cols-1 gap-6">
               {comments.map((comment) => (
                   <div
                       key={comment.slug}
                       className="p-6 border border-appGray-200 rounded-lg"
                   >
                       <h4 className="text-lg font-semibold">
                           {comment.title}
                       </h4>
                       <p className="text-sm text-appGray-500">
                           From: {comment.from}
                       </p>
                       <p className="mt-2">{comment.comment_text}</p>
                   </div>
               ))}
           </div>
       </div>
   );
};

export default CommentsList;

How it works:

  • It uses useState to store the list of comments, a loading flag, and an error message.

  • In the useEffect, you make a GET request to /api/comment/get-all to retrieve all comments.

  • It then filters comments based on the blog post's slug.

  • To render the comments, if it is loading, it displays a loading message.

  • If an error occurs, it displays an error message. Otherwise, it maps and displays all relevant comments.

Integrating Components in page.tsx

Finally, you’ll integrate the components inside individual blog posts. 

Add this in the blog/[slug]/page.tsx file above the “Display other blogs” code:

{/* Display Comments */}
  <CommentsList slug={params.slug} />
   
 {/* Comment Form */}
  <CommentForm blogEntryId={blog._id} blogTemplateId={blog.templateId} />

With all these, users should be able to add as many comments to your blog posts and see them displayed on the respective blog page. Go to “http://localhost:3000/” on your browser to see your blog app in action!

Comment system example

Let’s see how it works now:

Comment system example

Feel free to style your blog page and comment form any way you like.

With that, we’ve come to the end of this exciting article, where you learned how to utilize BCMS features to create a blog with comment system app functionality for an interactive and engaging user experience.

Conclusion: Forget Disqus! Build Your Own Comment System!

Implementing a blog comment system with BCMS and Next.js provides a structured and scalable way to manage user interactions in your blog posts. By using BCMS templates and entries, we were able to efficiently store and retrieve comments while ensuring our content organization is flexible.

In this article, I've covered how to:

  • Create a comment using BCMS entries.

  • Retrieve all comments and filter them for specific blog posts.

  • Build a comment form component to allow users to submit their comments.

  • Implement a comments list component to display comments dynamically.

With this setup, you can enhance your blog’s engagement by enabling real-time user feedback while maintaining a clean and structured backend in BCMS.

Now, it's time to integrate this into your blog and to enable users to discuss, share thoughts, and give feedback seamlessly.

Try BCMS and get ready for comments!

It takes a minute to start using BCMS

Gradient

Join our Newsletter

Get all the latest BCMS updates, news and events.

You’re in!

The first mail will be in your inbox next Monday!
Until then, let’s connect on Discord as well:

Join BCMS community on Discord

By submitting this form you consent to us emailing you occasionally about our products and services. You can unsubscribe from emails at any time, and we will never pass your email to third parties.

Gradient