building-emdash-site

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Building an EmDash Site

构建 EmDash 站点

EmDash is a CMS built on Astro. It stores schema in the database (not in code), serves content via live content collections, and provides a full admin UI at
/_emdash/admin
. Sites are standard Astro projects with the
emdash
integration.
EmDash 是一个基于 Astro 构建的 CMS。它将 schema 存储在数据库中(而不是代码中),通过 live content collections 提供内容,并在
/_emdash/admin
提供完整的 admin UI。站点是带有
emdash
integration 的标准 Astro 项目。

Common Gotchas

常见陷阱

These are the things that silently break sites. Know them before you start.
  1. Image fields are objects, not strings.
    post.data.featured_image
    is
    { id, src, alt }
    . Writing
    <img src={post.data.featured_image} />
    renders
    [object Object]
    . Use
    <Image image={post.data.featured_image} />
    from
    "emdash/ui"
    .
  2. entry.id
    vs
    entry.data.id
    are different things.
    entry.id
    is the slug, prefixed with the locale (
    en/my-post
    ) for locales whose URLs are prefixed (use in URLs;
    entry.data.slug
    is the bare slug).
    entry.data.id
    is the database ULID (use for
    getEntryTerms
    ,
    Comments
    , and other API calls that need the real ID). Mixing them up causes silent empty results.
  3. Taxonomy names must match the seed exactly. If your seed defines
    "name": "category"
    , you must query
    getTerm("category", slug)
    -- not
    "categories"
    . Wrong name = empty results, no error.
  4. Register cache hints when Astro's cache is enabled. Content queries return a
    cacheHint
    ; pass it to
    Astro.cache.set(cacheHint)
    . Use
    getSiteSettingsWithCacheHint()
    ,
    getMenuWithCacheHint()
    ,
    getTaxonomyTermsWithCacheHint()
    , and
    getWidgetAreaWithCacheHint()
    when a cached route renders those values.
  5. No
    getStaticPaths
    for CMS content.
    EmDash content is dynamic. Pages must be server-rendered (
    output: "server"
    in
    astro.config.mjs
    ).
以下问题会在毫无提示的情况下破坏站点。开始之前请先了解它们。
  1. Image 字段是对象,而不是字符串。
    post.data.featured_image
    是
    { id, src, alt }
    。写入
    <img src={post.data.featured_image} />
    会渲染出
    [object Object]
    。请使用从
    "emdash/ui"
    导入的
    <Image image={post.data.featured_image} />
    。
  2. entry.id
    与
    entry.data.id
    是不同的概念。
    entry.id
    是带 locale 前缀的 slug;对于 URL 带 locale 前缀的语言环境,其格式为
    en/my-post
    (用于 URL;
    entry.data.slug
    是不带前缀的 slug)。
    entry.data.id
    是数据库 ULID(用于
    getEntryTerms
    、
    Comments
    以及其他需要真实 ID 的 API 调用)。混用二者会静默返回空结果。
  3. Taxonomy 名称必须与 seed 完全匹配。 如果 seed 定义了
    "name": "category"
    ,就必须查询
    getTerm("category", slug)
    ,而不是
    "categories"
    。名称错误会返回空结果,且不会报错。
  4. 启用 Astro 缓存时,请注册 cache hints。 内容查询会返回
    cacheHint
    ;请将其传给
    Astro.cache.set(cacheHint)
    。当缓存路由渲染这些值时,请使用
    getSiteSettingsWithCacheHint()
    、
    getMenuWithCacheHint()
    、
    getTaxonomyTermsWithCacheHint()
    和
    getWidgetAreaWithCacheHint()
    。
  5. CMS 内容不需要
    getStaticPaths
    。
    EmDash 内容是动态的。页面必须以服务端渲染方式呈现(在
    astro.config.mjs
    中设置
    output: "server"
    )。

File Structure

文件结构

