建立部落格

本頁內容

EmDash 部落格範本提供了一個可執行的 Astro 站台,包含文章、頁面、作者、分類、標籤、搜尋、留言、小工具和 RSS 摘要。本教學會建立 Cloudflare 版本,發佈一篇文章,並沿著範本程式碼追蹤這篇文章的整個流程。

先決條件

開始之前,請安裝 Node.js 22.12 或更新版本,以及 pnpm。

只有在部署站台時才需要 Cloudflare 帳戶。在本機開發期間,範本會在你的電腦上執行本機版本的資料庫和檔案儲存。

建立部落格專案

下面的指令會依據 Cloudflare 部落格範本建立 my-blog,並使用 pnpm 安裝其相依套件:

npm create emdash@latest my-blog -- --template cloudflare:blog --pm pnpm --yes

鷹架工具還會建立一個本機 .env 檔案,其中包含 EMDASH_ENCRYPTION_KEY。產生的 .gitignore 會將 .env 排除在版本控制之外。如果指令回報相依套件安裝失敗,請進入專案目錄並執行 pnpm install,然後再繼續。

啟動本機開發伺服器:

cd my-blog
pnpm dev

開啟終端機中印出的本機 URL,然後開啟 /_emdash/admin。如果是第一次執行,請完成設定畫面。範本的種子資料會在設定期間建立內容模型和範例內容。

瞭解內容模型

範本在 seed/seed.json 中定義了兩個集合:

  • posts 透過 supports 啟用草稿、修訂、搜尋和 SEO,並透過 commentsEnabled: true 單獨啟用留言;
  • pages 支援草稿、修訂和搜尋。

每篇文章都有以下自訂欄位:

欄位用途
title必填的文章標題
featured_image隨文章顯示的選用圖片
contentPortable Text 內文
excerpt用於文章清單和中繼資料後備的簡短文字

EmDash 會新增系統欄位,例如穩定的內容 ID、slug、狀態、建立和更新時間以及發佈時間。範本還為文章定義了 category 和 tag 分類法,以及可以署名一位或多位作者的 byline。

開發伺服器會根據這個 schema 產生 emdash-env.d.ts。因此,getEmDashCollection("posts") 傳回的條目,其 data 屬性的型別為 Post。

發佈第一篇文章

  1. 在管理後台側邊欄中選擇 Posts,然後選擇 Add New。

  2. 輸入標題。EmDash 會根據標題建議一個 slug;如果公開 URL 需要不同的值,請自行編輯。

  3. 新增摘要,並在 Content 編輯器中撰寫內文。

  4. 從媒體庫中選擇一張精選圖片,或上傳一張。新增替代文字,描述這張圖片在文章中的作用。

  5. 在設定面板中指定 byline、分類以及相關標籤。

  6. 選擇 Save。該條目會成為草稿,編輯器會開啟它的永久條目 URL。

  7. 選擇 Preview 並檢查文章頁面。草稿準備就緒後,回到編輯器並選擇 Publish。

在本機站台上開啟 /posts/your-post-slug。這篇文章也會出現在首頁和文章封存中。如果沒有出現,請確認編輯器顯示的是 Published,而不是 Draft 或 Scheduled。

發佈之後,編輯內容會自動儲存為新的草稿,而目前文章仍保持上線狀態。當修訂後的草稿應當取代它時,請選擇 Publish changes。內容撰寫指南介紹了預覽、排程發佈、修訂和編輯鎖定。

追蹤集合查詢

首頁和文章封存在伺服器端算繪期間會呼叫 getEmDashCollection()。範本在資料庫中依儲存的 published_at 欄位對文章排序:

---
import { getEmDashCollection, getTermsForEntries } from "emdash";

const { entries: posts, cacheHint } = await getEmDashCollection("posts", {
  orderBy: { published_at: "desc" },
});

if (Astro.cache?.enabled) Astro.cache.set(cacheHint);

const tagsByEntry = await getTermsForEntries(
  "posts",
  posts.map((post) => post.data.id),
  "tag",
);
---

集合查詢預設傳回已發佈的條目。它使用資料庫欄位名稱 published_at 來排序。傳回的 publishedAt 屬性是供算繪使用的 JavaScript Date。

