Building a Modern Blog with NextJS and BCMS: A Step-by-Step Guide

Building a Modern Blog with NextJS and BCMS.jpeg
By Nnaemeka Eze
Read time 4 min
Posted on 14 Mar 2025

Are you frustrated with slow, outdated CMSs that make development a headache? You’re not alone. Many developers and businesses waste hours wrestling with WordPress limitations, fixing security issues, and making compromises just to get things working.

But what if you could have speed, flexibility, and control without the hassle? That’s where NextJS and BCMS come in.

Why make a blog with Nextjs and a headless CMS

Next.js is a powerful React framework that helps developers build high-performance web applications. Its features include Server-Side Rendering (SSR), Static Site Generation (SSG), and automatic code splitting. These enable websites to load faster, rank better on search engines, and deliver dynamic content efficiently.

On the other hand, BCMS is a headless CMS, which means it separates content storage from the front end. Unlike traditional CMSs, a headless CMS provides content through APIs, making it accessible to multiple platforms (web, mobile, etc.). This gives developers the freedom to create custom user interfaces while allowing content managers to update content seamlessly.

Benefits of a Headless CMS for blog site

A headless CMS decouples the backend from the front-end presentation. Instead of being tied to a specific website design or framework, it delivers content via APIs. This allows developers to build highly customized and flexible frontends using any technology, such as Next.js, React, Vue, or mobile frameworks.

By using a headless CMS for NextJS, businesses can:

  • Improve performance: A headless CMS delivers content via APIs, allowing developers to use SSG and CDNs for faster load times. Unlike traditional CMSs, which fetch data on every request, a headless CMS enables pre-cached content delivery, improving speed, user experience, and SEO.

  • Enhance security: Since the backend operates independently, the CMS isn’t exposed to public traffic, reducing risks like SQL injections, brute-force attacks, and plugin vulnerabilities. Content is accessed via secure APIs, making the system more resilient against cyber threats.

  • Increase flexibility for scalable and custom digital experiences: Developers can use Next.js, React, Vue, or mobile frameworks without being tied to a specific CMS front-end. This allows custom UI design, seamless third-party integrations, and effortless scaling, making it easier to expand and adapt.

Benefits of Building a Blog with Nextjs

In modern web development, speed, SEO, and scalability are important for delivering a great user experience. While React is a powerful front-end library, it lacks built-in features for server-side rendering, static generation, and backend API handling. This is where Next.js comes in.

Next.js enhances React by providing:

  • Server-Side Rendering (SSR): Generates pages on demand for dynamic content and better SEO.

  • Static Site Generation (SSG): Pre-builds pages for lightning-fast performance.

  • API Routes: Allows backend functionality without a separate server.

Prerequisites for NextJS blog tutorial

Before I dive into building a blog with BCMS, here’s what you’ll need:

  • A basic understanding of React, React components, hooks, and Typescript.

  • Node.js and npm installed.

  • A BCMS Account (Free Tier or Trial). Sign up at BCMS to manage content through its headless CMS API.

Setting Up BCMS

Getting started with NextJS and BCMS is a straightforward process. The NextJS project creation tool can be used to create a sample blog project to help you get started. To create a new BCMS and NextJS project, execute the following command from your terminal:

npx @thebcms/cli create next fruit-blog

Running this command will set up a Next.js project, create the blog starter, and link it to a BCMS instance.

You’ll need to log in through your browser. If you don’t have a BCMS account yet, you can create one by following the prompts.

Once you’re in, the command will ask for a project name and then complete the setup, as shown in the image below:

project name

 Next, I’d move into the project folder, install dependencies, and start the development server:

  • cd fruit-blog

  • npm install

  • npm run dev

nextjs blog

Setting up the BCMS content modeling

Let’s jump into BCMS, open the dashboard, and first check that the project I created has been selected.

Fruit blog project

Next, I’ll remove all the existing templates and entries from the blog starter to make room for our own.

To do this, go to Templates, which is located on the sidebar, and then select any existing templates.

BCMS templates

On the top right corner of the page, click Edit Template, then delete the template. Repeat this step until all Templates are deleted.

5. edit template.png

Let’s create a new template now. Click on the Create new template and click the Create button.

working templates

The blog template is going to contain keys like:

  • title → String

  • slug → String

  • createdAt → Date

  • description → Rich Text

  • author → String

  • tags → String Array

  • fruitImage → Media

Fruit blog template

After creating a template, I can start adding actual blog content by creating a new blog entry in BCMS.

