Back to all posts

Building a Dynamic Website with Strapi and Next.js

Nov 10, 2025
4 min read
blog post cover

Building a Dynamic Website: The Strapi & Next.js Approach

In today's web development landscape, building a flexible, dynamic website is a key requirement for many projects. From personal portfolios to large-scale enterprise sites, the ability to manage content from a centralized place while ensuring optimal performance is crucial. This is where a headless CMS like Strapi paired with a powerful frontend framework like Next.js truly shines.

My personal portfolio site is built on this exact stack, and I'd like to share a pattern I've implemented to make it not only easy to manage but also incredibly fast. The core idea is to build a dynamic content builder that uses lazy loading to only render the necessary components, on demand.

The Foundation: Headless Content Management

First, let's talk about the backend. Using Strapi, I define content "blocks" that are reusable across my site. Instead of hard-coding pages, I create a flexible schema where a "page" is simply a collection of these blocks. For instance, my homepage might consist of a `hero` block, followed by an experience-timeline block, and a projects block.

{
  "data": {
    "blocks": [
      {
        "__component": "block.hero",
        "title": "Welcome to my portfolio!",
        "subtitle": "I build amazing web applications."
      },
      {
        "__component": "block.projects",
        "featured": ["Project A", "Project B"]
      }
    ]
  }
}

This structure gives me immense flexibility. I can reorder blocks, add new ones, or remove old ones—all from the Strapi dashboard—without ever touching the frontend code.

The Frontend: A Dynamic Content Builder

On the Next.js side, my main page component is surprisingly simple. It fetches the page data from Strapi, gets the list of blocks, and then iterates over them, passing each block's data to a builder function.

import { getPageData } from "@/data/page";
import contentBuilder from "@/utils/content-builder";
import { BlockType } from "@/types/block";
import { NotFound } from "next/navigation";

export default async function Page() {
  const pageContent = await getPageData();

  if (!pageContent) return NotFound();

  const contentBlocks = pageContent.data.blocks;

  return ( 
   <>
      {contentBlocks.map((block: BlockType) => contentBuilder(block))}
    </>
  );
}

The real magic happens inside the content-builder utility. This function dynamically imports and renders the correct React component for each block type. The key here is using next/dynamic to achieve lazy loading.

import { BlockType } from "@/types/block";
import dynamic from "next/dynamic";
import { ComponentType } from "react";
const dynamicComponents: Record<string, ComponentType<any>> = {
  "block.hero": dynamic(() => import("@/components/blocks/HeroBlock")),
  "block.experience-timeline": dynamic(
    () => import("@/components/blocks/ExperienceTimeline")
  ),
  "block.projects": dynamic(() => import("@/components/blocks/ProjectsGrid")),
};
const contentBuilder = (block: BlockType) => {
  const Component = dynamicComponents[block.__component];
  if (!Component) {
    console.error(`Component not found for type: ${block.__component}`);
    return null;
  }
  // A unique key is essential for React's reconciliation process
  const blockKey = `${block.__component}-${Math.random().toString(36).substring(2, 8)}`;
  return <Component {...block} key={blockKey} />;
};
export default contentBuilder;

With this pattern, the initial JavaScript bundle for my homepage is minimal. It doesn't include the code for the ExperienceTimeline or ProjectsGrid components until they are actually needed, which dramatically improves page load times. This not only provides a better user experience but also gets a thumbs-up from performance tools like Google Lighthouse.

Why This Approach Works

This architecture provides the best of both worlds: the flexibility of a CMS-driven site with the performance benefits of a highly optimized Next.js application.

If you're looking to build a flexible, performant, and easy-to-manage website, this is a pattern worth exploring. It allows you to focus on creating great components while empowering content creators to build pages on their own.