EmDash pour les développeurs Astro

Sur cette page

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’administrationInterface d’édition WYSIWYG complète à /_emdash/admin
Stockage en base de donnéesContenu stocké dans SQLite, libSQL, Cloudflare D1 ou PostgreSQL
Bibliothèque de médiasTélécharger, organiser et servir des images et des fichiers
Menus de navigationGestion de menus par glisser-déposer avec imbrication
Zones de widgetsBarres latérales dynamiques et régions de pied de page
Paramètres du siteConfiguration globale (titre, logo, liens sociaux)
TaxonomiesCatégories, tags et taxonomies personnalisées
Système de prévisualisationURLs de prévisualisation signées pour le contenu en brouillon
RévisionsHistorique 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 AstroCollections EmDash
StockageFichiers Markdown/MDX dans src/content/Base de données SQL (SQLite, libSQL, D1 ou Postgres)
ÉditionÉditeur de codeInterface d’administration
Format de contenuMarkdown avec frontmatterPortable Text (JSON structuré)
Mises à jourNécessite une reconstructionInstantané (SSR)
SchémaZod dans content.config.tsDéfini dans l’admin, stocké dans la base de données
Idéal pourContenu géré par les développeursContenu 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.

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