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:
| Recurso | Descrição |
|---|---|
| Interface de administração | Interface de edição WYSIWYG completa em /_emdash/admin |
| Armazenamento em banco de dados | Conteúdo armazenado em SQLite, libSQL, Cloudflare D1 ou PostgreSQL |
| Biblioteca de mídia | Fazer upload, organizar e servir imagens e arquivos |
| Menus de navegação | Gerenciamento de menu arrastar e soltar com aninhamento |
| Áreas de widgets | Barras laterais dinâmicas e regiões de rodapé |
| Configurações do site | Configuração global (título, logo, links sociais) |
| Taxonomias | Categorias, tags e taxonomias personalizadas |
| Sistema de visualização | URLs de visualização assinadas para conteúdo rascunho |
| Revisões | Histó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 Astro | Coleções EmDash | |
|---|---|---|
| Armazenamento | Arquivos Markdown/MDX em src/content/ | Banco de dados SQL (SQLite, libSQL, D1 ou Postgres) |
| Edição | Editor de código | Interface de administração |
| Formato de conteúdo | Markdown com frontmatter | Portable Text (JSON estruturado) |
| Atualizações | Requer reconstrução | Instantâneo (SSR) |
| Schema | Zod em content.config.ts | Definido no admin, armazenado no banco de dados |
| Melhor para | Conteúdo gerenciado por desenvolvedores | Conteú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.
Menus de navegação
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
Getting Started
Crie seu primeiro site EmDash em menos de 5 minutos.
Querying Content
Aprenda a API de consulta em detalhes.
Create a Blog
Construa um blog completo com categorias e tags.
Deploy to Cloudflare
Leve seu site para produção em Workers.