EmDash für Astro-Entwickler

Auf dieser Seite

EmDash ist ein CMS, das speziell für Astro entwickelt wurde. Es erweitert Ihre Astro-Site mit datenbankgestützten Inhalten, einer ausgefeilten Admin-UI und WordPress-ähnlichen Funktionen (Menüs, Widgets, Taxonomien), während es die Entwicklererfahrung bewahrt, die Sie erwarten.

Alles, was Sie über Astro wissen, gilt weiterhin. EmDash fügt Content Management zu Ihrem bestehenden Astro-Workflow hinzu.

Was EmDash hinzufügt

EmDash bietet die Content-Management-Funktionen, die dateibasierten Astro-Sites fehlen:

FeatureBeschreibung
Admin-UIVollständige WYSIWYG-Bearbeitungsoberfläche unter /_emdash/admin
DatenbankspeicherInhalte gespeichert in SQLite, libSQL, Cloudflare D1 oder PostgreSQL
MedienbibliothekHochladen, organisieren und bereitstellen von Bildern und Dateien
NavigationsmenüsDrag-and-Drop-Menüverwaltung mit Verschachtelung
Widget-BereicheDynamische Sidebars und Footer-Bereiche
Site-EinstellungenGlobale Konfiguration (Titel, Logo, Social Links)
TaxonomienKategorien, Tags und benutzerdefinierte Taxonomien
VorschausystemSignierte Vorschau-URLs für Entwurfsinhalte
RevisionenContent-Versionsverlauf

Astro Collections vs EmDash

Astros astro:content-Collections sind dateibasiert und werden zur Build-Zeit aufgelöst. EmDash-Collections sind datenbankbasiert und werden zur Laufzeit aufgelöst.

Astro CollectionsEmDash Collections
SpeicherMarkdown/MDX-Dateien in src/content/SQL-Datenbank (SQLite, libSQL, D1 oder Postgres)
BearbeitungCode-EditorAdmin-UI
Content-FormatMarkdown mit FrontmatterPortable Text (strukturiertes JSON)
UpdatesErfordert RebuildSofort (SSR)
SchemaZod in content.config.tsIn Admin definiert, in Datenbank gespeichert
Am besten fürVon Entwicklern verwalteter InhaltVon Redakteuren verwalteter Inhalt

Beide zusammen verwenden

Astro Collections und EmDash können koexistieren. Verwenden Sie Astro Collections für Entwicklerinhalte (Docs, Changelogs) und EmDash für Redakteursinhalte (Blog-Posts, Seiten):

---
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,
});
---

Konfiguration

EmDash benötigt zwei Konfigurationsdateien.

Astro Integration

Die folgende Konfiguration registriert EmDash als Astro-Integration im Server-Output-Modus:

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",
			}),
		}),
	],
});

Live Collections Loader

Die folgende Datei registriert EmDash als Live-Content-Quelle:

import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";

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

Die _emdash-Collection leitet intern zu Ihren Content-Typen (Posts, Pages, Products) weiter.

Inhalte abfragen

EmDash bietet Query-Funktionen, die dem Pattern der Live Content Collections von Astro folgen und { entries, error } oder { entry, error } zurückgeben:

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");

Filteroptionen

getEmDashCollection unterstützt Filterung, die Astros getCollection nicht bietet:

const { entries: posts } = await getEmDashCollection("posts", {
	status: "published", // draft | published | archived
	limit: 10, // max results
	where: { category: "news" }, // taxonomy filter
});

Inhalte rendern

EmDash speichert Rich Text als Portable Text, ein strukturiertes JSON-Format. Rendern Sie es mit der PortableText-Komponente:

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>

Dynamische Features

EmDash bietet APIs für WordPress-ähnliche Features, die in Astros Content-Layer nicht existieren.

Das folgende Layout holt ein Menü nach Standort ab und rendert es mit verschachtelten Elementen:

---
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>
)}

Widget-Bereiche

Das folgende Layout holt einen Widget-Bereich ab und rendert jedes Widget:

---
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>
)}

Site-Einstellungen

Die folgende Komponente liest globale Site-Einstellungen und rendert ein Logo oder einen Titel:

---
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>

Plugins

Erweitern Sie EmDash mit Plugins, die Hooks, Storage, Settings und Admin-UI hinzufügen:

import emdash from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";

export default defineConfig({
	integrations: [
		emdash({
			// ...
			plugins: [seoPlugin({ generateSitemap: true })],
		}),
	],
});

Erstellen Sie benutzerdefinierte Plugins mit 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" },
		},
	},
});

Server Rendering

EmDash-Sites laufen im SSR-Modus, sodass Inhalte zur Laufzeit bereitgestellt werden und Änderungen sofort erscheinen.

Für statische Seiten mit getStaticPaths werden Inhalte zur Build-Zeit abgerufen:

---
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);
---

Für dynamische Seiten setzen Sie prerender = false, um Inhalte bei jeder Anfrage abzurufen:

---
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 });
}
---

Nächste Schritte