BlockBite docs

Page content

Store pages as readable JSON, validate them against your islands and render each section.

A page is a JSON file that lists its sections in order. Each section names an island variant and holds the values for that variant’s fields. This guide describes the current @blockbite/astro implementation.

The page file

{
  "version": 1,
  "template": "default",
  "title": "Home",
  "sections": [
    {
      "island": "hero/simple",
      "fields": {
        "heading": "Build an island once, publish it anywhere",
        "intro": "One component and one fields.yaml per variant."
      }
    },
    {
      "island": "editorial-grid/three-up",
      "fields": {
        "eyebrow": "Island collections",
        "title": "Give visitors something to interact with",
        "entries": [
          { "title": "Let the work lead", "meta": "Gallery" },
          { "title": "Turn browsing into discovery", "meta": "Collection" }
        ]
      }
    }
  ]
}
  • island is the variant’s family/variant path.
  • fields contains exactly the keys that variant’s fields.yaml declares, no more and no fewer. An empty string or an empty list is a valid value.
  • Repeater rows are plain objects, and their order in the file is the order on the page.
  • The same variant can appear more than once with different values.
  • Nothing in the file is a generated ID, so it reads well and diffs cleanly.

Validate pages with a content collection

Pages load through a native Astro content collection. loadIslands() reads your island folders, and createPageSchema() turns them into the collection schema:

// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { createPageSchema } from '@blockbite/astro/content';
import { loadIslands } from '@blockbite/astro/islands';

const { islands } = await loadIslands(new URL('./islands/', import.meta.url));

export const collections = {
  pages: defineCollection({
    loader: glob({ pattern: '*.json', base: './src/content/pages' }),
    schema: createPageSchema({ islands, templates: ['default'] }),
  }),
};

A page fails to load when a section names an unknown island, misses a declared field, adds a field the variant does not declare, or uses the wrong value type. The error points to the exact section, field and row.

Bind sections to a native route

For Desktop editing, import each island explicitly and spread its section’s fields directly into the call. For the page JSON above:

---
// src/pages/index.astro
import { getEntry } from 'astro:content';
import type { IslandProps } from '@blockbite/astro/islands';
import Hero from '../islands/hero/simple/Island.astro';
import EditorialGrid from '../islands/editorial-grid/three-up/Island.astro';

const page = await getEntry('pages', 'home');
if (!page) throw new Error('Missing src/content/pages/home.json');
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{page.data.title}</title>
  </head>
  <body>
    <main>
      <Hero {...(page.data.sections[0].fields as IslandProps<'hero/simple'>)} />
      <EditorialGrid {...(page.data.sections[1].fields as IslandProps<'editorial-grid/three-up'>)} />
    </main>
  </body>
</html>

@blockbite/astro does not add routes or a section renderer. This page is your own Astro code, so you decide layouts, URLs and which pages exist.

Keep the component calls aligned with the section paths and order in the JSON. The type assertions give each call its declared prop type; content validation still comes from createPageSchema(). You can move the document shell into your existing layout component.

This explicit shape lets Desktop connect a rendered section to its values. Dynamic registries and sections.map() may render in Astro while remaining read-only in the visual editor.

Rename a field

A field’s key is its identity, so a rename touches three places in one change:

  1. The key in fields.yaml, for example summary to description.
  2. Every page section that uses that variant.
  3. The component that reads the prop.

Until all three match, page validation or astro check fails. Astro caches validated content, so run npx astro sync --force after the change to check every page against the new declaration. Changing only a label needs no content changes.

See the island file reference for every rule and limit.