EmDash 博客模板深度指南:基于 Astro 的全栈 CMS 站点搭建与定制
2026/9/24 7:45:56 网站建设 项目流程
  • CMS
  • 后端
  • 前端
  • 插件系统

【免费下载链接】emdash

EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress

项目地址:https://gitcode.com/gh_mirrors/emdas/emdash
点击查看免费下载

EmDash 是一个基于 Astro 构建的全栈 TypeScript CMS,被视为 WordPress 的精神继承者。本文以仓库内的 博客模板 为骨架,系统讲解该模板的目录结构、开发命令、内容 Schema、页面路由、视觉体系与定制方法,并结合模板源码与 seed 数据给出可运行的实战细节。读完本文,你将能够独立启动、扩展并重新设计一个具备全文搜索、RSS、分类/标签、评论与多作者署名能力的 EmDash 博客站点。

模板定位:一个“以写作为产品”的博客

博客模板 在仓库中定位为“A blog with posts, pages, categories, tags, full-text search, and RSS”,即一个内置文章、页面、分类、标签、全文搜索与 RSS 订阅的完整博客,目标场景是个人写作、技术写作、独立 newsletter,以及任何“内容本身就是产品”的站点。它的视觉取向是“编辑/社论式美学”(editorial-tech aesthetic):自信的无衬线字体、克制的强调色、真实的文章结构(署名与阅读时长)。

整个模板由三部分构成:Astro 配置与集成(astro.config.mjs)、内容与 Schema 定义(seed/seed.json)、以及前端页面与样式(src/)。下面依次展开。

快速开始:两条命令与后台入口

模板的日常开发只需要两条命令,定义在 package.json 的 scripts 中:

pnpm dev # 启动 Astro 开发服务器 npx emdash types # 根据运行中的站点重新生成 TypeScript 类型
  • pnpm dev实际执行astro dev,启动 Astro 开发服务器。astro.config.mjsdevToolbar: { enabled: false }关闭了 Astro 自带的开发工具栏,避免与 EmDash 管理后台冲突。
  • npx emdash typesemdash包提供,会根据当前运行中的站点(读取数据库中的集合与字段)重新生成 emdash-env.d.ts 类型声明。开发服务器启动时会自动触发一次类型生成,因此该文件属于“自动生成物”,不建议手改。

管理后台地址为http://localhost:4321/_emdash/admin。登录后即可创建文章、页面、管理分类/标签与菜单。在 Base.astro 中,模板通过Astro.locals.user判断登录状态,登录用户会在导航栏看到 Admin 入口链接。

关键文件一览

模板的 AGENTS 文档用一张表概括了核心文件职责,结合源码整理如下:

文件用途
astro.config.mjsAstro 配置:emdash()集成、数据库(SQLite)、存储(本地目录)、字体与插件
src/live.config.tsEmDash loader 注册(样板代码,勿修改)
seed/seed.jsonSchema 定义 + 演示内容(集合、字段、分类法、菜单、widget)
emdash-env.d.ts集合类型(开发服务器启动时自动重新生成)
src/layouts/Base.astro基础布局:菜单、搜索、页面贡献(page contributions)
src/pages/Astro 页面,全部服务端渲染

其中 src/live.config.ts 是整个模板与 EmDash 数据层之间的桥梁,全文只有几行:

import { defineLiveCollection } from "astro:content"; import { emdashLoader } from "emdash/runtime"; export const collections = { _emdash: defineLiveCollection({ loader: emdashLoader() }), };

它通过 Astro Content Collections 的 live 机制暴露_emdash集合,页面里再通过getEmDashCollection()/getEmDashEntry()查询具体内容类型。该文件被注释明确标注为“boilerplate -- don't modify”,日常开发不应改动。

Astro 配置详解:集成、数据库与存储

astro.config.mjs 是模板运行的枢纽,值得逐段解读:

