Post

A page builder is a schema, not a layout tool

The page-components pattern is what most real Sanity projects use, and the reason it works is the part that looks like a restriction: an editor can only compose sections the frontend can actually draw.

A warm diagonal gradient, orange over dark brown.

Every CMS eventually meets the same request: let me build the page myself. There are two ways to answer it, and only one of them survives a redesign.

The way that does not survive

Give the editor a rich-text field and some way to drop in arbitrary HTML, columns and styles. It works on the first day. On the day the site is restyled, the content contains hundreds of decisions the new design has no equivalent for — a two-column block with a fixed pixel width, a heading that was made large by hand rather than by being a heading, an inline colour. None of it is queryable, none of it is portable, and the migration is a person reading pages one at a time.

The way that does

Model the sections the design system actually has, and let the editor compose them. This site's home page is one document whose sections array holds a hero, a feature grid, a statistics band, a code sample and a call to action, in that order. Each is an object with a _type, and the frontend maps that _type to a component.

groq
*[_type == "page" && slug.current == "home"][0]{
  title,
  sections[]{ _type, _key, ... }
}

Three properties fall out of that, and they are why the pattern is worth the extra schema work:

  • The content is data. "Which pages carry a compatibility matrix?" is a query, not a search. Try that against a wall of HTML.
  • A redesign is a re-render. The sections do not change; the components that draw them do.
  • An editor cannot produce something the frontend cannot render. The Studio's "Add item" menu is exactly the list of sections that exist.

Where the line sits

Not every section should be a rigid card grid. This site's splitSection and richTextSection carry full Portable Text, because prose is prose. The rule we settled on is narrower than "no rich text": rich text where a person is writing sentences, structured fields where a person is filling in a component. A feature card's body is a plain string with a length limit, because a bulleted list inside a grid cell is a layout nobody designed.

The other half of the pattern is that a section may query. The features on this site's home page are not a curated array of references — the section stores the word highlighted, and the frontend fetches the features flagged as such. Adding a feature document updates the home page. That is the part a big rich-text field can never do.