ChaiBuilder Logo

Page Types

A page type is the answer to "I have five hundred blog posts and I do not want to build five hundred pages." You design the layout once in the builder, and every record renders through it.

Concretely, a page type is a named kind of page that says:

  • what its URL looks like,
  • how to turn an incoming URL into one record,
  • how to list the records available, so an editor can preview any one of them.

Every page has a page type, including ordinary static pages. The interesting ones are dynamic: their URL contains a segment that is not fixed, and their content comes from somewhere else.

The idea in one picture

One layout in the builder            Many records
┌──────────────────────────┐         /blog/launching-v3
│ {{ blog.title }}         │  ──►    /blog/hiring-a-designer
│ {{ blog.publishedAt }}   │         /blog/how-we-cache
│ {{ blog.content }}       │         ...
└──────────────────────────┘

The layout is edited by editors in the builder, not by developers in a template file. That is the point of the feature: changing how every blog post looks is a visual edit, not a deploy.

What a page type declares

Declares Purpose
Key and name Its stable identity, and how it reads in the builder UI
Dynamic segment The shape of the variable part of the URL
Data provider Turns the resolved page into the data the layout binds to
Item list Lists and searches the records this page type can render, so the builder can offer a picker
Link resolution Turns a record reference into a URL, so links to it stay correct
Edit and create surfaces Optional URLs the builder opens in a panel, so an editor can edit the underlying record without leaving the builder
Defaults Fallback SEO, JSON-LD, meta tags, and tracking for records that do not set their own
Lifecycle hooks React to a record being created, updated, or deleted

Only the first three are essential. The rest is what turns a working dynamic page into a pleasant one to edit.

How a request resolves

  1. A visitor requests /blog/how-we-cache.
  2. The page lookup finds the page whose slug and dynamic segment match - the layout.
  3. The page type's data provider runs with the request details and returns the record's data.
  4. Bindings in the layout resolve against that data, and the page renders.
  5. If nothing matched, stored redirects are consulted, and only then does the request 404. A project can also supply a last-say handler for unresolved paths, which is where legacy URL patterns belong.

Step 3 is the whole contract. The builder does not know or care whether the provider read a CMS collection, called an API, or parsed markdown in your repository.

Slug and identifier are not the same thing

A page type lists its records for two different reasons, and they need two different keys:

  • The slug builds the URL an editor previews.
  • The identifier is what the data provider looks the record up by.

They are the same string often enough that the distinction is easy to miss, and then it bites: records keyed by id, by full path, or by an external system's reference need the identifier to be that key, not the slug. Keep them separate from the start.

Page types shape the builder, not just the URL

Once a page type exists, the builder can do things it otherwise could not:

  • Offer an item picker, so an editor can flip the layout between real records while designing.
  • Show real content in the canvas, so the design is checked against the longest title rather than lorem ipsum.
  • Open an edit or create panel for the underlying record inline.
  • Restrict which blocks appear in the library. A block can declare the page types it belongs to, so a blog-only block never clutters the landing-page library. See Blocks.
  • Supply SEO defaults for records that have not filled their own in.

Global blocks are page types too

A reusable header, footer, or CTA band is a global block, and a global block is a page of a page type that has no slug. That is not a quirk to work around - it is why global blocks get the same machinery as pages: their own block tree, their own draft and published versions, their own revisions.

It also explains a rule that otherwise looks arbitrary: a global block is edited like a page because it is one, just one that is referenced rather than routed to.

What a page type is not

  • Not a route. Next.js routing is untouched. The catch-all public route resolves any slug; a page type describes the shape of a slug, not a file in your app directory.
  • Not a collection. A collection is where records live. A page type is how one record becomes a page. Many page types are backed by a collection, and a page type can equally read from a git repository, an external API, or a filesystem.
  • Not a template file. The layout is stored page JSON, edited visually. There is no .tsx template for a blog post to open and edit.
  • Not a per-visitor mechanism. Resolution happens at render time, and rendered pages are cached. See Rendering Model.

Failure modes worth knowing

Symptom Usual cause
Every record shows the same content The data provider ignores the incoming identifier and returns a fixed record
The record exists but the URL 404s The dynamic segment does not match the slug's shape
Content is blank but the layout renders The provider returned nothing under the key the bindings use; missing bindings resolve to empty strings
Preview works, live is stale The record changed but nothing revalidated the pages that render that collection
The item picker is empty No item list is provided, so the builder has nothing to offer

© ChaiBuilder. All rights reserved.