EmDash per sviluppatori Astro

In questa pagina

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 adminInterfaccia di editing WYSIWYG completa su /_emdash/admin
Archiviazione in DBContenuti archiviati in SQLite, libSQL, Cloudflare D1 o PostgreSQL
Libreria mediaCaricare, organizzare e servire immagini e file
Menu di navigazioneGestione menu drag-and-drop con annidamento
Aree widgetBarre laterali dinamiche e aree piè di pagina
Impostazioni sitoConfigurazione globale (titolo, logo, link social)
TassonomieCategorie, tag e tassonomie personalizzate
Sistema di anteprimaURL di anteprima firmate per contenuti in bozza
RevisioniCronologia 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 AstroCollezioni EmDash
ArchiviazioneFile Markdown/MDX in src/content/Database SQL (SQLite, libSQL, D1 o Postgres)
ModificaEditor di codiceInterfaccia admin
Formato contenutoMarkdown con frontmatterPortable Text (JSON strutturato)
AggiornamentiRichiede rebuildIstantaneo (SSR)
SchemaZod in content.config.tsDefinito nell’admin, archiviato in DB
Ideale perContenuti gestiti dagli sviluppatoriContenuti 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.

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