Blog entry

Since I created a template Fruit_blog, it now appears in the Entries section, allowing me to add content based on that template.

To create a new entry, click on Create new entry at the top right of the screen, fill out the available form fields, and click the Create button.

blog entry example

Here’s the list of entries I’ve created so far:

List of blog entries

Before fetching data from BCMS, I need to configure our environment variables. To do this, go to the Settings section on the sidebar and click on API keys.

API keys settings

I’d be creating a new API key. Click the Add new key button, enter any name of your choice, and click Add key to create. Once it’s created, your API key will include the following fields:

New API key

Also, check all the Template Permissions that allow you to perform CRUD operations using the API I just created.   

Template Permissions

Fetching BCMS Content via APIs

Now that I’ve created and published entries in BCMS, the next step is to fetch this content into the Next.js application using BCMS APIs. This allows me to dynamically display our blog posts on the front end.

Let’s start by setting up the environmental variables in the NextJs project.

Head over to the .env file located at  /thebcms_projects/fruit-blog/.env  and match the API key into their respective variables: 

  • BCMS_ORG_ID=***

  • BCMS_INSTANCE_ID=***

  • BCMS_API_KEY_ID=***

  • BCMS_API_KEY_SECRET=***

To retrieve BCMS data in the Next.js application, open the src/app/page.tsx file and import the auto-generated types provided by BCMS. 

At the top of the file, I’ll import the necessary modules:

import React from 'react';
import { bcms } from './bcms-client';
import { Metadata } from 'next';
import BlogCard from '@/components/blog/Card';
import { FruitBlogEntry } from '@bcms-types/types/ts';
import { notFound } from 'next/navigation';

Next, I’d define a pageTitle and use Next.js metadata API to improve search engine visibility:

const pageTitle = 'Fruit Blog';
export const metadata: Metadata = {
    title: pageTitle,
    openGraph: {
        title: pageTitle,
    },
    twitter: {
        title: pageTitle,
    },
};

In BCMS integration, I retrieve all "fruit-blog" entries using bcms.entry.getAll(). It applies TypeScript type assertion (FruitBlogEntry[]) to ensure the data aligns with the CMS content model.

