このページで分かること

  • EmDashがAstroのサイトに加える機能(管理画面、データベースのコレクション、メディアライブラリ、下書き・リビジョン・プレビュー、メニュー、ウィジェットエリア、サイト設定、プラグイン)
  • Astroのコンテンツコレクションとの違いと、両方を1つのサイトで併用する方法
  • 必要な設定(output: "server"、EmDashインテグレーション、_emdash ライブコレクション)と、コレクション・単一エントリーの取得方法
難易度
実践
読む時間
3分
このページの目次

EmDashは、Astroのサイトに、管理アプリケーション、データベースに保存するコレクション、メディア、メニュー、タクソノミー、設定、リビジョン、プレビューを追加します。ページとコンポーネントは、通常のAstroのファイルのままです。

EmDashが追加するもの

機能 提供するもの
管理画面 /_emdash/admin で、コレクション、メディア、メニュー、タクソノミー、設定をブラウザーから管理する機能
データベースのコレクション 編集者が管理し、リクエスト時に取得するコンテンツ
メディアライブラリ 保存された画像やファイルと、テンプレートで使うメディアフィールドの値
下書き、リビジョン、プレビュー 公開前の編集作業
メニューとウィジェットエリア エントリーのフィールドとは別に、順序を持ち編集できるサイトの領域
サイト設定 タイトル、キャッチフレーズ、ロゴ、1ページあたりの件数など、サイト共通の基本情報と表示用の値
プラグイン フック、ルート、ストレージ、必要に応じた管理画面の拡張

これらの機能は、Astroを置き換えるのではなく、Astroと並んで動きます。ルーティング、レイアウト、描画、スタイル、デプロイ用のアダプターは、引き続きAstroが受け持ちます。

EmDashのコレクションとAstroのコレクション

Astroのコンテンツコレクションと、EmDashのコレクションは共存できます。リポジトリで管理するコンテンツにはAstroのコレクションを、/_emdash/admin で管理するコンテンツにはEmDashを使います。

Astroのコンテンツコレクション EmDashのコレクション
保存先 プロジェクト内のファイル SQLデータベース
編集 リポジトリのワークフロー EmDashの管理画面
クエリ getCollection() getEmDashCollection()
リッチテキスト MarkdownまたはMDX Portable Text
配信 ビルド時のローダーまたはライブローダー 実行時のライブローダー

管理する人が異なる場合は、両方のコレクションの仕組みを使います。たとえば製品サイトでは、開発者が書くリリースノートをAstroのコンテンツコレクションに置き、編集者が書く記事をEmDashに置けます。

src/pages/index.astro
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";

const [releaseNotes, { entries: articles }] = await Promise.all([
  getCollection("releases"),
  getEmDashCollection("articles", { limit: 3 }),
]);
---

2つの結果は別々のままです。EmDashは、ファイルで管理するエントリーを自分のデータベースにコピーしません。

本サイトの補足 やさしい解説

WordPressでいえば、EmDashのコレクションは管理画面で編集する投稿タイプにあたります。一方、Astroのコンテンツコレクションは、リポジトリの中のMarkdownファイルなどで管理するコンテンツです。1つのサイトで両方を使えるため、開発者が書くもの(リリースノートなど)はファイルで、編集者が書くもの(記事など)はEmDashで、と分けられます。2つは別々に取得し、EmDashがファイルの内容をデータベースに取り込むことはありません。

サイトの設定

現在のNode向けのテンプレートは、Astroをサーバー出力に設定し、EmDashのインテグレーションを追加して、SQLiteとローカルストレージのアダプターを使います。

次の最小限の設定には、必要な部分が含まれています。

astro.config.mjs
import node from "@astrojs/node";
import react from "@astrojs/react";
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";

export default defineConfig({
  output: "server",
  adapter: node({ mode: "standalone" }),
  integrations: [
    react(),
    emdash({
      database: sqlite({ url: "file:./data.db" }),
      storage: local({
        directory: "./uploads",
        baseUrl: "/_emdash/api/media/file",
      }),
    }),
  ],
});

EmDashには、D1とR2向けに設定されたCloudflare用のテンプレートもあります。Node用のアダプターを手で書き換えるのではなく、デプロイ先に合ったテンプレートから始めます。

ライブコレクションの登録

テンプレートは、_emdash という名前の1つのAstroのライブコレクションを通して、EmDashのコンテンツを公開します。

src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";

export const collections = {
  _emdash: defineLiveCollection({ loader: emdashLoader() }),
};

getEmDashCollection()getEmDashEntry() は、このローダーを通して、指定したコンテンツタイプを選びます。

コレクションの取得

次のクエリは、最近公開された投稿を読み込みます。orderBy には保存されているフィールド名を使い、それぞれの名前に "asc" または "desc" を指定します。

src/pages/posts/index.astro
---
import { getEmDashCollection } from "emdash";

const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
  orderBy: { published_at: "desc" },
  limit: 10,
});

if (error) return new Response("Could not load posts", { status: 500 });
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

{posts.map((post) => (
  <article>
    <h2><a href={`/posts/${post.id}`}>{post.data.title}</a></h2>
  </article>
))}

未ログインのクエリは、公開済みのコンテンツを返します。明示的な status の絞り込みは、認証済みのコードやプレビューに対応するコードで役立ちます。where には、コンテンツのフィールドとタクソノミーの名前を指定できます。絞り込みとページ分割の完全な形については、コンテンツの取得を参照します。

1件のエントリーの取得

getEmDashEntry() には、スラッグまたはデータベースのIDを渡します。次のルートでは、URLのスラッグを使います。

src/pages/posts/[slug].astro
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");

const { entry: post, error, cacheHint } = await getEmDashEntry("posts", slug);
if (error) return new Response("Could not load the post", { status: 500 });
if (!post) return Astro.redirect("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

<article>
  <h1>{post.data.title}</h1>
  <PortableText value={post.data.content} />
</article>

返される entry.id はAstroのルートの識別子で、通常はスラッグです。データベース上のコンテンツのIDは entry.data.id です。保存されたコンテンツのIDを必要とするヘルパーには、data.id を使います。

CMSの動的な機能の利用

EmDashは、1つのコレクションのエントリーに属さないデータのために、サーバー用のヘルパーをエクスポートしています。

src/layouts/Base.astro
---
import { getMenu, getSiteSettings } from "emdash";
import { WidgetArea } from "emdash/ui";

const [menu, settings] = await Promise.all([
  getMenu("primary"),
  getSiteSettings(),
]);
---

<header>
  <a href="/">{settings.title}</a>
  <nav>
    {menu?.items.map((item) => <a href={item.url}>{item.label}</a>)}
  </nav>
</header>

<main><slot /></main>
<aside><WidgetArea name="sidebar" /></aside>

プラグイン形式の選択

サンドボックス型プラグインとネイティブ型プラグインでは、パッケージの形が異なります。サンドボックス型プラグインは、emdash-plugin.jsonc と、デフォルトエクスポートした src/plugin.ts のオブジェクトを使います。ネイティブ型プラグインは、記述子のファクトリーと、definePlugin() で作った createPlugin() をエクスポートします。

プラグインを追加する前に、プラグイン形式の選び方を読みます。ネイティブ型の definePlugin() の例を、サンドボックス型のパッケージにコピーしないでください。

次のステップ

  • コンテンツを取得する:絞り込み、ページ分割、ロケール、キャッシュのヒントを使います。
  • テーマを作成する:Astroのサイトとそのシードファイルを、再利用できるテンプレートとしてまとめます。