Aller au contenu

EmDash pour les développeurs Astro

EmDash est un CMS conçu spécifiquement pour Astro — pas un CMS headless générique avec un adaptateur Astro. Il étend votre site Astro avec du contenu basé sur une base de données, une interface d’administration soignée et des fonctionnalités de type WordPress (menus, widgets, taxonomies) tout en préservant l’expérience de développement que vous attendez.

Tout ce que vous savez sur Astro s’applique toujours. EmDash améliore votre site ; il ne remplace pas votre flux de travail.

EmDash fournit les fonctionnalités de gestion de contenu qui manquent aux sites Astro basés sur des fichiers :

FonctionnalitéDescription
Interface d’administrationInterface d’édition WYSIWYG complète à /_emdash/admin
Stockage en base de donnéesContenu stocké dans SQLite, libSQL ou Cloudflare D1
Bibliothèque multimédiaTélécharger, organiser et servir des images et fichiers
Menus de navigationGestion de menus par glisser-déposer avec imbrication
Zones de widgetsBarres latérales et régions de pied de page dynamiques
Paramètres du siteConfiguration globale (titre, logo, liens sociaux)
TaxonomiesCatégories, étiquettes et taxonomies personnalisées
Système de prévisualisationURLs de prévisualisation signées pour le contenu brouillon
RévisionsHistorique des versions du contenu

Les collections astro:content d’Astro sont basées sur des fichiers et résolues au moment de la construction. Les collections EmDash sont basées sur une base de données et résolues à l’exécution.

Collections AstroCollections EmDash
StockageFichiers Markdown/MDX dans src/content/Base de données SQLite/D1
ÉditionÉditeur de codeInterface d’administration
Format de contenuMarkdown avec frontmatterPortable Text (JSON structuré)
Mises à jourNécessite une reconstructionInstantanées (SSR)
SchémaZod dans content.config.tsDéfini dans l’admin, stocké en base de données
Idéal pourContenu géré par les développeursContenu géré par les éditeurs

Les collections Astro et EmDash peuvent coexister. Utilisez les collections Astro pour le contenu des développeurs (docs, journaux des modifications) et EmDash pour le contenu des éditeurs (articles de blog, pages) :

src/pages/index.astro
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";
// Docs gérés par les développeurs depuis des fichiers
const docs = await getCollection("docs");
// Articles gérés par les éditeurs depuis la base de données
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
limit: 5,
});
---

EmDash nécessite deux fichiers de configuration.

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

Ceci enregistre EmDash comme une source de contenu en direct. La collection _emdash route en interne vers vos types de contenu (articles, pages, produits).

EmDash fournit des fonctions de requête qui suivent le modèle des collections de contenu en direct d’Astro, renvoyant { entries, error } ou { entry, error } :

import { getEmDashCollection, getEmDashEntry } from "emdash";
// Obtenir tous les articles publiés - renvoie { entries, error }
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
// Obtenir un seul article par slug - renvoie { entry, error, isPreview }
const { entry: post } = await getEmDashEntry("posts", "my-post");

getEmDashCollection prend en charge un filtrage que getCollection d’Astro ne propose pas :

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

EmDash stocke le texte enrichi sous forme de Portable Text, un format JSON structuré. Rendez-le avec le composant PortableText :

astro title="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 fournit des API pour des fonctionnalités de type WordPress qui n’existent pas dans la couche de contenu d’Astro.

astro title="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>
)}
astro title="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>
)}
astro title="src/components/Header.astro"
---
import { getSiteSettings, getSiteSetting } from "emdash";
const settings = await getSiteSettings();
// Ou récupérer des valeurs individuelles :
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>

Étendez EmDash avec des plugins qui ajoutent des hooks, du stockage, des paramètres et une interface d’administration :

ts title="astro.config.mjs"
import emdash from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";
export default defineConfig({
integrations: [
emdash({
// ...
plugins: [seoPlugin({ generateSitemap: true })],
}),
],
});

Créez des plugins personnalisés avec definePlugin :

ts title="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("Contenu sauvegardé", { id: event.content.id });
},
},
admin: {
settingsSchema: {
trackingId: { type: "string", label: "Tracking ID" },
},
},
});

Les sites EmDash s’exécutent en mode SSR. Les modifications de contenu apparaissent immédiatement sans reconstruction.

Pour les pages statiques avec getStaticPaths, le contenu est récupéré au moment de la construction :

astro title="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);
---

Pour les pages dynamiques, définissez prerender = false pour récupérer le contenu à chaque requête :

astro title="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("Erreur serveur", { status: 500 });
}
if (!post) {
return new Response(null, { status: 404 });
}
---