本文说明如何在 Elaine Blog 中创建和管理文章。按照这些规则,你可以控制文章的访问地址、发布时间、目录结构、图片和发布状态。
目录
创建文章
在 src/content/posts/ 中创建 .md 或 .mdx 文件。正式文章的文件名不能以 _ 开头,否则 Astro 不会加载它。
以下示例创建一篇 Markdown 文章:
src/content/posts/my-first-post.md
文章至少需要包含标题、发布时间、分类和摘要:
---
title: 我的第一篇博客
pubDatetime: 2026-08-27T10:00:00+08:00
category: docs
description: 介绍这篇博客的主要内容。
---
## 前言
从这里开始编写正文。
构建完成后,这篇文章的访问地址为:
/posts/my-first-post/
不要使用 _my-first-post.md 作为正式文章文件名。当前内容加载规则会忽略所有以 _ 开头的 Markdown 和 MDX 文件。
选择 Markdown 或 MDX
普通文章使用 .md。需要导入 Astro 组件或编写 JSX 表达式时,使用 .mdx。
| 格式 | 适用场景 | 是否支持导入组件 |
|---|---|---|
.md | 标题、列表、表格、图片和代码块等普通文章 | 否 |
.mdx | 自定义组件、交互内容和复杂布局 | 是 |
下面的 MDX 示例导入了项目组件:
---
title: MDX 示例
pubDatetime: 2026-08-27T10:00:00+08:00
category: docs
description: 展示如何在文章中使用 Astro 组件。
---
import ResponsiveTable from "@/components/ResponsiveTable.astro";
<ResponsiveTable variant="striped-minimal">
| 名称 | 说明 |
| ----- | ------------ |
| Astro | 静态网站框架 |
</ResponsiveTable>
组织文章目录
你可以将文章直接放在 posts 下,也可以使用多级子目录。普通目录名会成为文章 URL 的一部分。
| 文件位置 | 访问地址 | 是否加载 |
|---|---|---|
posts/hello.md | /posts/hello/ | 是 |
posts/java/spring.md | /posts/java/spring/ | 是 |
posts/_java/spring.md | /posts/spring/ | 是 |
posts/java/_legacy/spring.md | /posts/java/spring/ | 是 |
posts/_demo/_hello.md | 无 | 否 |
以 _ 开头的目录只会从 URL 中移除目录名,不会自动隐藏目录里的文章。只有文章文件名以 _ 开头时,内容加载器才会忽略该文件。
当前项目将原始示例文章保存在:
src/content/posts/_demo/
该目录中的文章文件全部以 _ 开头,因此不会出现在首页、文章列表、标签、RSS 或文章详情页中。
如果目录只用于整理文件,不希望目录名出现在 URL 中,请给目录名添加 _ 前缀。如果希望隐藏文章,请同时给文章文件名添加 _ 前缀。
不同文件不能生成相同的 URL。例如,以下两个文件都会尝试生成 /posts/hello/:
posts/_java/hello.md
posts/_astro/hello.md
创建文章前,请检查文件名和 slug,避免 URL 冲突。
配置 Frontmatter
Frontmatter 位于文章顶部的两组 --- 之间,用来定义文章元数据。
必填字段
| 字段 | 类型 | 说明 |
|---|---|---|
title | 字符串 | 文章标题 |
pubDatetime | 日期 | ISO 8601 格式的发布时间 |
category | 字符串 | 文章所属的分类 ID,通常填写叶子分类 |
description | 字符串 | 用于文章列表、搜索和 SEO 的摘要 |
可选字段
| 字段 | 类型 | 默认值或作用 |
|---|---|---|
slug | 字符串 | 覆盖文件名生成的 URL |
author | 字符串 | 默认使用站点作者 Elaine |
modDatetime | 日期 | 文章最后修改时间 |
featured | 布尔值 | 是否显示在首页精选文章区域 |
draft | 布尔值 | 设置为 true 时不发布文章 |
tags | 字符串数组 | 默认使用 others 标签 |
ogImage | 图片或 URL | 社交平台分享预览图 |
canonicalURL | URL | 文章在其他网站的原始地址 |
hideEditPost | 布尔值 | 是否隐藏文章编辑入口 |
timezone | 字符串 | 覆盖站点默认时区 |
推荐使用以下完整模板:
---
title: 文章标题
slug: article-slug
author: Elaine
pubDatetime: 2026-08-27T10:00:00+08:00
modDatetime:
featured: false
draft: false
category: docs
tags:
- Astro
- 博客
description: 用一句话说明文章解决的问题或介绍的内容。
---
## 前言
在这里编写正文。
设置稳定的 URL
如果文件名可能变化,设置 slug 保持访问地址稳定:
slug: astro-blog-guide
对应的访问地址为:
/posts/astro-blog-guide/
文章发布后尽量不要修改 slug。修改后,旧链接将无法访问,除非额外配置重定向。
使用多级分类
分类统一配置在 src/data/categories.ts。每个分类使用稳定的英文 ID,并通过 parent 指向父分类;不设置 parent 的分类是一级分类。
export const categoryIds = ["knowledge", "docs", "astro"] as const;
export const categories = {
knowledge: {
name: "知识库",
description: "系统整理和持续维护的知识内容。",
},
docs: {
name: "文档编写",
description: "Markdown、MDX 与内容发布指南。",
parent: "knowledge",
},
astro: {
name: "Astro",
description: "Astro 开发与博客建设实践。",
parent: "docs",
},
};
以上配置形成“知识库 → 文档编写 → Astro”三级结构。文章只需填写它直接所属的分类 ID:
category: astro
分类页面会自动生成完整路径:
/categories/knowledge/
/categories/knowledge/docs/
/categories/knowledge/docs/astro/
分类页面采用逐级导航:一级分类页只显示直属二级分类,二级分类仍有子分类时继续显示直属三级分类;只有没有子分类的分类页才显示文章。父分类卡片上的文章数量会统计整个分支。
同一个分类不能同时拥有直属文章和子分类。例如 docs 已经包含 astro 子分类后,文章就不能再使用 category: docs,必须放入 astro 或另一个叶子分类。构建程序会检查这一规则并报告冲突文章。
文章可以直接属于一级、二级或三级分类,前提是该分类没有子分类。分类 ID 必须全局唯一;移动分类时只需修改 parent,文章中的 category 不需要改动。
管理文章状态和排序
使用 draft、pubDatetime 和 modDatetime 控制文章是否发布以及文章的排列顺序。
保存草稿
如果文章还不能发布,将 draft 设置为 true:
draft: true
草稿不会生成文章详情页,也不会出现在首页、列表、标签或 RSS 中。
设置定时发布
将 pubDatetime 设置为未来时间,可以创建定时文章:
pubDatetime: 2026-09-01T09:00:00+08:00
开发环境会显示所有非草稿文章,便于预览。生产构建只包含已经到达发布时间的文章,并应用项目配置的发布时间容差。
控制排序
文章列表默认使用 date-desc,按时间倒序排列:
- 如果存在
modDatetime,使用最后修改时间排序。 - 如果没有
modDatetime,使用发布时间排序。 - 时间越新的文章越靠前。
连续教程或系列文章可以在分类配置中使用 filename-asc:
"llm-basic": {
name: "LLM 基础",
description: "理解大语言模型原理、API 形态和生产工程基础。",
parent: "agent-runtime",
postSort: "filename-asc",
}
filename-asc 会读取文件名开头的数字,并在分类分页前按数字从小到大排序:
01-token与tokenizer.md
02-transformer与attention.md
03-上下文窗口与长上下文.md
数字后可以使用 -、_ 或 .。没有数字前缀的文件会排在编号文章之后。文件名编号只控制分类文章列表,不改变首页、归档和全站文章列表的时间排序。
设置 featured: true 后,文章还会显示在首页精选文章区域。
添加图片
你可以使用远程图片,也可以将图片放在文章附近并使用相对路径引用。
使用远程图片

