Zum Inhalt springen

EmDash für Astro-Entwickler

EmDash ist ein CMS, das speziell für Astro entwickelt wurde und nicht einfach ein generisches Headless-CMS mit nachträglichem Adapter. Es erweitert Ihre Astro-Website um datenbankgestützte Inhalte, eine ausgereifte Admin-Oberfläche und WordPress-ähnliche Funktionen, ohne den gewohnten Astro-Workflow aufzubrechen.

Alles, was Sie über Astro wissen, gilt weiterhin. EmDash verbessert Ihre Website; es ersetzt nicht Ihren Workflow.

EmDash bietet die Inhaltsverwaltungsfunktionen, die dateibasierten Astro-Websites fehlen:

FunktionBeschreibung
Admin UIVollständige WYSIWYG-Bearbeitungsoberfläche unter /_emdash/admin
DatenbankspeicherInhalte gespeichert in SQLite, libSQL oder Cloudflare D1
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-Media-Links)
TaxonomienKategorien, Tags und benutzerdefinierte Taxonomien
VorschausystemSignierte Vorschau-URLs für Entwurfsinhalte
VersionenInhaltsversionsverlauf

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/SQLite/D1-Datenbank
BearbeitungCode-EditorAdmin-Oberfläche
InhaltsformatMarkdown mit FrontmatterPortable Text (strukturiertes JSON)
AktualisierungenErfordert NeubuildSofort (SSR)
SchemaZod in content.config.tsIn der Admin-Oberfläche definiert, in der Datenbank gespeichert
Am besten geeignet fürEntwickler-verwaltete InhalteRedakteur-verwaltete Inhalte

Astro Collections und EmDash können nebeneinander existieren. Verwenden Sie Astro Collections für Entwicklerinhalte (Dokumentation, Changelogs) und EmDash für Redakteursinhalte (Blogbeiträge, Seiten):

src/pages/index.astro
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";
// Entwickler-verwaltete Dokumentation aus Dateien
const docs = await getCollection("docs");
// Redakteur-verwaltete Beiträge aus der Datenbank
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
limit: 5,
});
---

EmDash erfordert zwei Konfigurationsdateien.

astro.config.mjs
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",
}),
}),
],
});
src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};

Dies registriert EmDash als Live-Inhaltsquelle. Die _emdash Collection leitet intern zu Ihren Inhaltstypen (Beiträge, Seiten, Produkte) weiter.

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

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 anhand des Slugs abrufen - gibt { entry, error, isPreview } zurück
const { entry: post } = await getEmDashEntry("posts", "my-post");

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

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

src/pages/posts/[slug].astro
---
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>

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

src/layouts/Base.astro
---
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>
)}
src/layouts/BlogPost.astro
---
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>
)}
src/components/Header.astro
---
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>

Erweitern Sie EmDash mit Plugins, die Hooks, Speicher, Einstellungen und Admin-Oberfläche hinzufügen:

astro.config.mjs
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:

src/plugins/analytics.ts
import { definePlugin } from "emdash";
export default definePlugin({
id: "analytics",
version: "1.0.0",
capabilities: ["read:content"],
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Inhalt gespeichert", { id: event.content.id });
},
},
admin: {
settingsSchema: {
trackingId: { type: "string", label: "Tracking ID" },
},
},
});

EmDash-Websites laufen im SSR-Modus. Inhaltsänderungen erscheinen sofort ohne Neubuilds.

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

src/pages/posts/[slug].astro
---
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:

src/pages/posts/[slug].astro
---
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("Serverfehler", { status: 500 });
}
if (!post) {
return new Response(null, { status: 404 });
}
---