Strapi
Content Modeling in Strapi: Pages, Sections, and Reusable Blocks
How to model flexible pages in Strapi with a Sections dynamic zone and reusable Block components — the schema shape that lets editors build new pages without a developer, without turning into an unmaintainable mess.
The question every headless CMS project eventually has to answer: how much structure does a page get? Too little, and every page is a wall of rich text that a developer has to hand-style — editors can't compose anything, and every layout variation is a one-off. Too much, and the content model becomes forty content types deep, each one a slightly different flavor of "hero section," until nobody — editor or developer — can predict what a given field actually does. The pattern that avoids both: model a page as an ordered list of sections, where each section composes a small set of reusable blocks. It's the first thing we design on every Strapi build, because getting it wrong is expensive to walk back once editors have published fifty pages against the old shape.
The three-tier model
Pages are the top level — a collection type with a slug, SEO fields, and an ordered list of sections. A page doesn't know or care what's inside its sections; it just holds the sequence.
Sections are full-width layout regions — a hero, a feature grid, a testimonial band, a CTA banner. Each section type has its own fields, and it's what an editor picks when building a page. Sections are Strapi components, assembled into a dynamic zone on the Page content type, so an editor can add, remove, and reorder them freely without a developer touching code.
Blocks are the small, reusable atoms inside sections — a CTA button (label, URL, style), a stat (value, label), a logo item (image, name, link). The same block.cta-button component gets reused inside a Hero section, a CTA Banner section, and a Feature Grid — defined once, consistent everywhere it appears.
Here's what that looks like in Strapi's Content-Type Builder:
Illustrative diagram, not a live screenshot — modeled on Strapi 5's Content-Type Builder to show the actual field types (Dynamic Zone, Component) and the relationship between sections and the blocks they compose.
Single type vs. collection type: get this decision right first
Single type when there's exactly one instance — Homepage, Global Settings, a Privacy Policy. Collection type when you have many of the same shape — Blog Post, Author, and (per this article) Page itself, for marketing and landing pages that share the same section-based structure.
The mistake we see most often: treating the homepage as fundamentally different from other marketing pages and giving it a bespoke single type with its own hand-modeled fields, while every other landing page uses the Page collection type with sections. Within a year, the homepage inevitably needs a new section type that exists everywhere else, and now it needs a parallel implementation. Unless the homepage is genuinely structurally unique, model it as a Page too — just the one with the slug / — and let it use the same sections dynamic zone as everything else.
Building the sections dynamic zone
In Strapi, a dynamic zone is a field type that holds a list of components, where each entry can be any type from an allowed set, in an order the editor controls. This is the mechanism that makes "Page as sequence of sections" actually work in the admin UI: an editor opens a Page, hits "Add a component to sections," and picks from Hero, Feature Grid, Testimonial Band, CTA Banner, or Logo Row — the exact set your project needs, no more.
Keep the allowed set small and deliberate — five to ten section types is the range that stays manageable. Every section type is a component the frontend has to have a matching React component for for the rest of the project's life; each one is also a permanent option in the editor's picker, whether or not it ever gets used again after the first few pages. Add a new section type when you have a genuinely reusable pattern that's shown up more than once, not preemptively because someone might want it.
Each section owns its layout, not its content atoms. A Hero section has an eyebrow, title, subtitle, and a CTA — but the CTA itself should be a block.cta-button reference, not three raw fields (label, url, style) duplicated inline. This is the detail that determines whether the model is maintainable in year two: if the CTA button's fields live only inside the Hero component's schema, changing how CTAs work means finding and updating every section type that happens to have its own copy of those three fields. If it's a shared block, you change it once.
Designing the reusable blocks
Blocks earn their place in the model by showing up in more than one section — that's the test. A stat (value, label) belongs in a Feature Grid and might also belong in a standalone Stats Band section; model it once as block.stat and reference it from both, rather than redefining "a number with a caption" twice with slightly different field names each time.
Common blocks worth having from day one on most marketing sites:
block.cta-button— label, URL, and a style enum (primary/secondary) so the frontend can render consistent button treatments everywhere a CTA appears.block.stat— value and label, used in feature grids, hero stat rows, and about-page metrics.block.logo-item— logo image, name, and optional link, for client/partner logo rows.block.testimonial— quote, author name, role/company, optional rating — reused across a testimonial section and potentially a dedicated case-study page.
Naming convention matters more than it seems like it should. Use a consistent prefix so section components and block components are visually distinguishable in the component list — section.hero, section.feature-grid vs. block.cta-button, block.stat. Six months in, a developer scanning the schema should be able to tell at a glance which components are page-level layout and which are reusable content atoms, without opening each one.
Keeping field names consistent across section types
Every section type should use the same field name for the same concept — title, not heading in one component and title in another. This sounds like a minor style preference; in practice, it's what lets the frontend share rendering logic instead of writing bespoke mapping code per section type. A frontend component that expects section.title should work whether the section is a Hero, a Feature Grid, or a Testimonial Band, provided all three actually call that field title.
Document the section and block inventory somewhere outside the CMS itself — a short internal wiki page or README listing what each section type is for and what it renders as on the frontend. Editors adding a new page need to know which section covers "we want a row of client logos" without guessing between Logo Row and improvising inside a Feature Grid. This documentation is cheap to write once and expensive to reconstruct later once five developers and two content editors have each formed a slightly different mental model of what each section is supposed to be for.
When this model gets more complex
Nested dynamic zones — a section that itself contains a dynamic zone of sub-blocks in flexible order, rather than fixed fields — are possible in Strapi but worth resisting until you have a concrete case for them. They make the admin UI harder to reason about (editors now navigate two levels of "add a component" pickers) and the frontend rendering logic recursive instead of flat. Most marketing sites never need this; a fixed set of fields per section, with blocks handling the genuinely repeatable parts, covers the overwhelming majority of layouts we've built.
The other common complexity: content that's genuinely shared across pages, like a global promo banner or a sitewide announcement. Don't model this as a section repeated on every page — model it once, at the site-settings or single-type level, and have the frontend layer it in during rendering, independent of any individual page's section list.
The model, condensed
| Tier | What it is | Strapi mechanism | Example |
|---|---|---|---|
| Page | The top-level entity: slug, SEO, ordered sections | Collection type | /pricing, /about, / |
| Section | A full-width layout region, one per "chunk" of a page | Component, used in a Dynamic Zone | Hero, Feature Grid, CTA Banner |
| Block | A small, reusable content atom inside sections | Component, referenced by sections | block.cta-button, block.stat |
Get this three-tier shape right at the start of a project, and adding a new page is a content operation — an editor composing existing sections — not a development ticket. This is exactly the content-model phase we run at the start of every headless CMS implementation, before any frontend code gets written, because a content model that's wrong is far more expensive to fix after fifty pages exist against it than before the first one does. If you're modeling a Strapi project and want a second opinion on the section/block split before you commit to a schema, get in touch.