import node from "@astrojs/node"; import react from "@astrojs/react"; import auditLog from "@emdash-cms/plugin-audit-log"; import { defineConfig, fontProviders } from "astro/config"; import emdash, { local } from "emdash/astro"; import { sqlite } from "emdash/db"; export default defineConfig({ output: "server", adapter: node({ mode: "standalone" }), image: { layout: "constrained", responsiveStyles: true, }, integrations: [ react(), emdash({ database: sqlite({ url: "file:./data.db" }), storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file", }), plugins: [auditLog], }), ], fonts: [ /* ... */ ], devToolbar: { enabled: false }, });
  • output: "server"+@astrojs/node(standalone 模式):站点以 Node 服务方式运行,所有 CMS 内容页面服务端渲染——这与 AGENTS 文档中“All content pages must be server-rendered”的硬性规则一致,模板中没有任何getStaticPaths()
  • emdash({ database, storage, plugins }):核心集成。database使用sqlite({ url: "file:./data.db" }),即本地 SQLite 文件;storage使用local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }),上传的图片落在./uploads目录,通过/ _emdash/api/media/file路由对外提供访问;plugins: [auditLog]挂载了审计日志插件(@emdash-cms/plugin-audit-log,仓库内工作区包)。
  • image.layout: "constrained":图片组件默认使用 constrained 布局并输出响应式样式。

内容模型:Seed 文件即 Schema

seed/seed.json 既是 Schema 定义也是演示数据源,其顶层结构为metasettingscollectionstaxonomiesbylinesmenuswidgetAreassectionscontent

集合(Collections)

模板定义了两个集合,与 AGENTS 文档描述的 Schema 一一对应:

  • poststitle(string,必填、可搜索)、featured_image(image)、content(portableText,可搜索)、excerpt(text);supports开启draftsrevisionssearchseo,且commentsEnabled: true(文章页渲染评论区)。
  • pagestitlecontent(portableText),supportsdraftsrevisionssearch,用于/about等静态页面。

值得注意:supports: ["search"]与字段上的searchable: true共同决定了全文搜索索引范围——标题与正文被标记为可搜索,这正是/search页面与头部 LiveSearch 的数据基础。

分类法(Taxonomies)

  • categoryhierarchical: true(层级分类),作用于posts,内置developmentdesignnotes三个术语。
  • taghierarchical: false(扁平标签),作用于posts,内置webdevopiniontoolscreativity四个术语。

AGENTS 文档中的一条重要规则:“Taxonomy names in queries must match the seed's"name"field exactly (e.g.,"category"not"categories")”,即查询时分类名必须与 seed 中name字段完全一致。

菜单与 Widget 区

  • 单个primary菜单,默认包含 Home(/)、About(/pages/about)、Posts(/posts)三个custom类型条目。
  • widgetAreas定义了两个区域:
    • sidebar(文章单页右侧栏):挂载core:searchcore:categoriescore:tagscore:recent-postscount: 5showDate: true)、core:archivestype: "monthly"limit: 6)五个组件 widget;
    • footer(页脚):一个content类型的“About”介绍块,正文以 Portable Text 块结构(_type: "block"+childrenspan)存储。
  • sections预置了两个可复用内容块:newsletter-signup(newsletter 订阅 CTA)与about-author(作者简介),均为source: "theme"的 Portable Text 内容。

演示内容与 Bylines

content.posts内置 6 篇已发布文章与 1 篇status: "draft"的草稿(work-in-progress,不会出现在公开列表)。文章通过featured_image.$media引用外部图片并携带 alt 与 filename;bylines数组把文章与作者身份关联,seed 预置了emdash-editorialisGuest: trueguest-contributor两个署名。每篇文章的taxonomies{ "category": [...], "tag": [...] }形式绑定分类与标签。

页面路由:从首页到 RSS 的完整地图

AGENTS 文档的 Pages 表格定义了全部路由,模板中对应 src/pages/ 下的文件:

页面路径文件展示内容
首页/index.astro置顶文章 hero(大图 + 摘要)+ 最新文章网格
全部文章/postsposts/index.astro文章计数、带摘要与标签 chip 的完整列表
文章详情/posts/[slug]posts/[slug].astro头图、标题、正文、左侧元信息列(作者+日期)、右侧 TOC+搜索+分类栏
搜索/searchsearch.astro全文搜索界面
页面/pages/[slug]pages/[slug].astro静态页面内容(Portable Text)
分类/category/[slug]category/[slug].astro按分类过滤的文章
标签/tag/[slug]tag/[slug].astro按标签过滤的文章
RSS/rss.xmlrss.xml.ts生成的订阅源
404/404404.astro未找到页面

首页的查询模式

首页源码 展示了 EmDash 推荐的查询写法,几个细节值得学习:

const POSTS_PER_PAGE = 7; const [{ entries: posts, cacheHint }, settings] = await Promise.all([ getEmDashCollection("posts", { orderBy: { published_at: "desc" }, limit: POSTS_PER_PAGE + 1, // +1 用于探测是否需要 "view all" }), getSiteSettings(), ]); if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
  • 数据库分页而非 JS 截断:limit: POSTS_PER_PAGE + 1多取一条用于探测溢出,随后posts.slice(0, POSTS_PER_PAGE)裁剪,避免“取回全部文章再丢弃”的浪费。
  • cacheHint是查询返回的缓存提示,配合规则“Always callAstro.cache.set(cacheHint)on pages that query content”,页面据此设置缓存头。
  • 标签查询使用getTermsForEntries("posts", tagEntryIds, "tag")一次批量获取多篇文章的标签,注释明确说明这是为了避免对每篇文章单独调用getEntryTerms()造成 N+1 查询。

文章详情页的三栏布局

posts/[slug].astro 是模板最核心的页面,完整呈现了 AGENTS 文档强调的“三栏阅读视图”:

  • 左栏(meta-col,180px):作者署名(头像 + 姓名 + 角色标签)、发布时间、阅读时长、标签,整栏 sticky 跟随滚动。
  • 中栏(content,680px):标题、摘要、正文(<PortableText value={post.data.content} />),以及Comments/CommentForm评论组件(threaded模式,集合为posts)。
  • 右栏(gutter,200px):由 JS 从正文 h2/h3 动态构建的目录(TOC,含IntersectionObserver高亮当前章节)+<WidgetArea name="sidebar" />渲染侧边栏 widget。

页面还演示了 EmDash 的 SEO 与编辑能力:getSeoMeta(post, {...})生成 title/OG/robots/canonical;post.edit.featured_imagepost.edit.titlepost.edit.excerpt展开为可视化编辑的标记属性;Astro.params.slug需经decodeSlug()解码。

关于 id 的两条关键规则

AGENTS 文档的 Rules 中有两条极易踩坑的 id 约定,源码中反复印证:

  • entry.id是 slug(用于 URL,如/posts/${post.id});
  • entry.data.id是数据库 ULID(用于 API 调用,如getTermsForEntries传入的post.data.id)。

首页与详情页的代码严格遵循这一区分:链接拼接用post.id,批量查询标签用post.data.id

基础布局:菜单、搜索与主题切换

Base.astro 是所有页面的外壳,其数据获取集中展示了 EmDash 的公共 API:

const { siteTitle, siteTagline, siteLogo } = resolveBlogSiteIdentity(await getSiteSettings()); const menu = await getMenu("primary"); const socialMenu = await getMenu("social"); const { entries: pages } = await getEmDashCollection("pages");
  • getSiteSettings()读取站点设置(seed 中settings.title/settings.tagline,两者都会渲染在页头与页脚)。
  • getMenu("primary")渲染主导航;模板还预留了可选的social菜单——若 seed 定义了 social 菜单,页脚“Connect”列会展示其条目,否则只保留 RSS 链接。
  • createPublicPageContext({...})构建“公共页面上下文”,交给EmDashHead(安全渲染 SEO 元数据)、EmDashBodyStart/EmDashBodyEnd完成插件页面贡献(page contributions)。
  • 导航栏内嵌<LiveSearch collections={["posts", "pages"]} />实时搜索组件,并注册了 ⌘K / Ctrl+K 聚焦搜索框的快捷键。
  • 页脚内置三态主题切换器(light / dark / system),通过themecookie 持久化;内联脚本在<head>中提前读取 cookie 以“防闪烁”,无 cookie 时color-scheme: light dark跟随操作系统。

视觉体系:字体、颜色与版式约束

AGENTS 文档的 “Visual character” 一节定义了模板的视觉底线,其具体实现落在 tokens.css:

  • 字体:正文与标题共用Inter--font-body,字重 400/500/600/700),--font-heading默认指向 body 字体,标题层级靠字重与字距(--font-weight-heading600、--font-weight-display700)区分;JetBrains Mono--font-mono,字重 400/500)用于行内代码与代码块。两种字体都在 astro.config.mjs 的fonts:数组中通过fontProviders.google()配置加载。
  • 品牌色#0066cc--color-brand),用于链接、文章卡片标题 hover、搜索框聚焦环;配套--color-brand-hover--color-on-brand--color-brand-ring。文档明确要求“Don't add a second accent”——页面就是黑、白、一种蓝。
  • 三栏文章布局--content-width(680px,正文列)、--wide-width(1200px,最大容器)、--gutter-width(200px,右侧 TOC 栏)、--meta-col-width(180px,左侧元信息列)。文档强调桌面端绝不压平为单栏,“这个布局在传达‘这是值得阅读的内容’”。
  • 颜色双模式:所有颜色以light-dark(<light>, <dark>)定义,一份 token 同时携带明暗两套值;tokens.css还为不支持light-dark()的旧浏览器(Safari < 17.5、Chrome < 123)提供了@supports not回退。

定制指南:tokens.css 与 theme.css 的分工

模板的定制哲学是“两层 token”:

  • tokens.css承载全部设计 token 默认值,位于@layer base层,属于“勿编辑”文件;
  • theme.css是唯一的定制入口,其中声明“unlayered”(无层),因此无论源码顺序如何,theme.css 的声明永远压过@layer base的默认值,无需提升特异性。

theme.css开头的注释给出了覆盖示例:

:root { --color-brand: light-dark(#0f766e, #2dd4bf); --font-heading: "Iowan Old Style", Georgia, serif; --radius: 8px; }

关键规则:用纯色覆盖会同时改变明暗两模式;用light-dark(<light>, <dark>)覆盖才能保持两模式独立。换字体则分两步——更换加载的 webfont 改astro.config.mjsfonts:条目(绑定到cssVariable: "--font-body"的那一项),仅想用系统字体或给标题单独配字体,则在theme.css覆盖--font-body/--font-heading

文档推荐的字体备选包括 Geist、IBM Plex Sans、Public Sans(无衬线),以及 Source Serif、Crimson Pro、Lora(衬线——若切换为衬线正文,应把--font-size-base提到1.0625rem保证可读性)。

常用 token 速查

tokens.css中的核心变量(完整清单见文件):

  • 颜色:--color-brand--color-brand-hover--color-on-brand--color-brand-ring--color-bg--color-bg-subtle--color-surface--color-text--color-text-secondary--color-muted--color-border--color-border-subtle
  • 字体:--font-body--font-heading--font-mono
  • 字重:--font-weight-heading(600)/--font-weight-display(700)——切换衬线字体时可考虑调低
  • 字距:--tracking-tight/--tracking-snug/--tracking-wide/--tracking-wider
  • 布局:--content-width(680px)、--wide-width(1200px)、--gutter-width(200px)、--meta-col-width(180px)、--nav-height(64px)
  • 头像:--avatar-size-{xs,sm,md,lg}(18/20/24/32px)

Agent 技能与文档生态

模板为 AI 开发代理预置了三份技能(Skills),位于仓库根目录的skills/下,AGENTS 文档建议“在从事具体任务时加载”:

  • building-emdash-site:内容查询、Portable Text 渲染、Schema 设计、seed 文件、站点特性(菜单、widget、搜索、SEO、评论、署名)——从它开始。
  • creating-plugins:用 hooks、存储、管理 UI、API 路由与 Portable Text 块类型构建 EmDash 插件。
  • emdash-cli:内容管理、seeding、类型生成与可视化编辑流程的 CLI 命令。

此外,模板随附.mcp.json.cursor/mcp.json.vscode/mcp.json,让 Claude Code、Cursor、VS Code 自动发现 EmDash 的文档 MCP 服务器;其他工具(OpenCode、Windsurf 等)需要一次性手动配置。

底线清单:模板的“不要做什么”

AGENTS 文档以 “What not to do” 收尾,这些约束共同保护模板的编辑美学与可用性:

  • 不要引入第二种强调色或彩色区块背景——页面保持黑、白、一种蓝。
  • 不要用展示型无衬线字体(Bebas、Anton 等)替换 Inter——标题依赖字重对比而非新奇字型。
  • 桌面端不要折叠文章侧栏——它是阅读体验的一部分。
  • 不要使用套话博客文案(“Welcome to my blog”“Stay tuned for more”)——写一个真正说明这个博客主题的 tagline。
  • 不要在首页 seed 三篇雷同的占位文章——只有一篇真文章就展示一篇真文章(seed 中的 6 篇文章即示范了差异化内容)。
  • 没有配套审核计划就不要开启评论——模板默认不内置评论系统是有原因的(尽管 posts 集合已将commentsEnabled置为 true 并在详情页渲染评论区)。

小结

EmDash 博客模板是理解整个 EmDash CMS 工作方式的最佳起点:它以 seed 文件 声明 Schema 与内容,以 astro.config.mjs 完成数据库、存储、插件与字体装配,以 Base.astro 统一菜单、搜索、SEO 与主题切换,并以 tokens.css + theme.css 实现“默认克制、覆盖自由”的定制模型。围绕它,skills/下的三份技能文档与随附的 MCP 文档服务器构成了完整的开发与 AI 辅助工作流,让你在保持编辑级阅读体验的同时,快速产出真正属于自己的写作站点。

  • CMS
  • 后端
  • 前端
  • 插件系统

【免费下载链接】emdash

EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress

项目地址:https://gitcode.com/gh_mirrors/emdas/emdash
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询