EmDash für Astro-Entwickler

Auf dieser Seite

EmDash ist ein CMS, das speziell für Astro entwickelt wurde. Es erweitert Ihre Astro-Website um datenbankgestützte Inhalte, eine ausgefeilte Admin-Oberfläche und WordPress-ähnliche Funktionen (Menüs, Widgets, Taxonomien) und bewahrt dabei die Entwicklererfahrung, die Sie erwarten.

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

Was EmDash hinzufügt

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

FunktionBeschreibung
Admin-UIVollständige WYSIWYG-Bearbeitungsoberfläche unter /_emdash/admin
DatenbankspeicherInhalte gespeichert in SQLite, libSQL, Cloudflare D1 oder PostgreSQL
MedienbibliothekBilder und Dateien hochladen, organisieren und bereitstellen
NavigationsmenüsDrag-and-Drop-Menüverwaltung mit Verschachtelung
Widget-BereicheDynamische Seitenleisten und Fußzeilenbereiche
Website-EinstellungenGlobale Konfiguration (Titel, Logo, Social Links)
TaxonomienKategorien, Tags und benutzerdefinierte Taxonomien
VorschausystemSignierte Vorschau-URLs für Entwurfsinhalte
RevisionenInhaltsversionsverlauf

Astro Collections vs EmDash

Astros astro:content-Collections sind dateibasiert und werden zur Build-Zeit aufgelöst. EmDash-Collections sind datenbankgestützt 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
InhaltsformatMarkdown mit FrontmatterPortable Text (strukturiertes JSON)
AktualisierungenErfordert RebuildSofort (SSR)
SchemaZod in content.config.tsIm Admin definiert, in Datenbank gespeichert
Ideal fürVom Entwickler verwaltete InhalteVom Redakteur verwaltete Inhalte

Beide zusammen verwenden

Astro-Collections und EmDash können koexistieren. Verwenden Sie Astro-Collections für Entwicklerinhalte (Docs, Changelogs) und EmDash für Redaktionsinhalte (Blogbeiträge, Seiten):

---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";

// Vom Entwickler verwaltete Docs aus Dateien
const docs = await getCollection("docs");

// Vom Redakteur verwaltete Beiträge aus der Datenbank
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-Ausgabemodus:

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

export default defineConfig({
	output: "server", // Erforderlich für EmDash
	integrations: [
		react(), // Erforderlich — die Admin-UI ist eine React-App
		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-Inhaltsquelle:

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

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

Die _emdash-Collection leitet intern an Ihre Inhaltstypen weiter (Posts, Seiten, Produkte).

Inhalte abfragen

EmDash bietet Abfragefunktionen, die dem Muster von Astros Live Content Collections folgen und { entries, error } oder { entry, error } zurückgeben:

EmDash

import { getEmDashCollection, getEmDashEntry } from "emdash";

// Alle veröffentlichten Beiträge abrufen - gibt { entries, error } zurück
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});

// Einen einzelnen Beitrag per Slug abrufen - gibt { entry, error, isPreview } zurück
const { entry: post } = await getEmDashEntry("posts", "my-post");

Astro

import { getCollection, getEntry } from "astro:content";

// Alle Blog-Einträge abrufen
const posts = await getCollection("blog");

// Einen einzelnen Eintrag per Slug abrufen
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, // maximale Ergebnisse
	where: { category: "news" }, // Taxonomie-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 Funktionen

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

Das folgende Layout ruft ein Menü nach Position 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 ruft 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>
)}

Website-Einstellungen

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

---
import { getSiteSettings, getSiteSetting } from "emdash";

const settings = await getSiteSettings();
// Oder einzelne Werte abrufen:
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, Speicher, Einstellungen und Admin-UI hinzufügen:

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

export default defineConfig({
	integrations: [
		react(),
		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-Websites laufen im SSR-Modus, sodass Inhalte zur Laufzeit bereitgestellt werden und Änderungen sofort sichtbar sind.

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