Building a Page Builder with Contentful and Astro

November 19, 2025 · 7 min read

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 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:
A Contentful content type (defining structure & fields)
A GraphQL Fragment (defining the data shape)
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.

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-operationsWe 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:
Create a new Space (or use an existing one).
Grab your Space ID and Content Delivery API token (CDA).
Confirm you can reach Contentful’s GraphQL endpoint.

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 codegenThis 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:
Create a new
.astrocomponentDefine a corresponding fragment
Add it to the
COMPONENTSmap
No route files need updating, no additional queries, everything just works.

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:
Creating a new
Pageentry in ContentfulFilling the
titleandslugfieldsAdding and rearranging content blocks via the “builder” reference field
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.

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.

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

Stay up-to-date
Be updated with all news, products and tips we share!
On this page
- Using a Contentful Page Builder
- Why a Contentful Page Builder?
- Architecture overview
- 1. Astro for rendering and speed
- 2. Contentful as a Headless CMS
- 3. GraphQL + Code generation
- 4. Page Builder mechanism
- Installation and initial setup
- Setting up Contentful for the Page Builder
- GraphQL code generation
- Creating the GraphQL fragments
- Assembling the Page Query
- Implementing the Builder component
- Dynamic page generation with Astro
- The editor experience: A dream for marketing teams
- Developers stay focused on scalability
- Real-world lessons learned
- Common pitfalls
- The results
- Conclusion
- Bring Your Ideas to Life 🚀
- FAQs

