使用區塊建構頁面

本頁內容

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

定義區塊類型

請在使用區塊類型的集合欄位之前先定義區塊類型。種子會保留每一個編號版本以及目前生效的 currentVersion 指標。

下面的種子定義了 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。該值包含 _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.rewrite("/404");

const { entry: page, cacheHint } = await getEmDashEntry("pages", slug);
if (!page) return Astro.rewrite("/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 欄位本身不能設為必填、唯一、可搜尋、可索引,也不能指定自訂欄位小工具。

有關確切的欄位校驗和已儲存值的規則,請參閱 blocks 欄位參考。有關種子衝突和匯出行為,請參閱 Seed 檔案。