Astro 開発者向け EmDash

このページ

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
コンテンツ形式フロントマターを持つ MarkdownPortable 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 });
}
---

次のステップ