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 存储在数据库中(而不是代码中),通过实时 content collections 提供内容,并在
/_emdash/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. 图片字段是对象,而不是字符串。
    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 时,必须注册 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
、部署目标(Node 与 Cloudflare)以及类型生成。

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 定义、字段类型、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,了解内容查询、Portable Text 渲染、Image 组件、可视化编辑属性、缓存,以及常见页面模式(列表、详情、taxonomy 归档、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,了解站点设置、导航菜单、taxonomies、widget areas、搜索、SEO meta、comments 和页面贡献。

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
运行时会在首次请求时执行待处理的 migration,并在 setup 完成前应用一次 seed 的 schema 和结构。示例内容需要显式应用;请参阅应用 Seeds。服务器启动时,Astro integration 会生成
emdash-env.d.ts
。管理 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 支持通过 hooks、storage、settings、admin UI、API routes 和自定义 Portable Text block types 扩展 CMS。出现以下需求时,可以考虑使用 plugin:
  • 响应内容生命周期事件(例如,在发布时发送通知、同步到外部服务)
  • 添加自定义管理页面或 dashboard widgets
  • 添加可复用行为或自定义 Portable Text 类型(例如,嵌入式地图或代码 playground)
  • 提供可复用服务(例如,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 结构、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
、部署、类型
references/schema-and-seed.mdCollections、fields、taxonomies、menus、widgets、seed 格式
references/querying-and-rendering.md内容 APIs、PortableText、Image、缓存、页面模式
references/site-features.md设置、menus、widgets、搜索、SEO、comments、页面贡献