EmDash para desenvolvedores Astro

Nesta página

EmDash é um CMS construído especificamente para Astro. Ele estende seu site Astro com conteúdo baseado em banco de dados, uma interface de administração refinada e recursos no estilo WordPress (menus, widgets, taxonomias) enquanto preserva a experiência de desenvolvedor que você espera.

Tudo o que você sabe sobre Astro ainda se aplica. EmDash adiciona gerenciamento de conteúdo sobre seu fluxo de trabalho Astro existente.

O que EmDash adiciona

EmDash fornece os recursos de gerenciamento de conteúdo que faltam aos sites Astro baseados em arquivos:

RecursoDescrição
Interface de administraçãoInterface de edição WYSIWYG completa em /_emdash/admin
Armazenamento em banco de dadosConteúdo armazenado em SQLite, libSQL, Cloudflare D1 ou PostgreSQL
Biblioteca de mídiaFazer upload, organizar e servir imagens e arquivos
Menus de navegaçãoGerenciamento de menu arrastar e soltar com aninhamento
Áreas de widgetsBarras laterais dinâmicas e regiões de rodapé
Configurações do siteConfiguração global (título, logo, links sociais)
TaxonomiasCategorias, tags e taxonomias personalizadas
Sistema de visualizaçãoURLs de visualização assinadas para conteúdo rascunho
RevisõesHistórico de versões de conteúdo

Coleções Astro vs EmDash

As coleções astro:content do Astro são baseadas em arquivos e resolvidas em tempo de compilação. As coleções EmDash são baseadas em banco de dados e resolvidas em tempo de execução.

Coleções AstroColeções EmDash
ArmazenamentoArquivos Markdown/MDX em src/content/Banco de dados SQL (SQLite, libSQL, D1 ou Postgres)
EdiçãoEditor de códigoInterface de administração
Formato de conteúdoMarkdown com frontmatterPortable Text (JSON estruturado)
AtualizaçõesRequer reconstruçãoInstantâneo (SSR)
SchemaZod em content.config.tsDefinido no admin, armazenado no banco de dados
Melhor paraConteúdo gerenciado por desenvolvedoresConteúdo gerenciado por editores

Usar ambos juntos

Coleções Astro e EmDash podem coexistir. Use coleções Astro para conteúdo de desenvolvedores (docs, changelogs) e EmDash para conteúdo de editores (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

EmDash requer dois arquivos de configuração.

Integração Astro

A seguinte configuração registra EmDash como uma integração Astro em modo de saída de servidor:

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

Carregador de coleções ao vivo

O seguinte arquivo registra EmDash como uma fonte de conteúdo ao vivo:

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

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

A coleção _emdash roteia internamente para seus tipos de conteúdo (posts, páginas, produtos).

Consultando conteúdo

EmDash fornece funções de consulta que seguem o padrão de coleções de conteúdo ao vivo do Astro, retornando { 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 filtragem

getEmDashCollection suporta filtragem que getCollection do Astro não oferece:

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

Renderizando conteúdo

EmDash armazena texto rico como Portable Text, um formato JSON estruturado. Renderize-o 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>

Recursos dinâmicos

EmDash fornece APIs para recursos no estilo WordPress que não existem na camada de conteúdo do Astro.

O seguinte layout busca um menu por localização e o renderiza com itens aninhados:

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

O seguinte layout busca uma área de widgets e renderiza cada 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>
)}

Configurações do site

O seguinte componente lê configurações globais do site e renderiza um logo ou título:

---
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 EmDash com plugins que adicionam hooks, armazenamento, configurações e interface de administração:

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

Renderização de servidor

Sites EmDash rodam em modo SSR, então o conteúdo é servido em tempo de execução e as mudanças aparecem imediatamente.

Para páginas estáticas com getStaticPaths, o conteúdo é buscado em tempo de compilação:

---
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 buscar conteúdo em cada solicitação:

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