Every EmDash site has these key files:
my-site/
├── astro.config.mjs          # Astro config with emdash() integration
├── src/
│   ├── live.config.ts         # EmDash loader registration (boilerplate)
│   ├── pages/                 # Astro pages (all server-rendered)
│   ├── layouts/               # Layout components
│   └── components/            # Reusable components
├── seed/
│   └── seed.json              # Schema + demo content
├── emdash-env.d.ts          # Generated types (from `emdash types`)
└── package.json
每个 EmDash 站点都有以下关键文件:
my-site/
├── astro.config.mjs          # Astro config with emdash() integration
├── src/
│   ├── live.config.ts         # EmDash loader registration (boilerplate)
│   ├── pages/                 # Astro pages (all server-rendered)
│   ├── layouts/               # Layout components
│   └── components/            # Reusable components
├── seed/
│   └── seed.json              # Schema + demo content
├── emdash-env.d.ts          # Generated types (from `emdash types`)
└── package.json

Workflow

工作流程

1. Configure the project

1. 配置项目

Read references/configuration.md for
astro.config.mjs
,
live.config.ts
, deployment targets (Node vs Cloudflare), and type generation.
阅读 references/configuration.md,了解
astro.config.mjs
、
live.config.ts
、deployment targets(Node 与 Cloudflare)以及 type generation。

2. Design the schema

2. 设计 schema

Read references/schema-and-seed.md for collection definitions, field types, taxonomies, menus, widget areas, sections, bylines, and the complete seed file format.
阅读 references/schema-and-seed.md,了解 collection definitions、field types、taxonomies、menus、widget areas、sections、bylines 以及完整的 seed 文件格式。

3. Build the pages

3. 构建页面

Read references/querying-and-rendering.md for content queries, Portable Text rendering, the Image component, visual editing attributes, caching, and common page patterns (list, detail, taxonomy archive, RSS, search, 404).
阅读 references/querying-and-rendering.md,了解 content queries、Portable Text rendering、Image component、visual editing attributes、caching 以及常见页面模式(列表、详情、taxonomy archive、RSS、搜索、404)。

4. Wire up site features

4. 接入站点功能

Read references/site-features.md for site settings, navigation menus, taxonomies, widget areas, search, SEO meta, comments, and page contributions.
阅读 references/site-features.md,了解 site settings、navigation menus、taxonomies、widget areas、搜索、SEO meta、comments 以及 page contributions。

5. Create the seed file

5. 创建 seed 文件

Write
seed/seed.json
with collections, fields, taxonomies, menus, widgets, and sample content.
编写
seed/seed.json
,其中包含 collections、fields、taxonomies、menus、widgets 和示例内容。

6. Run and verify

6. 运行并验证

bash
pnpm dev                # Start the Astro dev server
The runtime runs pending migrations on the first request and, before setup is completed, applies the seed's schema and structure once. Sample content needs an explicit step; see Applying Seeds. The Astro integration generates
emdash-env.d.ts
when the server starts. The admin UI is at
http://localhost:4321/_emdash/admin
.
bash
pnpm dev                # Start the Astro dev server
运行时会在首次请求时执行待处理的 migrations;在 setup 完成前,还会一次性应用 seed 中的 schema 和 structure。示例内容需要显式执行;请参阅 Applying Seeds。服务器启动时,Astro integration 会生成
emdash-env.d.ts
。admin UI 位于
http://localhost:4321/_emdash/admin
。

Quick API Cheat Sheet

快速 API 速查表

typescript
// Content (entries have .data.byline and .data.bylines eagerly loaded)
import { getEmDashCollection, getEmDashEntry } from "emdash";
const { entries, nextCursor, cacheHint } = await getEmDashCollection("posts", {
	limit: 10,
	cursor,
	orderBy: { published_at: "desc" },
});
const { entry: post, cacheHint: entryCacheHint } = await getEmDashEntry("posts", slug);

// Site features
import {
	getSiteSettings,
	getSiteSettingsWithCacheHint,
	getMenu,
	getMenuWithCacheHint,
	getTaxonomyTerms,
	getTaxonomyTermsWithCacheHint,
	getTerm,
	getEntryTerms,
	getEntriesByTerm,
	getWidgetArea,
	getWidgetAreaWithCacheHint,
	search,
	getSection,
	getSeoMeta,
} from "emdash";

// Bylines (standalone queries -- usually not needed since entries have bylines attached)
import { getByline, getBylineBySlug } from "emdash";

// UI components
import {
	PortableText,
	Blocks,
	defineBlockComponents,
	Image,
	WidgetArea,
	EmDashHead,
	EmDashBodyStart,
	EmDashBodyEnd,
} from "emdash/ui";
import { Comments, CommentForm } from "emdash/ui/comments";
import LiveSearch from "emdash/ui/search";

