Building a Page Builder with Contentful and Astro

Lokman Musliu Founder and CEO of Lucky Media
Lokman Musliu

November 19, 2025 · 7 min read

Building a Page Builder with Astro and Contentful

If there’s one universal truth in every web project, it’s this: marketing teams want to move fast, often faster than developers can keep up. New campaigns, refreshed landing pages, seasonal promotions; they all need to go live yesterday.

As an agency that’s worked with a wide range of clients running sites that had Contentful as their CMS, we’ve seen this story play out dozens of times. Developers get bogged down updating layouts or duplicating code, while marketing teams wait for tickets to move through the pipeline.

Using a Contentful Page Builder

This architecture bridges the gap between content creators and developers. It gives marketers complete autonomy to create new pages while keeping the system performant, maintainable, and type-safe.

By the end of this article, you’ll understand how to:

  • Build a Contentful Page Builder that puts your marketing team in control

  • Integrate it with Astro and GraphQL Codegen for type safety

  • Create a scalable component system that grows with your site

  • Avoid the classic “developer bottleneck” problem

Why a CMS Page Builder

Why a Contentful Page Builder?

Traditional CMS setups often rely on templates or hard-coded layouts, great for consistency, terrible for agility. Every new page requires developer involvement to modify layouts or clone components.

A Page Builder, however, flips that model entirely. Instead of rigid templates, you can build your site with reusable content blocks, including "Hero Banners, Feature Grids, CTAs, Galleries, and Testimonials".

Marketing users can stack and reorder these blocks inside Contentful’s editorial UI, creating infinite layout possibilities without touching code.

For developers, each block corresponds to:

  1. A Contentful content type (defining structure & fields)

  2. A GraphQL Fragment (defining the data shape)

  3. An Astro component (defining how it renders)

When you connect these dots, you get a modular, type-safe system that satisfies marketing agility and developer sanity.

Page Builder Architecture overview

Architecture overview

Let’s outline the structure that makes this possible.

1. Astro for rendering and speed

Astro compiles pages to static HTML and hydrates only interactive components. This makes your Contentful site load lightning-fast.

2. Contentful as a Headless CMS

Contentful gives editors a familiar UI to assemble pages from reusable blocks. Each block has its own content model.

3. GraphQL + Code generation

Contentful’s GraphQL endpoint, combined with graphql-codegen ensures your site always fetches exactly the data you need. No missing fields, no runtime surprises.

4. Page Builder mechanism

All pages share a unified template that iterates over their assigned “blocks,” dynamically rendering components based on each block's type.

The marketing team builds pages from Lego-like sections; developers build the Legos; marketers build the castles.

Installation and initial setup

Before writing any code, let’s get our environment ready. This is where most tutorials skip ahead, but we’re going to make sure everything is properly installed and configured so the rest actually works.

# GraphQL essentials
npm install graphql graphql-request

# GraphQL Codegen tooling
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations

We assume you already have an Astro project up and running!

Environment variables

In the project root, create a .env file to store your Contentful credentials (you’ll get these from Contentful shortly).

GRAPHQL_API_URL=
CONTENTFUL_PREVIEW_TOKEN=
CONTENTFUL_DELIVERY_TOKEN=

Connect to Contentful

You’ll need an active Contentful account. Once logged in:

  1. Create a new Space (or use an existing one).

  2. Grab your Space ID and Content Delivery API token (CDA).

  3. Confirm you can reach Contentful’s GraphQL endpoint.

Contenful logo

Setting up Contentful for the Page Builder

Now that the local environment is ready, let’s structure Contentful for our Page Builder.

Step 1: The page content type

Create a Content Type called Page with:

  • title (short text)

  • slug (short text)

  • builder (reference field, allowing multiple entries, referencing your block types)

Step 2: Block content types

Next, create one content type per block, for example:

  • Hero Section

  • Logo Cloud

  • Features

  • Testimonials

Each of these will have only the fields that the component needs.

Example: Hero Section might have:

  • Title (text)

  • Description (rich text)

  • Image (media)

  • CTA (reference to Cta model)

This granular structuring keeps everything modular.

GraphQL code generation

To enjoy full type safety, we automate type creation using GraphQL Codegen.

// codegen.ts

import type { CodegenConfig } from "@graphql-codegen/cli";
import { loadEnv } from "vite";

const { GRAPHQL_API_URL, CONTENTFUL_DELIVERY_TOKEN } = loadEnv(
  process.env.NODE_ENV || "development",
  process.cwd(),
  "",
);

