Build Reusable Blocks
This chapter turns the page model into a page-builder model.
Replace app.toml
At this checkpoint the manifest should contain the site model, the page content model, and the block schemas together:
name = "tutorial-app"
display_name = "Tutorial App"
[i18n]
default_locale = "en-GB"
supported_locales = ["en-GB", "fr-FR"]
[[sites]]
id = "tutorial-uk"
display_name = "Tutorial UK"
brand_name = "Tutorial"
canonical_domain = "uk.127.0.0.1.nip.io"
additional_domains = ["www.127.0.0.1.nip.io"]
default_locale = "en-GB"
supported_locales = ["en-GB"]
[[sites]]
id = "tutorial-fr"
display_name = "Tutorial France"
brand_name = "Tutorial"
canonical_domain = "fr.127.0.0.1.nip.io"
additional_domains = []
default_locale = "fr-FR"
supported_locales = ["fr-FR"]
[[content_models]]
name = "landing_page"
label = "Landing page"
[[content_models.fields]]
name = "title"
type = "string"
required = true
[[content_models.fields]]
name = "slug"
type = "slug"
required = true
[[content_models.fields]]
name = "summary"
type = "text"
required = true
[[content_models.fields]]
name = "page_type"
type = "string"
required = true
[[content_models.fields]]
name = "template"
type = "string"
required = false
[[content_models.fields]]
name = "seo_title"
type = "string"
required = false
[[content_models.fields]]
name = "seo_description"
type = "text"
required = false
[[block_types]]
id = "hero"
label = "Hero"
[[block_types.fields]]
name = "heading"
type = "string"
required = true
[[block_types.fields]]
name = "body"
type = "text"
required = true
[[block_types]]
id = "promo_grid"
label = "Promo grid"
[[block_types.fields]]
name = "intro"
type = "string"
required = false
[[block_types]]
id = "trust_strip"
label = "Trust strip"
[[block_types.fields]]
name = "heading"
type = "string"
required = true
[[block_types.fields]]
name = "body"
type = "text"
required = true
Add One Shared Block File
content/shared-blocks/delivery-promises.json
{
"id": "shared-delivery-promises",
"label": "Delivery promises",
"block_type": "trust_strip",
"fields": {
"heading": "Delivery and returns",
"body": "Tracked shipping, straightforward returns, and local support."
}
}
Replace The Page Record With A Block-Based Version
content/pages/spring-sale.json
{
"id": "page-spring-sale",
"type": "landing_page",
"title": "Spring Sale",
"slug": "spring-sale",
"summary": "Fresh arrivals and seasonal offers for the new quarter.",
"settings": {
"page_type": "landing_page",
"template": "pages/landing-page",
"show_in_navigation": true,
"allow_indexing": true
},
"blocks": [
{
"kind": "instance",
"id": "hero-spring-sale",
"block_type": "hero",
"label": "Spring Sale Hero",
"fields": {
"heading": "Spring layers and trail essentials",
"body": "Start the season with new arrivals, member offers, and event signups."
}
},
{
"kind": "instance",
"id": "promo-grid-spring-sale",
"block_type": "promo_grid",
"label": "Campaign cards",
"fields": {
"intro": "Shop by priority"
}
},
{
"kind": "shared_reference",
"id": "shared-delivery-promises-reference",
"shared_block_id": "shared-delivery-promises",
"label": "Delivery promises"
}
]
}
At this point you should have three concrete files in agreement:
app.toml
content/shared-blocks/delivery-promises.json
content/pages/spring-sale.json
That set teaches the boundary you need:
- block type is schema
- inline block is page-local content
- shared block is reusable content
- shared reference is how the page points at that reusable content
What Each File Is Doing
app.toml
This file now defines both the page schema and the block schema.
The important new section is [[block_types]].
Each block type gives the editor a reusable block contract:
herosupports the main campaign banner contentpromo_gridsupports a smaller structured list sectiontrust_stripsupports reusable reassurance content
This file only defines what kinds of blocks can exist. It does not place any blocks on a page by itself.
content/shared-blocks/delivery-promises.json
This file creates one reusable block instance that can be referenced from multiple pages.
The important fields are:
idThe stable identifier other pages will reference.block_typeThe schema contract this shared block uses.fieldsThe actual editorial values for that reusable block.
This is how the CMS can store one shared editorial asset instead of copying the same footer-style content into every page record.
content/pages/spring-sale.json
This file shows the page-builder shape on a real page.
The important section is blocks.
That ordered array now mixes two kinds of block records:
kind = "instance"A page-local block owned only by this page.kind = "shared_reference"A pointer to a reusable shared block stored elsewhere.
That is the core page-builder behavior. The page owns the order of the blocks, while some of the content can still be shared across many pages.
What Behavior This Enables
Once these files line up:
- the page becomes an ordered structured block list instead of one free-form body field
- editors can reuse common content without duplicating it across every page
- the runtime can resolve shared block references when composing the page
- the CMS admin can later expose block reorder, replace, and shared-block editing workflows
- page-level settings still remain separate from the block list
Checkpoint
Run the app and confirm:
cargo run -p tutorial-app-bin -- validate
cargo run -p tutorial-app-bin -- serve
- the page has an ordered block list
- one block is a shared reference
- the app still validates and serves