Primeiros Passos
Crie seu primeiro site EmDash em menos de 5 minutos.
O EmDash é um CMS construído especificamente para Astro — não um CMS headless genérico com um adaptador para Astro. Ele estende seu site Astro com conteúdo baseado em banco de dados, uma interface de administração polida e recursos no estilo WordPress (menus, widgets, taxonomias), preservando a experiência de desenvolvedor que você espera.
Tudo o que você sabe sobre Astro ainda se aplica. O EmDash aprimora seu site; ele não substitui seu fluxo de trabalho.
O EmDash fornece os recursos de gerenciamento de conteúdo que os sites Astro baseados em arquivos não possuem:
| Recurso | Descrição |
|---|---|
| Interface de Administração | Interface completa de edição WYSIWYG em /_emdash/admin |
| Armazenamento em Banco de Dados | Conteúdo armazenado em SQLite, libSQL ou Cloudflare D1 |
| Biblioteca de Mídia | Faça upload, organize e sirva imagens e arquivos |
| Menus de Navegação | Gerenciamento de menus com arrastar e soltar e aninhamento |
| Áreas de Widgets | Barras laterais e regiões de rodapé dinâmicas |
| Configurações do Site | Configuração global (título, logotipo, links sociais) |
| Taxonomias | Categorias, tags e taxonomias personalizadas |
| Sistema de Pré-visualização | URLs de pré-visualização assinadas para conteúdo em rascunho |
| Revisões | Histórico de versões do conteúdo |
As coleções astro:content do Astro são baseadas em arquivos e resolvidas no momento da construção. As coleções do EmDash são baseadas em banco de dados e resolvidas em tempo de execução.
| Coleções do Astro | Coleções do EmDash | |
|---|---|---|
| Armazenamento | Arquivos Markdown/MDX em src/content/ | Banco de dados SQLite/D1 |
| 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âneas (SSR) |
| Esquema | Zod em content.config.ts | Definido na administração, armazenado no banco de dados |
| Melhor para | Conteúdo gerenciado por desenvolvedores | Conteúdo gerenciado por editores |
As coleções do Astro e o EmDash podem coexistir. Use coleções do Astro para conteúdo de desenvolvedor (documentação, registros de alterações) e EmDash para conteúdo de editores (postagens de blog, páginas):
---import { getCollection } from "astro:content";import { getEmDashCollection } from "emdash";
// Documentação gerenciada por desenvolvedor a partir de arquivosconst docs = await getCollection("docs");
// Postagens gerenciadas por editor a partir do banco de dadosconst { entries: posts } = await getEmDashCollection("posts", { status: "published", limit: 5,});---O EmDash requer dois arquivos de configuração.
ts title="astro.config.mjs"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", }), }), ],});ts title="src/live.config.ts"import { defineLiveCollection } from "astro:content";import { emdashLoader } from "emdash/runtime";
export const collections = { _emdash: defineLiveCollection({ loader: emdashLoader(), }),};Isso registra o EmDash como uma fonte de conteúdo ao vivo. A coleção _emdash roteia internamente para seus tipos de conteúdo (postagens, páginas, produtos).
O 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 }:
import { getEmDashCollection, getEmDashEntry } from "emdash";
// Obtenha todas as postagens publicadas - retorna { entries, error }const { entries: posts } = await getEmDashCollection("posts", {status: "published",});
// Obtenha uma única postagem por slug - retorna { entry, error, isPreview }const { entry: post } = await getEmDashEntry("posts", "my-post");import { getCollection, getEntry } from "astro:content";
// Obtenha todas as entradas do blogconst posts = await getCollection("blog");
// Obtenha uma única entrada por slugconst post = await getEntry("blog", "my-post");getEmDashCollection suporta filtragem que o getCollection do Astro não suporta:
const { entries: posts } = await getEmDashCollection("posts", { status: "published", // draft | published | archived limit: 10, // max results where: { category: "news" }, // taxonomy filter});O EmDash armazena texto rico como Portable Text, um formato JSON estruturado. Renderize-o com o componente PortableText:
astro title="src/pages/posts/[slug].astro"---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 title="src/pages/blog/[slug].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>O EmDash fornece APIs para recursos no estilo WordPress que não existem na camada de conteúdo do Astro.
astro title="src/layouts/Base.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>)}astro title="src/layouts/BlogPost.astro"---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>)}astro title="src/components/Header.astro"---import { getSiteSettings, getSiteSetting } from "emdash";
const settings = await getSiteSettings();// Ou busque valores individuais: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>Estenda o EmDash com plugins que adicionam hooks, armazenamento, configurações e interface de administração:
ts title="astro.config.mjs"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:
ts title="src/plugins/analytics.ts"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("Conteúdo salvo", { id: event.content.id }); }, },
admin: { settingsSchema: { trackingId: { type: "string", label: "Tracking ID" }, }, },});Os sites EmDash rodam em modo SSR. As alterações de conteúdo aparecem imediatamente sem reconstruções.
Para páginas estáticas com getStaticPaths, o conteúdo é buscado no momento da construção:
astro title="src/pages/posts/[slug].astro"---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 o conteúdo em cada requisição:
astro title="src/pages/posts/[slug].astro"---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("Erro no servidor", { status: 500 });}
if (!post) { return new Response(null, { status: 404 });}---Primeiros Passos
Crie seu primeiro site EmDash em menos de 5 minutos.
Consultando Conteúdo
Aprenda a API de consulta em detalhes.
Criar um Blog
Construa um blog completo com categorias e tags.
Implantar no Cloudflare
Leve seu site para produção no Workers.