Ir al contenido

EmDash para Desarrolladores de Astro

EmDash es un CMS creado específicamente para Astro, no un headless CMS genérico con un adaptador añadido después. Amplía tu sitio con contenido respaldado por base de datos, una interfaz de administración pulida y funciones al estilo de WordPress, sin romper el flujo de trabajo de Astro.

Todo lo que sabes sobre Astro sigue aplicándose. EmDash mejora tu sitio; no reemplaza tu flujo de trabajo.

EmDash proporciona las características de gestión de contenido que carecen los sitios de Astro basados en archivos:

CaracterísticaDescripción
Interfaz de administraciónInterfaz de edición WYSIWYG completa en /_emdash/admin
Almacenamiento en base de datosContenido almacenado en SQLite, libSQL o Cloudflare D1
Biblioteca de mediosSubir, organizar y servir imágenes y archivos
Menús de navegaciónGestión de menús con arrastrar y soltar y anidación
Áreas de widgetsBarras laterales y regiones de pie de página dinámicas
Configuración del sitioConfiguración global (título, logotipo, enlaces sociales)
TaxonomíasCategorías, etiquetas y taxonomías personalizadas
Sistema de vista previaURLs de vista previa firmadas para contenido en borrador
RevisionesHistorial de versiones del contenido

Las colecciones astro:content de Astro están basadas en archivos y se resuelven en tiempo de compilación. Las colecciones de EmDash están respaldadas por base de datos y se resuelven en tiempo de ejecución.

Colecciones de AstroColecciones de EmDash
AlmacenamientoArchivos Markdown/MDX en src/content/Base de datos SQLite/D1
EdiciónEditor de códigoInterfaz de administración
Formato de contenidoMarkdown con frontmatterPortable Text (JSON estructurado)
ActualizacionesRequiere reconstrucciónInstantáneas (SSR)
EsquemaZod en content.config.tsDefinido en la administración, almacenado en la base de datos
Ideal paraContenido gestionado por desarrolladoresContenido gestionado por editores

Las colecciones de Astro y EmDash pueden coexistir. Usa las colecciones de Astro para contenido de desarrolladores (documentación, registros de cambios) y EmDash para contenido de editores (publicaciones de blog, páginas):

src/pages/index.astro
---
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,
});
---

EmDash requiere dos archivos de configuración.

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",
}),
}),
],
});
src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};

Esto registra a EmDash como una fuente de contenido en vivo. La colección _emdash enruta internamente a tus tipos de contenido (publicaciones, páginas, productos).

EmDash proporciona funciones de consulta que siguen el patrón de colecciones de contenido en vivo de Astro, devolviendo { entries, error } o { entry, error }:

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

getEmDashCollection admite filtrado que getCollection de Astro no admite:

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

EmDash almacena texto enriquecido como Portable Text, un formato JSON estructurado. Renderízalo con el componente PortableText:

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>

EmDash proporciona APIs para características al estilo de WordPress que no existen en la capa de contenido de Astro.

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>
)}
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>
)}
src/components/Header.astro
---
import { getSiteSettings, getSiteSetting } from "emdash";
const settings = await getSiteSettings();
// O recuperar valores individuales:
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>

Extiende EmDash con plugins que añaden hooks, almacenamiento, configuraciones e interfaz de administración:

astro.config.mjs
import emdash from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";
export default defineConfig({
integrations: [
emdash({
// ...
plugins: [seoPlugin({ generateSitemap: true })],
}),
],
});

Crea plugins personalizados con definePlugin:

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("Contenido guardado", { id: event.content.id });
},
},
admin: {
settingsSchema: {
trackingId: { type: "string", label: "Tracking ID" },
},
},
});

Los sitios de EmDash se ejecutan en modo SSR. Los cambios en el contenido aparecen inmediatamente sin reconstrucciones.

Para páginas estáticas con getStaticPaths, el contenido se obtiene en el momento de la construcción:

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, establece prerender = false para obtener el contenido en cada solicitud:

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("Error del servidor", { status: 500 });
}
if (!post) {
return new Response(null, { status: 404 });
}
---