使用本地图片
使用以下目录结构保存文章和图片:
src/content/posts/tutorial/
├── astro-guide.md
└── assets/
└── astro-cover.png
在 astro-guide.md 中使用相对路径:

也可以将图片设置为文章分享图:
ogImage: ./assets/astro-cover.png
每张图片都应包含有意义的替代文本。不要使用“图片”或“截图”等无法描述内容的文字。
添加图表
新的正式文章优先使用 D2,部署时会自动生成深浅色 SVG。Mermaid 适合临时草图和少量必须在 Markdown 内维护的简单图。
| 图表类型 | 推荐工具 | 原因 |
|---|---|---|
| 临时草图、文内示例 | Mermaid | 代码块可直接渲染 |
| 时序图、状态图 | D2 | 部署时生成 SVG,展示更稳定 |
| 多阶段流程、系统架构 | D2 | 支持网格和 ELK 布局 |
| 需要精确位置的图 | SVG | 可以完全控制位置和比例 |
使用 Mermaid
在 Markdown 或 MDX 中添加 mermaid 代码块:
```mermaid
sequenceDiagram
participant App as 应用
participant Model as 模型
App->>Model: 提交请求
Model-->>App: 返回结果
```
文章页面只在检测到 Mermaid 代码块时加载渲染库。图表支持深浅色主题和全屏缩放预览。
使用 D2
D2 源文件和生成的 SVG 使用相同的相对路径:
src/diagrams/agent-runtime/llm-basic/token-processing.d2
public/diagrams/agent-runtime/llm-basic/token-processing.svg
public/diagrams/agent-runtime/llm-basic/token-processing-dark.svg
创建或修改 .d2 文件后,可以在本机预先生成图表:
pnpm diagrams:build
本机预览命令需要安装 d2。macOS 可以使用 Homebrew 安装:
brew install d2
在文章中引用生成的 SVG:

只修改 src/diagrams/ 中的源文件,不要手动修改 public/diagrams/ 中的 SVG。生成命令会同时输出浅色版 .svg 和深色版 -dark.svg,博客会根据当前主题自动切换。pnpm run build 会比较源文件哈希与两个 SVG 中的标记。
运行 ../elaine-cicd/dev/build-elaine-blog.sh 时,Docker 会安装固定版本的 D2,先执行 pnpm diagrams:build,再构建 Astro。因此部署不依赖本机的 D2,也不会使用过期的 SVG。D2 不直接解析 Mermaid 语法;需要迁移时,先将 Mermaid 图改写为 .d2 源文件。
检查并发布文章
在项目目录中执行以下步骤:
-
启动开发服务器并预览文章:
pnpm run dev -
检查文章标题、摘要、时间、标签、图片和链接。
-
运行生产构建:
pnpm run build -
确认构建结果中没有内容校验错误。
-
使用本地部署脚本重新构建并启动 Docker 容器:
cd ../elaine-cicd/dev ./build-elaine-blog.sh
如果需要修改本地端口,在运行部署脚本时传入 ELAINE_BLOG_PORT:
ELAINE_BLOG_PORT=9000 ./build-elaine-blog.sh
下一步
- 在
src/content/posts/下创建一篇不以_开头的文章。 - 使用
draft: true完成内容预览,再切换为draft: false。 - 发布前运行
pnpm run build,检查 Frontmatter、链接和本地图片。