What I Learned Building a Page Builder in Contentful
by Allan Hillman, AI Engineer & Solution Architect
A page builder is a deal you make with your future self. Editors get to compose pages from blocks without touching code, and in return you agree to handle every shape those blocks can take, including the ones nobody meant to create. I built one for this site on Contentful and Next.js, and most of the engineering was not the blocks. It was the seams between a freeform content model and a type-safe render path.
The model: a page is an ordered list of sections
A page entry holds metadata and an ordered list of section references. Each section is its own content type: a hero, a stats row, a values grid, a set of process phases, a call to action. The section types are small and composable, and they are reused across pages, so the home page and the about page draw from the same vocabulary. Two things stay modular on purpose: the author of a post and the SEO metadata are their own entries, linked in rather than copied, so a single Person or SEO record powers many pages.
The seam that bites: GraphQL unions and drift
The section list is a union in GraphQL, which means the query has to spell out the fields for every member type. That is also where a page builder blows past query-complexity limits if you are not careful, so the list queries stay lean and only the detail views pull the heavy fields. Every payload runs through a validation boundary before render. The union is the most drift-prone part of the whole system, so it is the part I trust the least and validate the hardest.
Dispatch, and the unknown section
Rendering is a dispatch table: map each section by its type name to a component. The rule that matters is what happens for a type the code does not recognize, because an editor will eventually add a section a deploy does not know about yet. The renderer skips the unknown type and logs it, rather than throwing and taking the whole page down. One malformed or newer section should cost you that section, never the page. That single decision is the difference between a page builder that degrades gracefully and one that is a liability.
Flatten once, at the edge
The CMS hands back sections nested inside a collection wrapper. The renderer wants a flat array. I learned the hard way to do that flattening in one transform at the boundary and nowhere else: an early version passed the raw nested shape straight to the renderer, which found no flat list and rendered an empty page that still built green. The exact silent failure this whole architecture is meant to prevent. Now there is one transform, it is unit tested, and the render path only ever sees the shape it expects.
What it buys
Editors compose and reorder pages without a deploy.
The content model is small, modular, and reused, so there is less to maintain.
Unknown or malformed sections fail soft and loud, never taking the page with them.
Every payload is validated once, at the edge, so the render path is boring and safe.
Keep the section model small. Skip unknown types loudly. Validate at the boundary and flatten once. Do that and a page builder is freedom for the editor and calm for the developer, instead of a pile of edge cases you discover in production.