使用區塊建構頁面

本頁內容

blocks 欄位儲存一個有序的頁面組合。每個項目都記錄了區塊類型、保留的 schema 版本、一個穩定的鍵,以及該版本宣告的欄位。編輯者在內容編輯器中新增區塊並調整其順序。Astro 路由將每種區塊類型對應到一個元件。

定義區塊類型

先定義區塊類型,再定義使用它們的集合欄位。seed 會保留每個帶編號的版本,以及目前生效的 currentVersion 指標。

以下 seed 定義了 Hero 與 Feature grid 兩種區塊,然後讓它們可以在 Pages 集合的 layout 欄位中使用:

{
  "$schema": "https://emdashcms.com/seed.schema.json",
  "version": "1",
  "blockTypes": [
    {
      "slug": "hero",
      "label": "Hero",
      "category": "Layout",
      "currentVersion": 1,
      "versions": [
        {
          "version": 1,
          "fields": [
            { "slug": "heading", "label": "Heading", "type": "string", "required": true },
            { "slug": "body", "label": "Body", "type": "portableText" },
            { "slug": "image", "label": "Image", "type": "image" },
            { "slug": "link_label", "label": "Link label", "type": "string" },
            { "slug": "link_url", "label": "Link URL", "type": "url" }
          ]
        }
      ]
    },
    {
      "slug": "feature_grid",
      "label": "Feature grid",
      "category": "Layout",
      "currentVersion": 1,
      "versions": [
        {
          "version": 1,
          "fields": [
            { "slug": "heading", "label": "Heading", "type": "string" },
            {
              "slug": "items",
              "label": "Items",
              "type": "repeater",
              "validation": {
                "subFields": [
                  { "slug": "title", "label": "Title", "type": "string", "required": true },
                  { "slug": "description", "label": "Description", "type": "text" }
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "collections": [
    {
      "slug": "pages",
      "label": "Pages",
      "fields": [
        { "slug": "title", "label": "Title", "type": "string", "required": true },
        {
          "slug": "layout",
          "label": "Layout",
          "type": "blocks",
          "validation": {
            "allowedTypes": ["hero", "feature_grid"],
            "maxItems": 20
          }
        }
      ]
    }
  ]
}

allowedTypes 的順序決定了區塊選擇器中的排列順序。如果之後移除了某個允許的類型,EmDash 會將它移到由伺服器管理的 retiredTypes 清單中。既有的區塊仍然可以編輯,但編輯者無法再新增或複製該類型的區塊。

建立 Astro 元件

每個元件都會接收 value、index 與 blockKey。value 中包含 _type、_version 與 _key,因此當區塊 schema 演進時,元件可以據此區分保留的各個版本。

Hero 元件從已儲存的區塊中讀取所有要顯示的值:

---
import { sanitizeHref } from "emdash";
import { Image, PortableText, type BlockComponentProps } from "emdash/ui";
import type { PageLayoutBlock } from "../../../emdash-env";

type HeroBlock = Extract<PageLayoutBlock, { _type: "hero" }>;
type Props = BlockComponentProps<HeroBlock>;

const { value } = Astro.props;
---

<section class="hero">
  <div>
    <h1>{value.heading}</h1>
    {value.body && <PortableText value={value.body} />}
    {value.link_url && <a href={sanitizeHref(value.link_url)}>{value.link_label}</a>}
  </div>
  {value.image && <Image image={value.image} />}
</section>

為每種允許的類型建立一個元件。元件負責標記與樣式;區塊的值提供內容與媒體。

呈現頁面組合

使用 defineBlockComponents 可以要求為產生的欄位聯合型別中的每個 _type 都提供一個元件。在頁面路由中將這個對應表傳給 <Blocks>。

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Blocks, defineBlockComponents } from "emdash/ui";
import type { PageLayoutBlock } from "../../../emdash-env";
import FeatureGrid from "../../components/blocks/FeatureGrid.astro";
import Hero from "../../components/blocks/Hero.astro";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");

const { entry: page, cacheHint } = await getEmDashEntry("pages", slug);
if (!page) return Astro.redirect("/404");
Astro.cache.set(cacheHint);

const components = defineBlockComponents<PageLayoutBlock>({
  hero: Hero,
  feature_grid: FeatureGrid,
});
---

<Blocks value={page.data.layout} components={components} />

<Blocks> 不會執行任何內容、schema、媒體或網路查詢。它依儲存順序呈現傳入的陣列。如果某個區塊元件需要其他資料,可以在該元件中明確執行應用程式查詢。

處理缺少的元件

在開發環境中,未對應的類型會顯示一個可見的預留位置,並在主控台輸出警告。預留位置會顯示 _type,但不會印出已儲存的區塊值。

在正式環境中,如果提供了 fallback 元件,未對應的類型會呈現該元件;否則不輸出任何內容:

---
import MissingBlock from "../../components/blocks/MissingBlock.astro";
---

<Blocks value={page.data.layout} components={components} fallback={MissingBlock} />

在啟用或啟動正式內容所使用的區塊類型之前,請先發布呈現器對它的支援。

變更區塊 schema

相容的變更會修訂目前生效的版本。新增選填欄位、新增預設值或放寬驗證,都會維持相同的版本號。已儲存的區塊會在下次寫入時取得預設值。

破壞性變更會建立一個未啟用的版本。刪除欄位、變更欄位類型、新增必填欄位或收緊驗證,都屬於破壞性變更。

  1. 透過 schema API 或 MCP 建立破壞性版本,並保持其未啟用狀態。

  2. 更新呈現器,使其同時處理保留的舊版本與新版本,然後部署呈現器。

  3. 啟用新版本。啟用後,新建立的區塊將使用該版本。

  4. 使用 migrateBlocks: true 明確遷移已儲存的區塊。在變更 _version 及其版本特定欄位時,保留每個區塊的 _key。

舊版本仍可用於修訂版本、草稿、媒體追蹤與已儲存的內容。區塊類型與保留的版本沒有永久刪除操作。

支援的巢狀欄位

區塊定義支援 string、text、url、number、integer、boolean、datetime、select、multiSelect、portableText、image、file 與 repeater。

區塊定義內不支援參照、JSON、slug、巢狀區塊、自訂元件、實體索引、唯一性限制以及依子欄位在地化。區塊欄位本身不能設為必填、唯一、可搜尋或已建立索引,也不能指定自訂欄位元件。

關於欄位驗證與儲存值的確切規則,請參閱 blocks 欄位參考。關於 seed 衝突與匯出行為,請參閱 Seed 檔案。