EmDash è un CMS costruito specificamente per Astro. Estende il tuo sito Astro con contenuti supportati da database, un’interfaccia di amministrazione raffinata e funzionalità in stile WordPress (menu, widget, tassonomie) preservando l’esperienza di sviluppo che ti aspetti.
Tutto ciò che sai su Astro rimane valido. EmDash aggiunge la gestione dei contenuti al 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 amministrativa | Interfaccia di modifica WYSIWYG completa su /_emdash/admin |
| Archiviazione database | Contenuti archiviati in SQLite, libSQL, Cloudflare D1 o PostgreSQL |
| Libreria media | Carica, organizza e servi immagini e file |
| Menu di navigazione | Gestione menu drag-and-drop con annidamento |
| Aree widget | Barre laterali dinamiche e regioni footer |
| Impostazioni sito | Configurazione globale (titolo, logo, link social) |
| Tassonomie | Categorie, tag e tassonomie personalizzate |
| Sistema di anteprima | URL di anteprima firmati per contenuti in bozza |
| Revisioni | Cronologia delle versioni dei contenuti |
Collezioni Astro vs EmDash
Le collezioni astro:content di Astro sono basate su file e risolte al momento della build. Le collezioni EmDash sono supportate da database e risolte in fase di esecuzione.
| Collezioni Astro | Collezioni EmDash | |
|---|---|---|
| Archiviazione | File Markdown/MDX in src/content/ | Database SQL (SQLite, libSQL, D1 o Postgres) |
| Modifica | Editor di codice | Interfaccia amministrativa |
| Formato contenuto | Markdown con frontmatter | Portable Text (JSON strutturato) |
| Aggiornamenti | Richiede ricostruzione | Istantaneo (SSR) |
| Schema | Zod in content.config.ts | Definito nell’admin, archiviato nel database |
| Ideale per | Contenuti gestiti da sviluppatori | Contenuti gestiti da editor |
Usare entrambi insieme
Le collezioni Astro ed EmDash possono coesistere. Usa le collezioni Astro per i contenuti degli sviluppatori (documentazione, changelog) ed EmDash per i contenuti degli editor (post del blog, pagine):
---
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,
});
---
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 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",
}),
}),
],
});
Caricatore di collezioni live
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 indirizza internamente ai tuoi tipi di contenuto (post, pagine, prodotti).
Interrogare i contenuti
EmDash fornisce funzioni di query che seguono il pattern delle collezioni di contenuti live di Astro, restituendo { entries, error } o { 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"); Opzioni di filtraggio
getEmDashCollection supporta il filtraggio che getCollection di Astro non offre:
const { entries: posts } = await getEmDashCollection("posts", {
status: "published", // draft | published | archived
limit: 10, // max results
where: { category: "news" }, // taxonomy filter
});
Renderizzare i contenuti
EmDash archivia il testo ricco 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 livello 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 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();
// 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>
Plugin
Estendi EmDash con plugin che aggiungono hook, archiviazione, impostazioni e interfaccia amministrativa:
import emdash from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";
export default defineConfig({
integrations: [
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: "Tracking ID" },
},
},
});
Rendering lato server
I siti EmDash vengono eseguiti in modalità SSR, quindi il contenuto viene servito in fase di esecuzione e le modifiche appaiono immediatamente.
Per le pagine statiche con getStaticPaths, il contenuto viene recuperato al momento della 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 il contenuto 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
Getting Started
Crea il tuo primo sito EmDash in meno di 5 minuti.
Querying Content
Impara l’API di query in dettaglio.
Create a Blog
Costruisci un blog completo con categorie e tag.
Deploy to Cloudflare
Porta il tuo sito in produzione su Workers.