分類法輔助函式接收的是 post.data.id,因為分類法的指派屬於穩定的內容 ID。連結則使用 post.id,因為那是內容載入器產生的、面向 URL 的 slug:

<a href={`/posts/${post.id}`}>
  <h2>{post.data.title}</h2>
  {post.data.excerpt && <p>{post.data.excerpt}</p>}
</a>

目前的範本使用 getTermsForEntries() 批次查詢標籤,而不是為每篇文章各查詢一次。byline 已經由集合查詢包含在 post.data.bylines 中。

追蹤文章查詢

動態文章路由從 URL 中讀取 slug 並呼叫 getEmDashEntry()。下面的摘錄展示了核心的查詢和算繪路徑,而完整的範本還會處理 SEO、byline、留言、相關文章和小工具:

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Image, PortableText } from "emdash/ui";

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

const { entry: post, error, cacheHint } = await getEmDashEntry("posts", slug);

if (error) return new Response("Unable to load post", { status: 500 });
if (!post) return Astro.rewrite("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

<article>
  {post.data.featured_image && <Image image={post.data.featured_image} priority />}
  <h1>{post.data.title}</h1>
  <PortableText value={post.data.content} />
</article>

Image 讀取編輯者選擇的媒體值並產生回應式輸出。PortableText 把儲存的區塊資料轉換為標題、段落、連結、圖片、程式碼區塊以及其他受支援的區塊型別。

兩個部落格範本都在 astro.config.mjs 中設定了 output: "server"。這些查詢在算繪請求時執行,因此已發佈的內容不依賴於建構期間產生的靜態路由清單。

使用分類和標籤

範本為每個分類和標籤都包含了封存路由。分類路由會先解析術語 slug,然後依該分類法過濾文章:

---
import { decodeSlug, getEmDashCollection, getTerm } from "emdash";

const slug = decodeSlug(Astro.params.slug);
const category = slug
  ? await getTerm("category", slug, { includeCounts: false })
  : null;

if (!category) return Astro.rewrite("/404");

const { entries: posts, error } = await getEmDashCollection("posts", {
  where: { category: category.slug },
  orderBy: { published_at: "desc" },
});

if (error) return new Response("Unable to load posts", { status: 500 });
---

tag 路由使用相同的模式,只是換成 getTerm("tag", slug) 和 where: { tag: term.slug }。編輯者在管理後台中管理術語及其指派;分類法指南涵蓋了階層式分類、扁平標籤和自訂分類法。

getEmDashEntry() 會包含文章已指派的術語,因此詳細資料路由無需再次查詢就能算繪它們:

---
const categories = post.data.terms?.category ?? [];
const tags = post.data.terms?.tag ?? [];
---

{categories.map((category) => (
  <a href={`/category/${category.slug}`}>{category.label}</a>
))}
{tags.map((tag) => (
  <a href={`/tag/${tag.slug}`}>{tag.label}</a>
))}

新增封存分頁

範本的文章封存會算繪每一篇已發佈的文章。當封存變大時,請加入 limit:對 /posts/page/2 這樣的編號路由使用位移分頁,對 Older posts 連結使用游標分頁。在各頁之間保持 orderBy: { published_at: "desc" } 不變,這樣條目的順序才不會意外改變。

分頁範例展示了這兩種方式,並說明了何時選擇哪一種。

檢查 RSS 摘要

範本已經提供了 /rss.xml。它的端點用 getEmDashCollection() 讀取最新的 20 篇文章,格式化每個發佈日期,並在把標題和摘要插入 XML 之前對其進行跳脫。它還會從 EmDash 設定中讀取站台標題和標語。

發佈測試文章之後,開啟 /rss.xml 並搜尋它的標題。如果站台要在摘要中使用正式環境的絕對 URL,請在部署之前設定 Astro 的 site 選項;在本機開發期間,該端點會回退到目前請求的來源。

至此,這個部落格已經具備了撰寫工作流程、執行階段文章頁面、分類法封存、媒體算繪和摘要。接下來可以閱讀查詢內容瞭解過濾與分頁,或閱讀媒體庫瞭解資產編輯與使用情況追蹤。若要借助 AI 助理起草或編輯文章,請參閱 AI 工具。