EmDash est un CMS construit spécifiquement pour 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 style 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 ajoute la gestion de contenu par-dessus votre flux de travail Astro existant.
Ce qu’EmDash ajoute
EmDash fournit les fonctionnalités de gestion de contenu qui manquent aux sites Astro basés sur des fichiers :
| Fonctionnalité | Description |
|---|---|
| Interface d’administration | Interface d’édition WYSIWYG complète à /_emdash/admin |
| Stockage en base de données | Contenu stocké dans SQLite, libSQL, Cloudflare D1 ou PostgreSQL |
| Bibliothèque de médias | Télécharger, organiser et servir des images et des fichiers |
| Menus de navigation | Gestion de menus par glisser-déposer avec imbrication |
| Zones de widgets | Barres latérales dynamiques et régions de pied de page |
| Paramètres du site | Configuration globale (titre, logo, liens sociaux) |
| Taxonomies | Catégories, tags et taxonomies personnalisées |
| Système de prévisualisation | URLs de prévisualisation signées pour le contenu en brouillon |
| Révisions | Historique des versions du contenu |
Collections Astro vs EmDash
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 Astro | Collections EmDash | |
|---|---|---|
| Stockage | Fichiers Markdown/MDX dans src/content/ | Base de données SQL (SQLite, libSQL, D1 ou Postgres) |
| Édition | Éditeur de code | Interface d’administration |
| Format de contenu | Markdown avec frontmatter | Portable Text (JSON structuré) |
| Mises à jour | Nécessite une reconstruction | Instantané (SSR) |
| Schéma | Zod dans content.config.ts | Défini dans l’admin, stocké dans la base de données |
| Idéal pour | Contenu géré par les développeurs | Contenu géré par les éditeurs |
Utiliser les deux ensemble
Les collections Astro et EmDash peuvent coexister. Utilisez les collections Astro pour le contenu des développeurs (docs, changelogs) et EmDash pour le contenu des éditeurs (articles de blog, pages) :
---
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,
});
---
Configuration
EmDash nécessite deux fichiers de configuration.
Intégration Astro
La configuration suivante enregistre EmDash comme une intégration Astro en mode de sortie serveur :
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",
}),
}),
],
});
Chargeur de collections en direct
Le fichier suivant enregistre EmDash comme source de contenu en direct :
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
La collection _emdash route en interne vers vos types de contenu (posts, pages, produits).
Interroger le contenu
EmDash fournit des fonctions de requête qui suivent le modèle des collections de contenu en direct d’Astro, retournant { entries, error } ou { entry, error } :
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"); Options de filtrage
getEmDashCollection prend en charge le filtrage que getCollection d’Astro n’offre pas :
const { entries: posts } = await getEmDashCollection("posts", {
status: "published", // draft | published | archived
limit: 10, // max results
where: { category: "news" }, // taxonomy filter
});
Rendre le contenu
EmDash stocke le texte enrichi sous forme de Portable Text, un format JSON structuré. Rendez-le avec le composant PortableText :
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> Fonctionnalités dynamiques
EmDash fournit des API pour des fonctionnalités de style WordPress qui n’existent pas dans la couche de contenu d’Astro.
Menus de navigation
La mise en page suivante récupère un menu par emplacement et le rend avec des éléments imbriqués :
---
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>
)}
Zones de widgets
La mise en page suivante récupère une zone de widgets et rend chaque 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>
)}
Paramètres du site
Le composant suivant lit les paramètres globaux du site et rend un logo ou un titre :
---
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
Étendez EmDash avec des plugins qui ajoutent des hooks, du stockage, des paramètres et une interface d’administration :
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 :
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" },
},
},
});
Rendu côté serveur
Les sites EmDash fonctionnent en mode SSR, donc le contenu est servi à l’exécution et les changements apparaissent immédiatement.
Pour les pages statiques avec getStaticPaths, le contenu est récupéré au moment de la construction :
---
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 :
---
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 });
}
---
Prochaines étapes
Getting Started
Créez votre premier site EmDash en moins de 5 minutes.
Querying Content
Apprenez l’API de requête en détail.
Create a Blog
Construisez un blog complet avec des catégories et des tags.
Deploy to Cloudflare
Mettez votre site en production sur Workers.