const HomePage: React.FC = async () => {
    const fruit_blogs = (await bcms.entry.getAll(
        'fruit-blog',
    )) as FruitBlogEntry[];
    if (!fruit_blogs) return notFound();

Next, let’s style page.tsx  and improve the user interface.

import React from 'react';
import { bcms } from './bcms-client';
import { Metadata } from 'next';
import BlogCard from '@/components/blog/Card';
import { FruitBlogEntry } from '@bcms-types/types/ts';
import { notFound } from 'next/navigation';
const pageTitle = 'Fruit Blog';
export const metadata: Metadata = {
    title: pageTitle,
    openGraph: {
        title: pageTitle,
    },
    twitter: {
        title: pageTitle,
    },
};
const HomePage: React.FC = async () => {
    const fruit_blogs = (await bcms.entry.getAll(
        'fruit-blog',
    )) as FruitBlogEntry[];
    if (!fruit_blogs) return notFound();
    const items = fruit_blogs.map((blog) => {
        return blog.meta.en;
    });
    return (
        <div className="py-24 md:py-32">
            <div className="container">
                <div className="flex flex-col gap-6 items-center text-center mb-20 md:mb-[120px]">
                    <h2 className="text-5xl font-bold">
                        Let's talk about fruits
                    </h2>
                </div>
                <div>
                    <div className="grid grid-cols-4 gap-12 max-w-[1040px] mx-auto">
                        {items.map((item, index) => {
                            return <BlogCard key={index} blog={item!} />;
                        })}
                    </div>
                </div>
            </div>
        </div>
    );
};
export default HomePage;

Here’s the BlogCard.tsx component, which I’ve rendered:

import React from 'react';
import Link from 'next/link';
import { BCMSImage } from '@thebcms/components-react';
import { bcms } from '@/app/bcms-client';
import { FruitBlogEntryMetaItem } from '@bcms-types/types/ts/entry/fruit-blog';
interface Props {
    blog: FruitBlogEntryMetaItem;
}
const BlogCard = ({ blog }: Props) => {
    const fruit_tags = blog.tags?.map((tag, index) => {
        return (
            <div
                className="bg-black text-white text-xs rounded-md  py-2 px-4 flex justify-center"
                key={index}
            >
                {tag}
            </div>
        );
    });
    return (
        <div className="border rounded-lg">
            <Link className="w-full" href={`/blog/${blog.slug}`}>
                <div>
                    <div className=" w-[200px] h-[200px]">
                        <BCMSImage
                            clientConfig={bcms.getConfig()}
                            media={blog.fruitimage}
                            className="size-full object-cover object-center"
                        />
                    </div>
                    <div className="p-2">
                        <div className="flex items-center gap-2 flex-wrap">
                            {fruit_tags}
                        </div>
                        <div className="flex gap-2 mt-2">
                            <p>By</p>
                            <h4 className="font-semibold">{blog.author}</h4>
                        </div>
                        <div className="mt-2">
                            <span
                                className="line-clamp-3"
                                dangerouslySetInnerHTML={{
                                    __html: blog.description.nodes[0].value,
                                }}
                            />
                        </div>
                    </div>
                </div>
            </Link>
        </div>
    );
};
export default BlogCard;

Here’s a preview of what our user interface looks like, Next js blog example:

BCMS and NextJS blog example

Dynamic Routing

I’ve finally created our homepage. Let’s now create a post for each individual blog post.

import React from 'react';
import Link from 'next/link';
import { bcms } from '@/app/bcms-client';
import { notFound } from 'next/navigation';
import { FruitBlogEntry } from '@bcms-types/types/ts';
import { BCMSImage } from '@thebcms/components-react/image';
import { ArrowLeft } from 'lucide-react';
import { toReadableDate } from '@/utils/date';
type Props = {
    params: {
        slug: string;
    };
};
export async function generateStaticParams() {
    const blogs = (await bcms.entry.getAll('fruit-blog')) as FruitBlogEntry[];
    return blogs.map((blog) => ({
        slug: blog.meta.en?.slug,
    }));
}
const BlogPage: React.FC<Props> = async ({ params }) => {
    const blogs = (await bcms.entry.getAll('fruit-blog')) as FruitBlogEntry[];
    const blog = blogs.find((e) => e.meta.en?.slug === params.slug);
    if (!blog) {
        return notFound();
    }
    return (
        <div className="py-15 md:py-20">
            <div className="container">
                <Link
                    href="/"
                    className="border border-appGray-200 bg-appGray-100 flex w-fit leading-none px-3 py-2 text-xl font-medium rounded-lg transition-colors duration-300 hover:bg-appGray-200 focus-visible:bg-appGray-200 mb-10 md:mb-10 md:px-5 md:py-4 md:text-2xl"
                >
                    <ArrowLeft />
                </Link>
                <div className="flex justify-center">
                    <div className="aspect-[1.25] w-[400px] h-[400px]">
                        <BCMSImage
                            clientConfig={bcms.getConfig()}
                            media={blog.meta.en?.fruitimage!}
                            className="size-full object-cover transition-transform duration-500 object-center group-hover:scale-105 group-focus-visible:scale-105"
                        />
                    </div>
                </div>
                <div>
                    <div className="mb-5 md:mb-5">
                        <h1 className="text-3xl font-semibold leading-none md:text-[40px]">
                            {blog.meta.en?.title}
                        </h1>
                    </div>
                </div>
                <div className="my-3 flex justify-between">
                    <p>
                        Created by{' '}
                        <span className="font-bold">
                            {blog.meta.en?.author}{' '}
                        </span>
                    </p>
                    <p>{toReadableDate(blog.meta.en?.createdat!)}</p>
                </div>
                <div>
                    <span
                        dangerouslySetInnerHTML={{
                            __html: blog.meta.en!.description.nodes[0].value.toString(),
                        }}
                    />
                </div>
            </div>
        </div>
    );
};
export default BlogPage;

The snippet generates static parameters for each “fruit-blog” entry, giving each post a unique route.

It then fetches the matching post by slug and neatly displays the image, title, author, and date.

For RTF, the content is inserted directly as HTML.

Conclusion: Build the best blog with NextJS and BCMS

In this article, I explored how to integrate BCMS with a Next.js application, from setting up a project and managing content to fetching and displaying data dynamically. By leveraging BCMS’s API, I created a structured and scalable blog website while keeping content management flexible.

With this foundation, you can now customize and expand your project, whether by adding more content types, enhancing styling, or implementing advanced features.

The combination of Next.js and BCMS offers a powerful way to build modern, content-driven web applications with ease. To learn more about Next.js and BCMS, you can visit the Next js blog starter toolkit and view or contribute to our fruit-blog project.

Happy Coding! 🚀

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