const config: CodegenConfig = {
  schema: {
    [GRAPHQL_API_URL || ""]: {
      headers: {
        Authorization: `Bearer ${CONTENTFUL_DELIVERY_TOKEN}`,
      },
    },
  },
  documents: ["src/**/*.graphql"],
  ignoreNoDocuments: true,
  generates: {
    "./src/generated/graphql.ts": {
      plugins: ["typescript", "typescript-operations", "typescript-graphql-request"],
      config: {
        preset: "client",
        rawRequest: true,
        useTypeImports: true,
        strictScalars: false,
      },
    },
  },
  config: {
    skipDocumentsValidation: true,
  },
  hooks: {
    afterAllFileWrite: ["prettier --write"],
  },
};

export default config;

Now, instead of guessing data types, your IDE will autocomplete field names from the Contentful schema.

Run:

npm run codegen

This generates a rock-solid contract between frontend and CMS.

Creating the GraphQL fragments

Each block gets its own GraphQL fragment file. For example:

# Hero Section
fragment HeroSection on HeroSection {
  __typename
  _id
  title
  isCenter
  description {
    json
  }
  image {
    ...Image
  }
  ctaLink {
    ...Cta
  }
}

Each fragment is self-contained and reusable.

When you run codegen, TypeScript types like HeroSectionFragment and LogoCloudFragment are automatically generated.

Assembling the Page Query

With your fragments ready, build a single query to fetch all pages and their composed blocks:

query pages($preview: Boolean) {
  pageCollection(preview: $preview, limit: 20) {
    items {
      title
      slug
      builderCollection(limit: 20) {
        items {
          ...HeroSection
          ...LogoCloud
          ...ServiceSection
        }
      }
    }
  }
}

The elegance here is in the modularity. Adding a new block only means adding its fragment, the rest of the system doesn’t change.

Implementing the Builder component

Here’s where the magic happens.

The builder takes CMS-delivered blocks and figures out which Astro component to render based on __typename.

---
import type { Maybe, PageBuilderCollection } from "@/generated/graphql";
import HeroSection from "./blocks/HeroSection.astro";
import LogoCloud from "./blocks/LogoCloud.astro";
import ServiceSection from "./blocks/ServiceSection.astro";

const BLOCK_MAP = {
  HeroSection,
  LogoCloud,
  ServiceSection,
};

function renderBlock(block: Maybe<PageBuilderCollection>) {
  if (!block || !block.__typename || !BLOCK_MAP[block.__typename as keyof typeof BLOCK_MAP]) {
    console.warn("Missing block type on page:", Astro.params.uri);
    return null;
  }

  const template = BLOCK_MAP[block.__typename as keyof typeof BLOCK_MAP];

  return template;
}

const { block } = Astro.props as { block: Maybe<PageBuilderCollection> };

const Template = renderBlock(block) ?? Fragment;
---

<Template block={block} />

Now, no matter which blocks the marketer adds in Contentful, the builder will know exactly how to render them.

To add a new block, developers only:

  1. Create a new .astro component

  2. Define a corresponding fragment

  3. Add it to the COMPONENTS map

No route files need updating, no additional queries, everything just works.

Astro logo

Dynamic page generation with Astro

We’ll create a catch-all page route to resolve any slug the marketing team creates.

<!-- src/pages/[...slug].astro -->
---
import { isDev, sdk } from "@/utils/client";
import Layout from "@/layouts/Layout.astro";
import Builder from "@/components/Builder.astro";

export async function getStaticPaths() {
  const { data } = await sdk.pages({
    preview: isDev,
  });

  if (!data?.pageCollection?.items || data.pageCollection.items.length === 0) {
    return [];
  }

  return data.pageCollection.items.map((page) => {
    const slug = page?.slug === "home" ? undefined : page?.slug;

    return {
      params: {
        slug,
      },
      props: {
        page,
      },
    };
  });
}

const { page } = Astro.props;
---

<Layout>
  <div>
    {page?.builderCollection?.items.map((block) => <Builder block={block} />)}
  </div>
</Layout>

When editors create a new Page entry in Contentful, Astro will generate a static path automatically during the build.

The editor experience: A dream for marketing teams

From an editor’s perspective, creating a new page is as easy as:

  1. Creating a new Page entry in Contentful

  2. Filling the title and slug fields

  3. Adding and rearranging content blocks via the “builder” reference field

  4. Hitting Publish

There’s no need to contact developers, push code, or duplicate templates. Content teams can experiment freely, building landing pages, campaign microsites, or product pages on their own terms.

Developers stay focused on scalability

While marketing teams enjoy creative freedom, developers get a clean, modular, and maintainable codebase.

