DEV Community

Aswanth Raveendran EK
Aswanth Raveendran EK

Posted on

Render Block component in Next JS and Headless CMS

Introduction

In modern web development, creating dynamic and scalable websites is essential for delivering personalized user experiences. One of the most effective ways to achieve this is by integrating a headless CMS with a frontend framework like Next.js. A headless CMS allows developers to separate the content management layer from the presentation layer, enabling seamless content updates without touching the codebase. This setup is ideal for sites that require frequent content changes, like blogs, portfolios, and e-commerce sites.

The Role of Headless CMS

A headless CMS (Content Management System) is a backend-only system that manages content independently of the frontend. This approach allows developers to select or build a frontend framework without being constrained by the CMS’s native capabilities. Popular headless CMS options includes Strapi, Payload etc.
With a headless CMS, content creators and developers can work in parallel: editors manage content, and developers focus on the presentation layer using tools like Next.js.
NB: You don't need to know more about CMS to create this. Just understand about the APIs your CMS framework provides.

Set Up

Create a component called renderBlocks in your Next JS src folder.

Folder structure

Now create a functional component

import type { Page } from "@/types/block-types/types";
import React, { Fragment } from "react";
import HeroBanner from "./blocks/HeroBanner";
import FormBlock from "./blocks/FormBlock";

const blockComponents = {
    banner: HeroBanner,
    formBlock: FormBlock
};

interface RenderBlocksProps {
  blocks: Page["layout"];
}

export const RenderBlocks: React.FC<RenderBlocksProps> = (props) => {

};
Enter fullscreen mode Exit fullscreen mode

The blockComponents object contain the Components that we created in our project.

The component will accept a prop called blocks which is the API response of the CMS which includes all the data that needs to show in the page.

Below is an example type of blocks

export interface Page {
    layout: (BannerBlock | FormBlock)[];
}

export interface BannerBlock {
    media: {
        src: string;
        alt: string;
    };
    title: string;
    blockType: "banner"
}

export interface FormBlock {
    title: string;
    blockType: "formblock"
}
Enter fullscreen mode Exit fullscreen mode

You can create a file called block-types.ts in the utils folder and create your types for blocks there.
NB: This types can be changed according to the CMS you use and also it depends on the field name that you use in your CMS.

In the blocks props you will have array of objects each object is the data that you want to show in your page. And each object contain a unique property. I mentioned it here as blockType. This blockType should be the same key of the blockComponents object.

Below is a mock data that you are going to get from your CMS API. This will have changes from an actual response. I am just showing it here for better understanding.

{
  layout: [
    {
      image: {
        src: "image.png",
        alt: "Alt text",
      },
      title: "Banner Title",
      blockType: "banner",
    },
    {
      title: "Contact Form",
      blockType: "formBlock"
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Below is the complete code for your renderBlocks component

import type { Page } from "@/types/block-types/types";
import React, { Fragment } from "react";
import HeroBanner from "./blocks/HeroBanner";
import FormBlock from "./blocks/FormBlock";

const blockComponents = {
    banner: HeroBanner,
    formBlock: FormBlock
};

interface RenderBlocksProps {
  blocks: Page["layout"];
}

export const RenderBlocks: React.FC<RenderBlocksProps> = (props) => {
  const { blocks } = props;

  const hasBlocks = blocks && Array.isArray(blocks) && blocks.length > 0;

  if (hasBlocks) {
    return (
      <Fragment>
        {blocks.map((block, index) => {
          const { blockType } = block;

          if (blockType && blockType in blockComponents) {
            const Block = blockComponents[blockType];

            if (Block) {
              return (
                <div key={index}>
                  {/* @ts-expect-error: has no properties in common with type 'IntrinsicAttributes' */}
                  <Block {...block} />
                </div>
              );
            }
          }
          return null;
        })}
      </Fragment>
    );
  }

  return null;
};

Enter fullscreen mode Exit fullscreen mode

How to use this?
Create a page in your app directory and call the render blocks components there with data you fetched from the CMS API.

import { Page } from "@/types/block-types/types";
import { RenderBlocks } from "@/component/RenderBlocks";

const HomePage= async() => {
cons page:Page = await CallToPageDataAPI()
  return (
    <div className="bg-background">
      <RenderBlocks blocks={page.layout} />
    </div>
  );
};

export default HomePage;
Enter fullscreen mode Exit fullscreen mode

The logic presented here is tailored to my specific use case, but feel free to adapt it as needed. If you encounter any issues or have suggestions, please don't hesitate to leave a comment. I'm always open to feedback and improvement. Thank you for taking the time to read this!

NB: A CMS API response will be more complex than what is referred to here. Please make necessary changes to the types according to your use case.

Top comments (0)