EmDash para desenvolvedores Astro

Nesta página

O EmDash é um CMS feito especificamente para Astro—não um CMS headless genérico com adaptador Astro. Estende o seu site Astro com conteúdo em base de dados, uma admin UI cuidada e funcionalidades ao estilo WordPress (menus, widgets, taxonomias), preservando a experiência de desenvolvimento que espera.

Tudo o que sabe sobre Astro continua a aplicar-se. O EmDash melhora o site; não substitui o seu fluxo de trabalho.

O que o EmDash acrescenta

O EmDash fornece as funcionalidades de gestão de conteúdo que faltam aos sites Astro baseados só em ficheiros:

FuncionalidadeDescrição
Admin UIInterface de edição WYSIWYG completa em /_emdash/admin
Database storageConteúdo em SQLite, libSQL ou Cloudflare D1
Media libraryCarregar, organizar e servir imagens e ficheiros
Navigation menusGestão de menus com arrastar e largar e aninhamento
Widget areasBarras laterais dinâmicas e regiões de rodapé
Site settingsConfiguração global (título, logo, ligações sociais)
TaxonomiesCategorias, etiquetas e taxonomias personalizadas
Preview systemURLs de pré-visualização assinadas para rascunhos
RevisionsHistórico de versões do conteúdo

Astro Collections vs EmDash

As coleções astro:content do Astro são baseadas em ficheiros e resolvidas em tempo de build. As coleções EmDash são suportadas por base de dados e resolvidas em tempo de execução.

Astro CollectionsEmDash Collections
StorageFicheiros Markdown/MDX em src/content/Base de dados SQLite/D1
EditingEditor de códigoAdmin UI
Content formatMarkdown com frontmatterPortable Text (JSON estruturado)
UpdatesExige rebuildInstantâneo (SSR)
SchemaZod em content.config.tsDefinido na admin, guardado na base de dados
Best forConteúdo gerido por programadoresConteúdo gerido por editores

Usar ambos em conjunto

As coleções Astro e EmDash podem coexistir. Use Astro para conteúdo de programador (docs, changelogs) e EmDash para conteúdo editorial (posts de blog, páginas):

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

Configuração

O EmDash requer dois ficheiros de configuração.

Integração 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(),
	}),
};

Isto regista o EmDash como fonte de conteúdo live. A coleção _emdash encaminha internamente para os tipos de conteúdo (posts, pages, products).

Consultar conteúdo

O EmDash oferece funções de consulta que seguem o padrão das live content collections do Astro, devolvendo { 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");

Opções de filtro

getEmDashCollection suporta filtros que getCollection do Astro não tem:

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

Renderizar conteúdo

O EmDash armazena rich text como Portable Text, um formato JSON estruturado. Renderize com o 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>

Funcionalidades dinâmicas

O EmDash fornece APIs para funcionalidades ao estilo WordPress que não existem na camada de conteúdo do 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>
)}

Áreas de widgets

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

Definições do site

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

Estenda o EmDash com plugins que acrescentam hooks, armazenamento, definições e admin UI:

import emdash from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";

export default defineConfig({
	integrations: [
		emdash({
			// ...
			plugins: [seoPlugin({ generateSitemap: true })],
		}),
	],
});

Crie plugins personalizados com 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

Os sites EmDash correm em modo SSR. As alterações de conteúdo aparecem de imediato sem rebuilds.

Para páginas estáticas com getStaticPaths, o conteúdo é obtido em tempo de 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);
---

Para páginas dinâmicas, defina prerender = false para carregar conteúdo em cada pedido:

---
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 });
}
---

Próximos passos