EmDash per sviluppatori Astro

In questa pagina

EmDash è un CMS costruito appositamente per Astro—non un CMS headless generico con adapter Astro. Estende il tuo sito Astro con contenuti in database, un’admin UI curata e funzionalità in stile WordPress (menu, widget, tassonomie), mantenendo la developer experience che ti aspetti.

Tutto ciò che sai su Astro resta valido. EmDash arricchisce il sito; non sostituisce il tuo flusso di lavoro.

Cosa aggiunge EmDash

EmDash offre le funzionalità di content management che mancano ai siti Astro solo su file:

FunzionalitàDescrizione
Admin UIInterfaccia di editing WYSIWYG completa su /_emdash/admin
Database storageContenuti in SQLite, libSQL o Cloudflare D1
Media libraryCaricare, organizzare e servire immagini e file
Navigation menusGestione menu drag-and-drop con annidamento
Widget areasSidebar dinamiche e aree footer
Site settingsConfigurazione globale (titolo, logo, link social)
TaxonomiesCategorie, tag e tassonomie personalizzate
Preview systemURL di anteprima firmate per le bozze
RevisionsCronologia delle versioni del contenuto

Astro Collections vs EmDash

Le collection astro:content di Astro sono basate su file e risolte in fase di build. Le collection EmDash sono in database e risolte a runtime.

Astro CollectionsEmDash Collections
StorageFile Markdown/MDX in src/content/Database SQLite/D1
EditingEditor di codiceAdmin UI
Content formatMarkdown con frontmatterPortable Text (JSON strutturato)
UpdatesRichiede rebuildImmediato (SSR)
SchemaZod in content.config.tsDefinito in admin, salvato nel database
Best forContenuti gestiti dagli sviluppatoriContenuti gestiti dai redattori

Usare entrambi insieme

Le collection Astro ed EmDash possono coesistere. Usa Astro per contenuti da sviluppatore (docs, changelog) ed EmDash per contenuti editoriali (post di 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

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",
			}),
		}),
	],
});

Live Collections Loader

import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";

export const collections = {
	_emdash: defineLiveCollection({
		loader: emdashLoader(),
	}),
};

Registra EmDash come sorgente di contenuto live. La collection _emdash instrada internamente ai tipi di contenuto (posts, pages, products).

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";

// 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 filtro

getEmDashCollection supporta filtri che getCollection di Astro non ha:

const { entries: posts } = await getEmDashCollection("posts", {
	status: "published", // draft | published | archived
	limit: 10, // max results
	where: { category: "news" }, // taxonomy filter
});

Rendering dei contenuti

EmDash salva il rich text come Portable Text, un 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 offre API per funzionalità in stile WordPress assenti nel content layer di Astro.

---
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

---
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

---
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, storage, impostazioni e admin UI:

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: ["read:content"],

	hooks: {
		"content:afterSave": async (event, ctx) => {
			ctx.log.info("Content saved", { id: event.content.id });
		},
	},

	admin: {
		settingsSchema: {
			trackingId: { type: "string", label: "Tracking ID" },
		},
	},
});

Server Rendering

I siti EmDash girano in modalità SSR. Le modifiche ai contenuti compaiono subito senza rebuild.

Per pagine statiche con getStaticPaths, i contenuti si recuperano in 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 pagine dinamiche imposta prerender = false per caricare i contenuti a 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 });
}
---

Passi successivi