EmDash è un CMS costruito specificamente per Astro. Estende il tuo sito Astro con contenuti supportati da database, un’interfaccia admin curata e funzionalità in stile WordPress (menu, widget, tassonomie) preservando l’esperienza di sviluppo che ti aspetti.
Tutto ciò che sai su Astro resta valido. EmDash aggiunge la gestione dei contenuti sopra il tuo flusso di lavoro Astro esistente.
Cosa aggiunge EmDash
EmDash fornisce le funzionalità di gestione dei contenuti che mancano ai siti Astro basati su file:
| Funzionalità | Descrizione |
|---|---|
| Interfaccia admin | Interfaccia di editing WYSIWYG completa su /_emdash/admin |
| Archiviazione in DB | Contenuti archiviati in SQLite, libSQL, Cloudflare D1 o PostgreSQL |
| Libreria media | Caricare, organizzare e servire immagini e file |
| Menu di navigazione | Gestione menu drag-and-drop con annidamento |
| Aree widget | Barre laterali dinamiche e aree piè di pagina |
| Impostazioni sito | Configurazione globale (titolo, logo, link social) |
| Tassonomie | Categorie, tag e tassonomie personalizzate |
| Sistema di anteprima | URL di anteprima firmate per contenuti in bozza |
| Revisioni | Cronologia delle versioni dei contenuti |
Astro Collections vs EmDash
Le collezioni astro:content di Astro sono basate su file e risolte al momento del build. Le collezioni EmDash sono supportate da database e risolte a runtime.
| Collezioni Astro | Collezioni EmDash | |
|---|---|---|
| Archiviazione | File Markdown/MDX in src/content/ | Database SQL (SQLite, libSQL, D1 o Postgres) |
| Modifica | Editor di codice | Interfaccia admin |
| Formato contenuto | Markdown con frontmatter | Portable Text (JSON strutturato) |
| Aggiornamenti | Richiede rebuild | Istantaneo (SSR) |
| Schema | Zod in content.config.ts | Definito nell’admin, archiviato in DB |
| Ideale per | Contenuti gestiti dagli sviluppatori | Contenuti gestiti dai redattori |
Usarli insieme
Le collezioni Astro e EmDash possono coesistere. Usa le collezioni Astro per i contenuti degli sviluppatori (docs, changelog) e EmDash per i contenuti editoriali (articoli del blog, pagine):
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";
// Docs gestiti dagli sviluppatori dai file
const docs = await getCollection("docs");
// Articoli gestiti dai redattori dal database
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
limit: 5,
});
---
Configurazione
EmDash richiede due file di configurazione.
Integrazione Astro
La seguente configurazione registra EmDash come integrazione Astro in modalità output server:
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", // Richiesto per EmDash
integrations: [
react(), // Richiesto — l'interfaccia admin è un'app React
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
Live Collections Loader
Il seguente file registra EmDash come sorgente di contenuti live:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
La collezione _emdash instrada internamente verso i tuoi tipi di contenuto (articoli, pagine, prodotti).
Interrogare i contenuti
EmDash fornisce funzioni di query che seguono il pattern delle Live Content Collections di Astro, restituendo { entries, error } o { entry, error }:
EmDash
import { getEmDashCollection, getEmDashEntry } from "emdash";
// Ottieni tutti gli articoli pubblicati - restituisce { entries, error }
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
// Ottieni un singolo articolo per slug - restituisce { entry, error, isPreview }
const { entry: post } = await getEmDashEntry("posts", "my-post");
Astro
import { getCollection, getEntry } from "astro:content";
// Ottieni tutte le voci del blog
const posts = await getCollection("blog");
// Ottieni una singola voce per slug
const post = await getEntry("blog", "my-post"); Opzioni di filtro
getEmDashCollection supporta il filtraggio che getCollection di Astro non offre:
const { entries: posts } = await getEmDashCollection("posts", {
status: "published", // draft | published | archived
limit: 10, // risultati massimi
where: { category: "news" }, // filtro tassonomia
});
Renderizzare i contenuti
EmDash archivia il testo formattato come Portable Text, un formato JSON strutturato. Renderizzalo con il componente 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> Funzionalità dinamiche
EmDash fornisce API per funzionalità in stile WordPress che non esistono nel layer di contenuti di Astro.
Menu di navigazione
Il seguente layout recupera un menu per posizione e lo renderizza con elementi annidati:
---
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>
)}
Aree widget
Il seguente layout recupera un’area widget e renderizza ogni 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>
)}
Impostazioni del sito
Il seguente componente legge le impostazioni globali del sito e renderizza un logo o un titolo:
---
import { getSiteSettings, getSiteSetting } from "emdash";
const settings = await getSiteSettings();
// Oppure recupera valori individuali:
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>
Plugin
Estendi EmDash con plugin che aggiungono hook, archiviazione, impostazioni e interfaccia admin:
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 })],
}),
],
});
Crea plugin personalizzati con 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: "ID di tracciamento" },
},
},
});
Rendering lato server
I siti EmDash funzionano in modalità SSR, quindi i contenuti vengono serviti a runtime e le modifiche appaiono immediatamente.
Per le pagine statiche con getStaticPaths, i contenuti vengono recuperati al momento del build:
---
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);
---
Per le pagine dinamiche, imposta prerender = false per recuperare i contenuti ad ogni richiesta:
---
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 });
}
---
Prossimi passi
Primi passi
Crea il tuo primo sito EmDash in meno di 5 minuti.
Interrogare i contenuti
Scopri l’API di query nel dettaglio.
Creare un blog
Costruisci un blog completo con categorie e tag.
Deploy su Cloudflare
Porta il tuo sito in produzione su Workers.