EmDash-Seiten lesen Inhalte zur Anforderungszeit mit getEmDashCollection() und getEmDashEntry(). Die erste Funktion gibt eine Liste zurück, die zweite einen Eintrag anhand seines Slugs oder seiner Inhalts-ID. Beide geben Fehler als Daten zurück, damit die Seite entscheiden kann, wie sie antwortet.
Die gebündelten Templates verwenden Astros Server-Ausgabe. Ein Besucher erhält daher die zuletzt veröffentlichte Revision beim nächsten Render, nachdem ein Redakteur sie veröffentlicht hat. Ein gespeicherter Entwurf bleibt privat, bis er veröffentlicht oder über eine gültige Vorschau-URL angefordert wird.
Eine Sammlung abfragen
Die folgende Seite lädt die sieben zuletzt veröffentlichten Beiträge. Öffentliche Sammlungsabfragen haben standardmäßig status: "published", daher muss der Filter das nicht wiederholen.
---
import { getEmDashCollection } from "emdash";
const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
orderBy: { published_at: "desc" },
limit: 7,
});
if (error) {
console.error("Failed to load posts:", error);
return new Response("Unable to load posts", { status: 500 });
}
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
<h1>Posts</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/posts/${post.id}`}>{post.data.title}</a>
</li>
))}
</ul>
Sortierung und Begrenzung erfolgen in der Datenbank, bevor EmDash die Einträge hydriert. So wird vermieden, eine gesamte Sammlung zu laden und sie in der Seite zu sortieren oder zu schneiden.
Das Ergebnis enthält:
entries, ein leeres Array, wenn es keine Treffer gibt oder die Abfrage fehlgeschlagen ist;error, das bei einer fehlgeschlagenen Abfrage gesetzt ist, aber nicht bei einem leeren Ergebnis;cacheHint, das die Tags und die Last-Modified-Zeit für Astros Cache trägt;nextCursor, das gesetzt ist, wenn eine begrenzte Cursor-Seite weitere Einträge hat; undhasMore, das meldet, ob eine begrenzte Cursor- oder Offset-Seite eine weitere Seite hat.
Eintrags-Identifikatoren
Jedes Ergebnis hat zwei Identifikatoren mit unterschiedlichen Aufgaben:
entry.idist der URL-seitige Identifikator, den der Content-Loader erzeugt. Normalerweise ist es der Slug. Wenn die Internationalisierung ein Locale voranstellt, ist das Präfix enthalten. Verwenden Sie diesen Wert, wenn Sie einen Link aus einem Sammlungsergebnis bauen.entry.data.idist die stabile Inhalts-ID in der Datenbank. Sie ändert sich nicht, wenn ein Redakteur den Slug ändert. Verwenden Sie sie, wenn eine API, ein Taxonomie-Helper, ein Seitenkontext oder eine Relation eine Inhalts-ID erwartet.
getEmDashEntry() akzeptiert entweder den Slug oder die stabile Inhalts-ID. Eine Slug-Suche ist auf ein Locale beschränkt, wenn die Internationalisierung aktiviert ist; eine Inhalts-ID identifiziert die Zeile direkt.
Eine Sammlung filtern
Übergeben Sie Filter im zweiten Argument. Die folgende Abfrage gibt veröffentlichte Beiträge in der Kategorie news zurück, deren Feld series den Wert engineering hat:
const { entries } = await getEmDashCollection("posts", {
where: {
category: "news",
series: "engineering",
},
});
Schlüssel, die eine Taxonomie benennen, stimmen mit zugewiesenen Term-Slugs überein. Andere Schlüssel stimmen mit Sammlungsfeldern überein. Mehrere Schlüssel werden mit AND-Logik kombiniert. Ein Array an einem Schlüssel stimmt mit jedem aufgelisteten Wert überein, sodass { category: ["news", "updates"] } eine der beiden Kategorien trifft.
Verwenden Sie locale, um eine Sprache explizit anzufordern:
const { entries: frenchPosts } = await getEmDashCollection("posts", {
locale: "fr",
orderBy: { published_at: "desc" },
});
Wenn locale weggelassen wird, verwendet EmDash das Anfrage-Locale und dann das konfigurierte Standard-Locale. Siehe Internationalisierung für Fallback-Regeln und übersetzte Routen.
status akzeptiert "published", "draft" oder "archived". Fordern Sie keine Entwürfe von einer öffentlichen Route an. Verwenden Sie den Vorschau-Flow, wenn ein Besucher vorübergehenden Zugriff auf einen unveröffentlichten Eintrag braucht.
Ergebnisse in der Datenbank sortieren
orderBy ordnet Feldnamen "asc" oder "desc" zu. Verwenden Sie gespeicherte Feldnamen, nicht die camelCase-Namen in entry.data:
const { entries } = await getEmDashCollection("posts", {
orderBy: {
published_at: "desc",
title: "asc",
},
});
Systemspalten verwenden ihre Datenbanknamen, z. B. created_at, updated_at und published_at. Benutzerdefinierte Felder verwenden ihren Sammlungsslug, z. B. title oder priority. Die zurückgegebenen Daten mappen Systemdaten auf createdAt, updatedAt und publishedAt, aber diese camelCase-Eigenschaftsnamen sind keine gültigen orderBy-Felder.
EmDash verwendet das erste gültige orderBy-Feld als Paginierungsschlüssel und die Inhalts-ID als stabilen Tie-Breaker. Ohne orderBy sind Sammlungen standardmäßig nach created_at absteigend sortiert. Markieren Sie benutzerdefinierte Skalarfelder als indiziert, wenn die Website regelmäßig danach sortiert oder filtert; der Index vermeidet einen Full-Table-Scan, wenn die Sammlung wächst.
Eine Sammlung paginieren
Verwenden Sie einen Cursor für einen fortlaufenden Feed oder einen Offset für nummerierte Seiten. Es sind getrennte Paginierungsmodelle und können in einer typisierten Abfrage nicht kombiniert werden.
Cursor-Paginierung
Die Cursor-Paginierung setzt nach dem letzten Eintrag der vorherigen Abfrage fort. Lassen Sie die Sortierung zwischen Anfragen unverändert und geben Sie nextCursor unverändert zurück, ohne ihn zu prüfen oder zu ändern.
Die folgende Route rendert einen Link Older posts, wenn eine weitere Seite existiert:
---
import { getEmDashCollection } from "emdash";
const cursor = Astro.url.searchParams.get("cursor") ?? undefined;
const { entries: posts, nextCursor, error } = await getEmDashCollection("posts", {
limit: 10,
cursor,
orderBy: { published_at: "desc" },
});
if (error) return new Response("Unable to load posts", { status: 500 });
---
<ul>
{posts.map((post) => <li><a href={`/posts/${post.id}`}>{post.data.title}</a></li>)}
</ul>
{nextCursor && (
<a href={`/posts?cursor=${encodeURIComponent(nextCursor)}`}>Older posts</a>
)}
nextCursor fehlt auf der letzten Seite. Die Cursor-Paginierung berechnet keine Gesamtseitenzahl und liefert keinen Cursor für die vorherige Seite; bewahren Sie frühere URLs im Browserverlauf, wenn die Oberfläche zurücknavigieren muss.
Offset-Paginierung
Die Offset-Paginierung eignet sich für Routen wie /posts/page/3. Wandeln Sie die Seitenzahl in einen Offset um und verwenden Sie hasMore für den Link zur nächsten Seite:
---
import { getEmDashCollection } from "emdash";
const parsedPage = Number(Astro.params.page ?? "1");
if (!Number.isInteger(parsedPage) || parsedPage < 1) {
return Astro.redirect("/404");
}
const perPage = 10;
const { entries: posts, hasMore, error } = await getEmDashCollection("posts", {
limit: perPage,
offset: (parsedPage - 1) * perPage,
orderBy: { published_at: "desc" },
});
if (error) return new Response("Unable to load posts", { status: 500 });
---
<ul>
{posts.map((post) => <li><a href={`/posts/${post.id}`}>{post.data.title}</a></li>)}
</ul>
<nav aria-label="Post pages">
{parsedPage > 1 && <a href={`/posts/page/${parsedPage - 1}`}>Newer posts</a>}
{hasMore && <a href={`/posts/page/${parsedPage + 1}`}>Older posts</a>}
</nav>
Ein Offset muss eine nicht negative Ganzzahl sein. Seite 1 verwendet einen Offset von null, was „am ersten Eintrag beginnen“ bedeutet. Offset-Paginierung ist leicht per Seitenzahl adressierbar, aber zwischen Anfragen hinzugefügte Einträge können spätere Seiten verschieben. Verwenden Sie Cursor, wenn diese Bewegung verwirrend wäre.
Einen Eintrag abfragen und rendern
Die folgende Runtime-Route liest einen Beitrag anhand des Slugs, rendert sein Beitragsbild und den Portable-Text-Körper und unterscheidet einen Abfragefehler von einem fehlenden Eintrag:
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Image, PortableText } from "emdash/ui";
const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");
const { entry: post, error, isPreview, cacheHint } = await getEmDashEntry("posts", slug);
if (error) {
console.error("Failed to load post:", error);
return new Response("Unable to load post", { status: 500 });
}
if (!post) return Astro.redirect("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
{isPreview && <p>This is an unpublished preview.</p>}
<article>
{post.data.featured_image && <Image image={post.data.featured_image} priority />}
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
PortableText liefert Renderer für EmDashs Standardblöcke, einschließlich Bilder, Galerien, Code, Tabellen und bereinigter HTML-Blöcke. Übergeben Sie benutzerdefinierte Komponenten, wenn eine Website eigene Portable-Text-Blöcke hinzufügt. Wenn Sie den htmlBlock-Renderer ersetzen, bereinigen Sie das HTML und erlauben Sie nur die iframe-Hosts, denen die Website vertrauen will.
PortableText zeigt Tabellen im Bearbeitungsmodus als schreibgeschützte Platzhalter. Um ihre anfängliche Beschriftung zu lokalisieren, übergeben Sie tablePlaceholder={translatedLabel}. Der Standard ist "Table (edit in admin)" und betrifft nicht veröffentlichte Tabelleninhalte.
Referenzfelder lesen
Ein reference-Feld verknüpft einen Eintrag mit Einträgen in einer anderen Sammlung, und Beziehungen beschreibt die Definition. Sein Wert ist nicht Teil von data. Übergeben Sie eine references-Option an getEmDashEntry mit den Feldern, die die Seite rendert, nach Feldslug gruppiert, und jedes kommt als Seite von Einträgen zurück:
---
import { getEmDashEntry } from "emdash";
const { entry: post } = await getEmDashEntry("posts", Astro.params.slug, {
references: { author: true, related_posts: { limit: 6 } },
});
if (!post) return Astro.redirect("/404");
const author = post.references?.author.entries[0];
---
<article>
<h1>{post.data.title}</h1>
{author && <p>By {author.data.name}</p>}
<ul>
{post.references?.related_posts.entries.map((related) => (
<li><a href={`/posts/${related.data.slug}`}>{related.data.title}</a></li>
))}
</ul>
</article>
true fordert die erste Seite mit dem Standardlimit von 50 Einträgen an. Verwenden Sie { limit, cursor } für ein Feld mit mehr, bis zu 100 pro Seite.
Jeder referenzierte Eintrag ist ein ContentEntry mit derselben Form wie ein direkt geladener: eine id, ein data-Objekt mit Daten als Date-Objekten und aufgelösten Medienwerten sowie einem edit-Proxy, der auf den referenzierten Eintrag beschränkt ist, sodass ein Klick auf eine Karte im visuellen Editing den Eintrag öffnet, um den es bei der Karte geht. Bylines und Taxonomie-Terme sind die Ausnahme — EmDash hydriert sie nicht auf referenzierte Einträge, lesen Sie also data.bylines und data.terms vom Eintrag selbst.
Einträge kommen in der Reihenfolge an, die der Redakteur festgelegt hat, wenn das Feld am Elternende seiner Relation sitzt. Ein Feld am Kindende listet die Einträge, die darauf zeigen, und hat keine eigene Reihenfolge.
Die Option ist in beiden Richtungen opt-in. Ein Aufruf ohne references führt keine Extra-Abfragen aus, und ein Feld, das nicht ausgewählt ist, wird nicht gelesen. Ein Aufruf, der Felder auswählt, kostet eine Link-Abfrage pro Feld plus eine Eintragsabfrage pro eindeutigem Zielkollektion, unabhängig davon, wie viele Einträge jedes Feld hält.
Ein Referenzfeld paginieren
getEmDashReferences holt die nächste Seite eines einzelnen Feldes mit dem Cursor, den die vorherige Seite zurückgegeben hat:
import { getEmDashReferences } from "emdash";
const { entries, nextCursor } = await getEmDashReferences("posts", post.id, "related_posts", {
cursor,
limit: 20,
});
Es liest die Entwurfsichtbarkeit aus demselben Anfragekontext wie getEmDashEntry, sodass ein in der Vorschau begonnener Durchlauf die ausstehende Auswahl weiterhin sieht. Eine Seiten-ID trägt ihr Locale, wo i18n eines voranstellt (fr/about), und das ist die ID, die zurückgegeben werden muss.
Auf einer Route, die ihre Ausgabe cached, führen Sie den von der Seite zurückgegebenen cacheHint mit dem der Route zusammen, damit ein Schreiben an einen der genannten Einträge die Seite invalidiert.
Referenzfelder in der Vorschau
Ein öffentliches Render sieht veröffentlichte Einträge und die veröffentlichte Auswahl. Eine Vorschau des Eintrags oder ein Redakteur im visuellen Editing sieht unveröffentlichte Einträge und die im Entwurf des Eintrags gestufte Auswahl, sodass ein Vorschau-Link die Referenzen zeigt, die die Seite nach der Veröffentlichung haben wird.
SEO- und Editing-Funktionen anwenden
Für eine Sammlung mit SEO-Unterstützung löst getSeoMeta() den SEO-Titel, die Beschreibung, das Bild, die kanonische URL und die No-Index-Wahl des Redakteurs mit Fallbacks aus dem Eintrag auf. Das aktuelle Blog-Template übergibt dieses Ergebnis an sein Basis-Layout:
---
import { getSeoMeta } from "emdash";
const seo = getSeoMeta(post, {
siteTitle: "My Blog",
siteUrl: Astro.url.origin,
path: Astro.url.pathname,
});
---
<Base
title={seo.title}
pageTitle={seo.ogTitle}
description={seo.description}
image={seo.ogImage}
canonical={seo.canonical}
robots={seo.robots}
>
<!-- Post content -->
</Base>
Ein Layout, das <EmDashHead> enthält, kann dieselben SEO-Felder und Plugin-Beiträge auf serverseitig gerenderte Inhaltsseiten anwenden. Handgeschriebene Meta-Tags, die nur data.title oder data.excerpt lesen, wenden die kanonische URL oder die No-Index-Einstellung des Redakteurs nicht an.
Vorschau-URLs brauchen keine separate Abfrage. Die Middleware validiert das _preview-Token, und getEmDashEntry() gibt den passenden Entwurf mit isPreview: true zurück. Der Leitfaden zu Vorschau und visuellem Editing erklärt die URL-Erzeugung und die entry.edit-Annotationen für Inline-Editing.
TypeScript-Typen generieren
Der Dev-Server erzeugt emdash-env.d.ts aus dem aktiven Schema. Halten Sie diese Datei in der TypeScript-Konfiguration des Projekts enthalten, damit ein Sammlungsname wie "posts" automatisch den generierten Post-Datentyp auswählt.
Für eine remote EmDash-Instanz kann die CLI das Schema abrufen und Typen nach .emdash/types.ts schreiben:
npx emdash types --url https://cms.example.com
Der Befehl akzeptiert ein API-Token oder benutzerdefinierte Authentifizierungs-Header. Siehe Typen generieren für diese Optionen.
Jede Sammlung mit einem an eine Relation gebundenen Referenzfeld erhält eine zweite Schnittstelle, {Collection}References, unter demselben Slug. getEmDashEntry schränkt sein Ergebnis auf die von der references-Option genannten Felder ein, und die Einträge jeder Seite tragen die Schnittstelle der Zielsammlung:
const { entry: post } = await getEmDashEntry("posts", "my-post", {
references: { author: true },
});
// post?.references?.author.entries[0] carries the Author interface
// post?.references?.related_posts is a type error: it was not selected
Runtime-Rendering und Caching
Die Node.js- und Cloudflare-Blog-Templates setzen output: "server" in astro.config.mjs. Ihre Inhaltsabfragen laufen bei jedem Server-Render, sodass eine neu veröffentlichte Revision für die nächste Anfrage in Frage kommt. Wenn Sie eine Route absichtlich vorgerendern, enthält ihr HTML den Inhalt zum Build-Zeitpunkt und ändert sich erst nach einem weiteren Build.
Wenn Astros Cache aktiviert ist, übergeben Sie den cacheHint der Abfrage an Astro.cache.set(). EmDash verknüpft die Antwort mit den Sammlungs- und Eintrags-Tags, damit das Veröffentlichen betroffene gecachte Seiten invalidieren kann. Vermeiden Sie es, diese Integration durch eine lange feste Cache-Control-Lebensdauer zu ersetzen, es sei denn, verzögerte Updates sind eine bewusste Produktentscheidung.
Für exakte Signaturen und weniger gängige Filter siehe die JavaScript-API-Referenz. Um ein funktionierendes Beispiel um diese Abfragen zu bauen, fahren Sie mit Einen Blog erstellen fort.