// Page context (for plugin contributions)
import { createPublicPageContext } from "emdash/page";
typescript
// Content (entries have .data.byline and .data.bylines eagerly loaded)
import { getEmDashCollection, getEmDashEntry } from "emdash";
const { entries, nextCursor, cacheHint } = await getEmDashCollection("posts", {
	limit: 10,
	cursor,
	orderBy: { published_at: "desc" },
});
const { entry: post, cacheHint: entryCacheHint } = await getEmDashEntry("posts", slug);

// Site features
import {
	getSiteSettings,
	getSiteSettingsWithCacheHint,
	getMenu,
	getMenuWithCacheHint,
	getTaxonomyTerms,
	getTaxonomyTermsWithCacheHint,
	getTerm,
	getEntryTerms,
	getEntriesByTerm,
	getWidgetArea,
	getWidgetAreaWithCacheHint,
	search,
	getSection,
	getSeoMeta,
} from "emdash";

// Bylines (standalone queries -- usually not needed since entries have bylines attached)
import { getByline, getBylineBySlug } from "emdash";

// UI components
import {
	PortableText,
	Blocks,
	defineBlockComponents,
	Image,
	WidgetArea,
	EmDashHead,
	EmDashBodyStart,
	EmDashBodyEnd,
} from "emdash/ui";
import { Comments, CommentForm } from "emdash/ui/comments";
import LiveSearch from "emdash/ui/search";

// Page context (for plugin contributions)
import { createPublicPageContext } from "emdash/page";

Plugins

Plugins

EmDash supports plugins for extending the CMS with hooks, storage, settings, admin UI, API routes, and custom Portable Text block types. Consider a plugin when you need to:
  • React to content lifecycle events (e.g., send a notification on publish, sync to an external service)
  • Add custom admin pages or dashboard widgets
  • Add reusable behavior or custom Portable Text types (e.g., embedded maps or code playgrounds)
  • Provide a reusable service (e.g., analytics, forms, comments via a third-party provider)
Plugins are registered in
astro.config.mjs
:
javascript
emdash({
	database: sqlite({ url: "file:./data.db" }),
	storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }),
	plugins: [myPlugin()],
}),
To build a plugin, load the
creating-plugins
skill
(in
.agents/skills/creating-plugins/
). It covers plugin anatomy, hooks, storage, admin UI, API routes, Portable Text blocks, capabilities, and the full
definePlugin()
API.
EmDash 支持通过 plugins 扩展 CMS,包括 hooks、storage、settings、admin UI、API routes 和 custom Portable Text block types。当你需要执行以下操作时,请考虑使用 plugin:
  • 响应 content lifecycle events(例如,在 publish 时发送通知、与外部服务同步)
  • 添加 custom admin pages 或 dashboard widgets
  • 添加 reusable behavior 或 custom Portable Text types(例如嵌入式 maps 或 code playgrounds)
  • 提供 reusable service(例如 analytics、forms、通过第三方提供商实现的 comments)
Plugins 在
astro.config.mjs
中注册:
javascript
emdash({
	database: sqlite({ url: "file:./data.db" }),
	storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }),
	plugins: [myPlugin()],
})
如需构建 plugin,请加载
creating-plugins
skill
(位于
.agents/skills/creating-plugins/
)。它涵盖 plugin anatomy、hooks、storage、admin UI、API routes、Portable Text blocks、capabilities 以及完整的
definePlugin()
API。

Reference Documents

参考文档

FileContents
references/configuration.mdProject setup, astro.config, live.config, deployment, types
references/schema-and-seed.mdCollections, fields, taxonomies, menus, widgets, seed format
references/querying-and-rendering.mdContent APIs, PortableText, Image, caching, page patterns
references/site-features.mdSettings, menus, widgets, search, SEO, comments, page contributions
文件内容
references/configuration.md项目设置、astro.config、live.config、deployment、types
references/schema-and-seed.mdCollections、fields、taxonomies、menus、widgets、seed format
references/querying-and-rendering.mdContent APIs、PortableText、Image、caching、page patterns
references/site-features.mdSettings、menus、widgets、search、SEO、comments、page contributions