Each new request, such as “Can we have a testimonials slider?”, simply becomes a new block. No new route logic, no messy template merges.

Over time, this approach leads to a library of well-tested, reusable components that can be combined to build anything.

CMS Page Builder Tips

Real-world lessons learned

Here are insights we’ve learned building multiple page builders for clients:

  • Naming is everything. Make sure CMS field names match component props intuitively.

  • __typename is mandatory. Without it, the builder can’t map blocks correctly.

  • Treat content types like interfaces. Keep them minimal and focused.

  • Encourage preview environments. Marketing should see drafts before publishing; use Contentful’s preview token for dev mode.

  • Document your blocks. Maintain a Notion or Storybook reference with screenshots and example usage for editors.

When developers and editors speak the same language (literally and figuratively), projects move faster, quality improves, and releases become painless.

Common pitfalls

  • Forgetting to re-run codegen after schema changes

  • Overloading blocks with too many fields (“mega-blocks”)

  • Tight-coupling data and presentation: keep components dumb and data external

  • Omitting Contentful references in the Page Builder model

  • Ignoring design consistency: enforce shared styling tokens

The results

After rolling out this pattern for multiple clients, here’s what we see every time:

  • Faster turnaround times: marketing teams can create and launch pages independently

  • Fewer dev bottlenecks: developers no longer handle trivial page updates

  • Happier teams: marketing gets agility, devs get cleaner code

  • Consistent design systems: controlled via reusable components

And because we use Astro for static site generation, performance remains exceptional, often scoring 95+ in Lighthouse without extra optimization.

Astro Partner Agency

Conclusion

A well-designed Page Builder marries freedom and control:

  • Marketing controls content and page structure

  • Developers control functionality, performance, and design framework

  • The codebase remains elegant and scalable

As an agency that has iterated this pattern across several Contentful projects, we can confidently say:

Astro + Contentful + Page Builder is the sweet spot for teams that value both autonomy and reliability.

If you’re building a new project or refactoring an existing Contentful site, bake the Page Builder pattern in from day one. Your marketing team (and your dev backlog) will thank you later.


Bring Your Ideas to Life 🚀

If you need help with an Astro project, let's get in touch.

Lucky Media is proud to be recognized as a leading Astro partner agency.

FAQs

1. How does a Page Builder differ from traditional page templates?

A Page Builder offers a more adaptable and user-friendly alternative to traditional page templates. Unlike fixed templates that require developer intervention for any changes, a Page Builder empowers content editors to assemble pages using reusable content blocks, such as "Hero Banners" and "Feature Grids," directly through a CMS like Contentful.

2. Won’t marketers break the design by rearranging blocks?

Design integrity is maintained in a Page Builder setup through the use of pre-styled components and strict design tokens. Each content block is carefully styled to ensure visual consistency and meets predefined design specifications. This controlled flexibility allows marketers to rearrange blocks without risking design inconsistency, offering them the freedom to customize pages while upholding the brand's visual standards and aesthetic guidelines.

3. What happens when we add a new block type?

Adding a new block type is streamlined within the Page Builder architecture: developers create a corresponding Contentful content type, define a GraphQL fragment for data retrieval, and build an Astro component for rendering. This modular setup allows the new block type to be integrated seamlessly into existing pages. Marketers can immediately start using the new block across the site, using its functionality without needing to update any overarching route logic or rebuild the site’s structure.

4. Is this approach suitable for large enterprise sites?

Yes, the Page Builder approach is highly scalable and well-suited for large enterprise sites with extensive content needs. We have successfully deployed this architecture on websites with over 100 pages and content types. Contentful's user-friendly editor interface scales efficiently, especially when content blocks are clearly named and organized. This scalability ensures that as content demands grow, the system remains manageable and responsive, providing both a robust infrastructure and flexible content management capabilities.

5. Why use Astro instead of Next.js or Gatsby?

Astro stands out with its partial hydration capability and static-first approach, making it particularly efficient for content-heavy sites that require speed and simplicity. Unlike Next.js or Gatsby, Astro compiles pages to static HTML while hydrating only essential components, optimizing load times and performance. This methodology aligns with the needs of high-performance sites where a fast-loading experience is crucial for search engine rankings.

Technologies

AstroContentful
Lokman Musliu Founder and CEO of Lucky Media
Lokman Musliu

Founder and CEO of Lucky Media

Stay up-to-date

Be updated with all news, products and tips we share!

Let’s chat

We partner with a limited number of brands each quarter to ensure senior-level attention on every project.

lokman and arlind headshots
Teamwork