Creating a Dynamic Templating System in Next.js and Tailwind

In this tutorial, we'll demonstrate how to build a dynamic templating system for a landing page using Next.js. We'll fetch data from a sample API and render corresponding sections based on that data.

Setting Up the API

First, let's set up a mock API to provide dynamic templating data.

Below is the get API which will provide dynamic templating data. This data is generally maintained in databases which are generally configured or inserted through the admin panel by the admin or is simply created using seeders.

//src/pages/api/get-template.ts
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";

type Data = {
  sectionId: number;
  sectionName: string;
  bgColor: string;
  color: string;
  position: number;
};

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data[]>
) {
  setTimeout(() => {
    res.status(200).json([
      {
        position: 6,
        sectionId: 1,
        sectionName: "Section One",
        bgColor: "red",
        color: "white",
      },
      {
        position: 3,
        sectionId: 2,
        sectionName: "Section Two",
        bgColor: "green",
        color: "white",
      },
      {
        position: 5,
        sectionId: 3,
        sectionName: "Section Three",
        bgColor: "blue",
        color: "white",
      },
      {
        position: 2,
        sectionId: 4,
        sectionName: "Section Four",
        bgColor: "orange",
        color: "white",
      },
      {
        position: 1,
        sectionId: 5,
        sectionName: "Section Five",
        bgColor: "white",
        color: "black",
      },
      {
        position: 4,
        sectionId: 6,
        sectionName: "Section Six",
        bgColor: "violet",
        color: "white",
      },
      {
        position: 8,
        sectionId: 7,
        sectionName: "Section Seven",
        bgColor: "indigo",
        color: "white",
      },
      {
        position: 9,
        sectionId: 8,
        sectionName: "Section Eight",
        bgColor: "amber",
        color: "black",
      },
      {
        position: 10,
        sectionId: 9,
        sectionName: "Section Nine",
        bgColor: "yellow",
        color: "black",
      },
      {
        position: 7,
        sectionId: 10,
        sectionName: "Section Ten",
        bgColor: "green",
        color: "black",
      },
    ]);
  }, 2000);
}

Section Components

Next, we'll create components for each section that will be rendered on the landing page.

Section Component One

// src/components/SectionComponent.jsx
import React from "react";

const SectionComponent = ({ item }) => {
  return (
    <div
      className="h-[30vh] text-2xl flex items-center justify-center flex-col"
      style={{
        color: item.color,
        background: item.bgColor,
        border: "2px solid black",
      }}
    >
      <h3>{item.sectionName || "ONE"}</h3>
      <p>This section one</p>
    </div>
  );
};

export default SectionComponent;

Section Component Two

// src/components/SectionComponentTwo.jsx
import React from "react";

const SectionComponentTwo = ({ item }) => {
  return (
    <div
      className="h-[30vh] text-2xl flex items-center justify-center flex-col"
      style={{
        color: item.color,
        background: item.bgColor,
        border: "2px solid black",
      }}
    >
      <h3>{item.sectionName || "TWO"}</h3>
      <p>This section two</p>
    </div>
  );
};

export default SectionComponentTwo;

(Repeat for other section components)

Similarly, you can split your landing page code into different components for different sections whose ordering and title can be decided by admin by providing sequencing or positioning of these sections.

Building the Landing Page

Now, let's create the landing page where we'll fetch data from the API and render the corresponding sections dynamically.

// src/pages/index.tsx

import { Fragment, useEffect, useState } from "react";
import SectionComponent from "../components/SectionComponent";
import SectionComponentTwo from "../components/SectionComponentTwo";
import SectionComponentThree from "../components/SectionComponentThree";
import SectionComponentFour from "../components/SectionComponentFour";
import SectionComponentFive from "../components/SectionComponentFive";
import SectionComponentSix from "../components/SectionComponentSix";
import SectionComponentSeven from "../components/SectionComponentSeven";
import SectionComponentEight from "../components/SectionComponentEight";
import SectionComponentNine from "../components/SectionComponentNine";
import SectionComponentTen from "../components/SectionComponentTen";

export default function Home() {
  const [templateData, setTemplateData] = useState<any>([]);
  const [loader, setLoader] = useState(false);
  const TEMPLATE_CONSTANT: any = {
    //1 is the value of sectionId. 
    //Here we map different sections with their sectionId.
    1: { COMP_NAME: SectionComponent },
    2: { COMP_NAME: SectionComponentTwo },
    3: { COMP_NAME: SectionComponentThree },
    4: { COMP_NAME: SectionComponentFour },
    5: { COMP_NAME: SectionComponentFive },
    6: { COMP_NAME: SectionComponentSix },
    7: { COMP_NAME: SectionComponentSeven },
    8: { COMP_NAME: SectionComponentEight },
    9: { COMP_NAME: SectionComponentNine },
    10: { COMP_NAME: SectionComponentTen },
  };
  useEffect(() => {
    getLayout();
  }, []);

  const getLayout = async () => {
    setLoader(true);
    const response = await fetch("http://localhost:3000/api/get-template");
    const result: any = (await response.json()) || [];
    setTemplateData(
      !!result.length
        ? result.sort(
            (a: any, b: any) => Number(a.position) - Number(b.position)
          )
        : result
    );
    setLoader(false);
  };

  const getSectionToRender = (item: any) => {
    const SectionComp = TEMPLATE_CONSTANT[item.sectionId]["COMP_NAME"];
    return <SectionComp item={item} />;
  };

  return (
    <div>
      {loader && (
        <div className="h-[100vh] text-4xl flex items-center justify-center">
          Loading...
        </div>
      )}
      {!loader &&
        !!templateData.length &&
        templateData.map((item: any) => (
          <Fragment key={item.sectionId}>{getSectionToRender(item)}</Fragment>
        ))}
      {/* this will map sectionIds */}
      {!loader &&
        !!!templateData.length &&
        Object.keys(TEMPLATE_CONSTANT).map((item: any) => (
          <Fragment key={item}>
            {getSectionToRender({ sectionId: item })}
          </Fragment>
        ))}
    </div>
  );
}

In this file, we fetch data from the API using useEffect and render the appropriate section components based on the fetched data.

Conclusion

Creating a dynamic templating system in Next.js allows for a flexible and customizable landing page. By fetching data from an API and rendering components accordingly, you can tailor the content and layout of your landing page to suit your needs.

Feel free to extend this concept by adding more sections and customizations to enhance the overall user experience.

Happy coding!