EmDash は Astro 専用に構築された CMS です。データベースベースのコンテンツ、洗練された管理 UI、WordPress スタイルの機能(メニュー、ウィジェット、タクソノミー)で Astro サイトを拡張しながら、期待する開発者体験を保持します。
Astro について知っていることはすべて引き続き適用されます。EmDash は既存の Astro ワークフローの上にコンテンツ管理を追加します。
EmDash が追加するもの
EmDash は、ファイルベースの Astro サイトに欠けているコンテンツ管理機能を提供します:
| 機能 | 説明 |
|---|---|
| 管理 UI | /_emdash/admin での完全な WYSIWYG 編集インターフェース |
| データベースストレージ | SQLite、libSQL、Cloudflare D1、または PostgreSQL に保存されるコンテンツ |
| メディアライブラリ | 画像とファイルのアップロード、整理、配信 |
| ナビゲーションメニュー | ネスト対応のドラッグ&ドロップメニュー管理 |
| ウィジェットエリア | 動的サイドバーとフッター領域 |
| サイト設定 | グローバル設定(タイトル、ロゴ、ソーシャルリンク) |
| タクソノミー | カテゴリー、タグ、カスタムタクソノミー |
| プレビューシステム | 下書きコンテンツ用の署名付きプレビュー URL |
| リビジョン | コンテンツバージョン履歴 |
Astro コレクション vs EmDash
Astro の astro:content コレクションはファイルベースで、ビルド時に解決されます。EmDash コレクションはデータベースベースで、実行時に解決されます。
| Astro コレクション | EmDash コレクション | |
|---|---|---|
| ストレージ | src/content/ 内の Markdown/MDX ファイル | SQL データベース(SQLite、libSQL、D1、または Postgres) |
| 編集 | コードエディタ | 管理 UI |
| コンテンツ形式 | フロントマターを持つ Markdown | Portable Text(構造化 JSON) |
| 更新 | 再ビルドが必要 | 即座(SSR) |
| スキーマ | content.config.ts 内の Zod | 管理画面で定義され、データベースに保存 |
| 最適な用途 | 開発者管理のコンテンツ | 編集者管理のコンテンツ |
両方を一緒に使用
Astro コレクションと EmDash は共存できます。開発者コンテンツ(ドキュメント、変更履歴)には Astro コレクション、編集者コンテンツ(ブログ投稿、ページ)には EmDash を使用します:
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";
// Developer-managed docs from files
const docs = await getCollection("docs");
// Editor-managed posts from database
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
limit: 5,
});
---
設定
EmDash には 2 つの設定ファイルが必要です。
Astro 統合
次の設定は、サーバー出力モードで EmDash を Astro 統合として登録します:
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
output: "server", // Required for EmDash
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
ライブコレクションローダー
次のファイルは EmDash をライブコンテンツソースとして登録します:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
_emdash コレクションは、内部的にコンテンツタイプ(投稿、ページ、製品)にルーティングします。
コンテンツのクエリ
EmDash は、Astro のライブコンテンツコレクションパターンに従うクエリ関数を提供し、{ entries, error } または { entry, error } を返します:
EmDash
import { getEmDashCollection, getEmDashEntry } from "emdash";
// Get all published posts - returns { entries, error }
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
// Get a single post by slug - returns { entry, error, isPreview }
const { entry: post } = await getEmDashEntry("posts", "my-post");
Astro
import { getCollection, getEntry } from "astro:content";
// Get all blog entries
const posts = await getCollection("blog");
// Get a single entry by slug
const post = await getEntry("blog", "my-post"); フィルタリングオプション
getEmDashCollection は、Astro の getCollection が提供しないフィルタリングをサポートします:
const { entries: posts } = await getEmDashCollection("posts", {
status: "published", // draft | published | archived
limit: 10, // max results
where: { category: "news" }, // taxonomy filter
});
コンテンツのレンダリング
EmDash はリッチテキストを Portable Text(構造化 JSON 形式)として保存します。PortableText コンポーネントでレンダリングします:
EmDash
---
import { getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug);
if (!post) {
return Astro.redirect("/404");
}
---
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article> Astro
---
import { getEntry, render } from "astro:content";
const { slug } = Astro.params;
const post = await getEntry("blog", slug);
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article> 動的機能
EmDash は、Astro のコンテンツレイヤーには存在しない WordPress スタイルの機能用の API を提供します。
ナビゲーションメニュー
次のレイアウトは、場所別にメニューを取得し、ネストされた項目でレンダリングします:
---
import { getMenu } from "emdash";
const primaryMenu = await getMenu("primary");
---
{primaryMenu && (
<nav>
<ul>
{primaryMenu.items.map(item => (
<li>
<a href={item.url}>{item.label}</a>
{item.children.length > 0 && (
<ul>
{item.children.map(child => (
<li><a href={child.url}>{child.label}</a></li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
)}
ウィジェットエリア
次のレイアウトは、ウィジェットエリアを取得し、各ウィジェットをレンダリングします:
---
import { getWidgetArea } from "emdash";
import { PortableText } from "emdash/ui";
const sidebar = await getWidgetArea("sidebar");
---
{sidebar && sidebar.widgets.length > 0 && (
<aside>
{sidebar.widgets.map(widget => (
<div class="widget">
{widget.title && <h3>{widget.title}</h3>}
{widget.type === "content" && widget.content && (
<PortableText value={widget.content} />
)}
</div>
))}
</aside>
)}
サイト設定
次のコンポーネントは、グローバルサイト設定を読み取り、ロゴまたはタイトルをレンダリングします:
---
import { getSiteSettings, getSiteSetting } from "emdash";
const settings = await getSiteSettings();
// Or fetch individual values:
const title = await getSiteSetting("title");
---
<header>
{settings.logo ? (
<img src={settings.logo.url} alt={settings.title} />
) : (
<span>{settings.title}</span>
)}
{settings.tagline && <p>{settings.tagline}</p>}
</header>
プラグイン
フック、ストレージ、設定、管理 UI を追加するプラグインで EmDash を拡張します:
import emdash from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";
export default defineConfig({
integrations: [
emdash({
// ...
plugins: [seoPlugin({ generateSitemap: true })],
}),
],
});
definePlugin でカスタムプラグインを作成します:
import { definePlugin } from "emdash";
export default definePlugin({
id: "analytics",
version: "1.0.0",
capabilities: ["content:read"],
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved", { id: event.content.id });
},
},
admin: {
settingsSchema: {
trackingId: { type: "string", label: "Tracking ID" },
},
},
});
サーバーレンダリング
EmDash サイトは SSR モードで実行されるため、コンテンツは実行時に配信され、変更は即座に表示されます。
getStaticPaths を使用した静的ページの場合、コンテンツはビルド時に取得されます:
---
import { getEmDashCollection, getEmDashEntry } from "emdash";
export async function getStaticPaths() {
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
return posts.map((post) => ({
params: { slug: post.data.slug },
}));
}
const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug);
---
動的ページの場合は、prerender = false を設定して、リクエストごとにコンテンツを取得します:
---
export const prerender = false;
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Server error", { status: 500 });
}
if (!post) {
return new Response(null, { status: 404 });
}
---
次のステップ
Getting Started
最初の EmDash サイトを作成5分未満で。
Querying Content
クエリ API を学ぶ詳細に。
Create a Blog
完全なブログを構築カテゴリーとタグ付き。
Deploy to Cloudflare
サイトを本番環境に展開 Workers 上で。