JavaScript-API-Referenz

Auf dieser Seite

Diese Seite beschreibt die öffentliche API, die Astro-Seiten, Layouts und Komponenten zum Lesen und Präsentieren einer EmDash-Site verwenden. Sie listet nicht jeden Export aus dem Paket-Root emdash auf: Datenbank-Repositories, API-Handler, Migrations-Hilfen, Plugin-Autoren-APIs und andere Server-Interna haben eigene Referenzen oder sind für Framework-Integrationscode gedacht.

Der Site-Template-API-Vertrag in dieser Referenz listet jede Funktion auf, die von EmDashs gepflegten Site-Templates aus emdash importiert wird. Reine Typ-Imports wie MediaValue gehören zur jeweiligen Datenmodell-Dokumentation. Verwandte Funktionen werden in denselben Abschnitten dokumentiert, wenn sie Site-Autoren helfen; die Seite behandelt keine unverwandten Root-Exports.

Site-Template-API-Vertrag

Die gepflegten Templates rufen diese Laufzeit-Helfer auf:

FunktionVerwendung
decodeSlugDynamischen Route-Parameter vor der Suche dekodieren
getEmDashCollectionEinträge in einer Collection lesen und filtern
getEmDashEntryEinen Eintrag per ID oder Slug lesen
getMenuWithCacheHintNavigationsmenü mit Cache-Invalidierung rendern
getSeoMetaSEO-Panel-Werte eines Eintrags und Template-Fallbacks auflösen
getSiteSettingsÖffentliche Site-Identität und andere globale Einstellungen lesen
getSiteSettingsWithCacheHintGlobale Einstellungen mit Cache-Invalidierung lesen
getTaxonomyTermsWithCacheHintTaxonomie-Filter mit Cache-Invalidierung rendern
getTermsForEntriesEine Taxonomie für eine Liste von Einträgen batch-laden
sanitizeHrefUnsichere URL-Schemata vor dem Rendern gespeicherter Links ablehnen
searchVeröffentlichten Content über Collections hinweg durchsuchen

Content queries

Die Abfragefunktionen von EmDash folgen dem Muster von Astros Live Content Collections und liefern { entries, error } bzw. { entry, error } für graceful Error Handling.

getEmDashCollection()

Lädt alle Einträge einer Collection. Das folgende Beispiel lädt alle Posts und prüft auf einen Fehler:

import { getEmDashCollection } from "emdash";

const { entries: posts, error } = await getEmDashCollection("posts");

if (error) {
	console.error("Failed to load posts:", error);
}

Parameters

ParameterTypeDescription
collectionstringCollection slug
optionsCollectionFilterOptional filter options

Options

Der Parameter options akzeptiert folgenden Filter:

interface WhereRange {
	gt?: string;
	gte?: string;
	lt?: string;
	lte?: string;
}

interface CollectionFilter {
	status?: "draft" | "published" | "archived";
	limit?: number;
	cursor?: string; // Keyset pagination — pass a previous `nextCursor`
	offset?: number; // Offset pagination — skip N entries (use with `limit`)
	where?: Record<string, string | string[] | WhereRange>;
	orderBy?: Record<string, "asc" | "desc">;
	locale?: string;
}

cursor und offset schließen sich gegenseitig aus. Die where-Keys können Content-Felder, Taxonomien oder byline benennen; Range-Objekte stehen für geordnete Vergleiche zur Verfügung.

Returns

Die Funktion löst zu einem CollectionResult auf:

interface CollectionResult<T> {
	entries: ContentEntry<T>[]; // Empty array if error or none found
	error?: Error; // Set if query failed
	cacheHint: CacheHint; // Tags and last-modified time for Astro route caching
	nextCursor?: string; // Cursor for the next keyset page, if any
	hasMore?: boolean; // Whether more entries exist beyond this page (when `limit` is set)
}

Examples

Die folgenden Beispiele filtern nach Status und Taxonomie, begrenzen Ergebnisse und behandeln Fehler:

// Get all published posts
const { entries: posts } = await getEmDashCollection("posts", {
	status: "published",
});

// Get latest 5 posts
const { entries: latest } = await getEmDashCollection("posts", {
	limit: 5,
	status: "published",
});

// Filter by taxonomy
const { entries: newsPosts } = await getEmDashCollection("posts", {
	status: "published",
	where: { category: "news" },
});

// Numbered archive page (e.g. /page/3) with offset pagination
const perPage = 20;
const page = Number(Astro.params.page ?? 1);
const { entries: pagePosts, hasMore } = await getEmDashCollection("posts", {
	status: "published",
	limit: perPage,
	offset: (page - 1) * perPage,
	orderBy: { published_at: "desc" },
});

// Handle errors
const { entries, error } = await getEmDashCollection("posts");
if (error) {
	return new Response("Server error", { status: 500 });
}

getEmDashEntry()

Lädt einen einzelnen Eintrag per Slug oder ID. Das folgende Beispiel lädt einen Post und leitet um, wenn er fehlt:

import { getEmDashEntry } from "emdash";

const { entry: post, error } = await getEmDashEntry("posts", "my-post-slug");

if (!post) {
	return Astro.redirect("/404");
}

Parameters

ParameterTypeDescription
collectionstringCollection slug
slugOrIdstringEntry slug or ID
options{ locale?: string; references?: ReferenceSelection }Optional. Locale for slug resolution, and the reference fields to load

Der Preview-Modus wird automatisch behandelt: Wenn die Anfrage ein gültiges _preview-Token hat, liefert die Abfrage Draft-Inhalt. Für den Preview-Zustand ist kein Parameter nötig.

references benennt die zu ladenden Reference-Felder, keyed by field slug. true fordert die erste Seite mit dem Standard-Limit von 50 Einträgen an; die Objektform nimmt limit (höchstens 100) und den cursor einer vorherigen Seite. Ausgelassene Felder werden nicht gelesen; ohne Option führt der Aufruf keine Reference-Abfragen aus.

Returns

Die Funktion löst zu einem EntryResult auf:

interface EntryResult<T, R> {
	entry: ContentEntry<T, R> | null; // null if not found
	error?: Error; // Set only for actual errors, not "not found"
	isPreview: boolean; // true if draft content is being served
	fallbackLocale?: string; // Set when locale fallback returned another locale
	cacheHint: CacheHint; // Tags and last-modified time for Astro route caching
}

Examples

Die folgenden Beispiele laden per Slug und ID, lesen den Preview-Zustand und unterscheiden Fehler von Not Found:

// Get by slug
const { entry: post } = await getEmDashEntry("posts", "hello-world");

// Get by ID
const { entry: post } = await getEmDashEntry("posts", "01HXK5MZSN0FVXT2Q3KPRT9M7D");

// Preview is automatic — isPreview is true when a valid _preview token is present
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);

// Handle errors vs not-found
if (error) {
	return new Response("Server error", { status: 500 });
}
if (!entry) {
	return Astro.redirect("/404");
}

getTranslations()

Lädt die verfügbaren Übersetzungen eines Eintrags anhand von Collection-Slug und Datenbank-ID:

import { getTranslations } from "emdash";

const { translations, error } = await getTranslations("posts", post.data.id);

Das Ergebnis enthält die gemeinsame translationGroup, ein translations-Array und optional error. Jede Übersetzungs-Zusammenfassung umfasst id, locale, slug und status.

resolveEmDashPath()

Löst einen öffentlichen Pfadnamen gegen die für routable Collections konfigurierten URL-Muster auf:

import { resolveEmDashPath } from "emdash";

const result = await resolveEmDashPath("/blog/hello-world");

if (result) {
	console.log(result.collection, result.entry.data.title);
}

Das Ergebnis enthält die gematchte collection, den entry und Route-params. Die Funktion gibt null zurück, wenn kein konfiguriertes URL-Muster passt.

getEditMeta()

Liest die nicht-enumerierbaren Metadaten für visuelles Bearbeiten, die an einen Portable-Text-Wert angehängt sind:

import { getEditMeta } from "emdash";

const meta = getEditMeta(post.data.content);

Sie liefert { collection, id, field } für einen annotierten Wert oder undefined, wenn der Wert keine Annotation hat.

Content types

ContentEntry

Abfragefunktionen liefern Einträge in folgender Form:

interface ContentEntry<T = Record<string, unknown>, R = ReferencePages> {
	id: string;
	data: T;
	references?: R; // One page per reference field requested; absent otherwise
	edit: EditProxy; // Visual editing annotations
}

Der edit-Proxy liefert Annotations für visuelles Bearbeiten. Spreaden Sie ihn auf Elemente, um Inline-Bearbeitung zu aktivieren: {...entry.edit.title}. Außerhalb des Edit-Modus erzeugt er keine Ausgabe.

Das Objekt data enthält alle Content-Felder plus Systemfelder:

  • id - Unique identifier
  • slug - URL-friendly identifier
  • status - “draft” | “published” | “archived”
  • createdAt - Creation time as a Date
  • updatedAt - Last update time as a Date
  • publishedAt - Publication time as a Date, or null; retained when content is unpublished
  • Plus all custom fields defined in your collection schema

ReferencePage

entry.references hält eine Seite pro Reference-Feld, das getEmDashEntry angefordert hat, keyed by field slug:

interface ReferencePage<T = Record<string, unknown>> {
	entries: ContentEntry<T>[];
	nextCursor?: string; // Set when the field holds more entries than the limit
}

Jeder Eintrag wird wie ein direkt geladener Eintrag gemappt, mit einer Ausnahme: Bylines und Taxonomie-Terms werden nicht hydratisiert; data.bylines und data.terms fehlen daher.

getEmDashReferences()

Lädt eine Seite eines einzelnen Reference-Felds, ohne den Eintrag erneut zu lesen, an dem das Feld hängt. Nutzen Sie es, um mit dem nextCursor dieser Seite über die erste Seite hinaus zu gehen.

ParameterTypeDescription
collectionstringCollection slug of the entry that holds the field
slugOrIdstringEntry slug or ID
fieldstringReference field slug
options{ limit?: number; cursor?: string; locale?: string }Optional. Defaults to 50 entries, at most 100

Das folgende Beispiel lädt die nächsten 20 verknüpften Einträge:

import { getEmDashReferences } from "emdash";

const { entries, nextCursor, error } = await getEmDashReferences(
	"posts",
	post.id,
	"related_posts",
	{ cursor, limit: 20 },
);

error wird nur bei echten Fehlern gesetzt. Unbekanntes Feld, fehlender Eintrag oder ein Eintrag, den die Anfrage nicht sehen darf, führen alle zu einem leeren entries-Array.

Das Ergebnis trägt außerdem einen cacheHint mit den Zeilen, die die Seite gelesen hat. Eine Route, die ihr Rendering cached, sollte ihn in den eigenen Hint mergen – wie getEmDashEntry({ references }) den der ersten Seite einfoldet – damit Schreiben an einen dieser Einträge die Seite invalidiert.

URL helpers

decodeSlug() and slugify()

Verwenden Sie decodeSlug() auf einem dynamischen Route-Parameter, bevor Sie ihn an eine Content-Abfrage übergeben. Sie gibt undefined für einen fehlenden Parameter zurück und wendet sonst decodeURIComponent() an, das bei fehlerhafter Prozent-Kodierung wirft:

function decodeSlug(raw: string | undefined): string | undefined;
import { decodeSlug, getEmDashEntry } from "emdash";

const slug = decodeSlug(Astro.params.slug);
const { entry } = slug ? await getEmDashEntry("posts", slug) : { entry: null };

slugify(value) wandelt Text in einen kleingeschriebenen, bindestrich-getrennten Slug um. Nutzen Sie es, wenn ein Template aus einem Label einen Slug bauen muss; gespeicherte Entry-Slugs kommen bereits von EmDash.

sanitizeHref() and isSafeHref()

Diese Helfer lehnen unsichere Link-Schemata wie javascript: ab. isSafeHref(value) liefert einen Boolean. sanitizeHref(value) liefert die ursprüngliche sichere URL oder "#", wenn der Wert leer oder unsicher ist.

import { sanitizeHref } from "emdash";

const href = sanitizeHref(menuItem.url);

Preview system

generatePreviewToken()

Erzeugt ein Preview-Token für Draft-Inhalt. Das folgende Beispiel erstellt ein Token, das in einer Stunde abläuft:

import { generatePreviewToken } from "emdash";

const token = await generatePreviewToken({
	contentId: "posts:01HXK5MZSN...",
	secret: process.env.EMDASH_PREVIEW_SECRET!,
	expiresIn: 3600, // 1 hour
});

contentId muss das Format collection:id verwenden. expiresIn akzeptiert Sekunden oder eine Dauer mit Suffix s, m, h, d oder w und standardmäßig "1h". Bewahren Sie das Signing-Secret auf dem Server auf.

verifyPreviewToken()

Verifiziert ein Preview-Token und liest dessen Payload:

import { verifyPreviewToken } from "emdash";

const result = await verifyPreviewToken({
	token,
	secret: process.env.EMDASH_PREVIEW_SECRET!,
});

if (result.valid) {
	const { cid, exp, iat } = result.payload;
	// cid is "collection:id" format, e.g. "posts:my-draft-post"
}

Übergeben Sie entweder token oder url mit dem Signing-secret. Ein ungültiges Token liefert { valid: false, error }, wobei error "none", "malformed", "invalid" oder "expired" ist.

parseContentId()

Teilt den collection:id-Wert einer Preview-Payload in seine beiden Teile:

import { parseContentId } from "emdash";

const parsed = parseContentId(result.payload.cid);

Sie liefert { collection, id } und wirft, wenn der Wert keinen Doppelpunkt enthält.

getPreviewUrl() and buildPreviewUrl()

getPreviewUrl() erstellt und signiert eine Preview-URL. Sie akzeptiert collection, id und secret sowie optional expiresIn, baseUrl, pathPattern und locale:

import { getPreviewUrl } from "emdash";

const previewUrl = await getPreviewUrl({
	collection: "posts",
	id: post.id,
	secret: process.env.EMDASH_PREVIEW_SECRET!,
	pathPattern: "/blog/{id}",
});

Ohne baseUrl liefert sie eine site-relative URL. Nutzen Sie buildPreviewUrl({ path, token, baseUrl? }), wenn bereits ein Token existiert.

isPreviewRequest()

Prüft, ob eine Anfrage ein Preview-Token enthält, und liest es:

import { isPreviewRequest, getPreviewToken } from "emdash";

if (isPreviewRequest(Astro.url)) {
	const token = getPreviewToken(Astro.url);
	// Verify and show preview content
}

getPreviewToken() liefert den Query-Parameter _preview oder null, wenn er fehlt. Die EmDash-Middleware verifiziert normale Preview-Anfragen und stellt den Preview-Zustand getEmDashEntry() automatisch bereit; diese Helfer sind für custom Preview-Routen und Tooling gedacht.

Content converters

Konvertiert zwischen Portable Text und ProseMirror:

import { prosemirrorToPortableText, portableTextToProsemirror } from "emdash";

// From ProseMirror (editor) to Portable Text (storage)
const portableText = prosemirrorToPortableText(prosemirrorDoc);

// From Portable Text to ProseMirror
const prosemirrorDoc = portableTextToProsemirror(portableText);

Site settings

Site-weite Einstellungen lesen Sie mit getSiteSettings und getSiteSetting:

function getSiteSettings(): Promise<Partial<SiteSettings>>;
function getSiteSetting<K extends SiteSettingKey>(key: K): Promise<SiteSettings[K] | undefined>;
import { getSiteSettings, getSiteSetting } from "emdash";

// Get all settings
const settings = await getSiteSettings();

// Get single setting
const title = await getSiteSetting("title");

Einstellungen sind über die Runtime-API read-only. Zum Aktualisieren nutzen Sie die Admin-API.

getSiteSettings() liefert ein partielles Objekt, weil unset Keys fehlen. Media-Einstellungen wie Site-Logo und Favicon werden vor der Rückgabe zu Media-Reference-Objekten aufgelöst.

getSiteSettingsWithCacheHint() liefert { data, cacheHint }. Übergeben Sie den Hint an Astro.cache.set(), wenn ein Astro-Route-Cache nach Änderungen an Site-Einstellungen invalidiert werden soll.

SEO

SEO-Panel-Werte und Content-Fallbacks für eine Seite lösen Sie mit getSeoMeta() auf:

function getSeoMeta<T>(content: SeoContentInput<T>, options?: SeoMetaOptions): SeoMeta;

interface SeoMetaOptions {
	siteTitle?: string;
	siteUrl?: string;
	titleSeparator?: string; // Default: " | "
	path?: string;
	defaultOgImage?: string;
	defaultTitle?: string;
	defaultDescription?: string;
}

interface SeoMeta {
	title: string;
	description: string | null;
	ogTitle: string;
	ogDescription: string | null;
	ogImage: string | null;
	canonical: string | null;
	robots: string | null;
}
import { getSeoMeta } from "emdash";

const meta = getSeoMeta(post, {
	siteTitle: "Example Blog",
	siteUrl: "https://example.com",
	path: `/blog/${post.data.slug}`,
});

Sie liefert aufgelösten Titel, Description, Open-Graph-Werte, Canonical-URL und Robots-Wert. getContentSeo(content) liefert das rohe SEO-Objekt ohne Template-Fallbacks.

Für übersetzten Content liefert getHreflangAlternates(collection, entryId, { siteUrl? }) veröffentlichte, routable Locale-Varianten als { hreflang, href }-Objekte und fügt einen x-default-Eintrag hinzu. Sie liefert ein leeres Array, wenn Internationalisierung deaktiviert ist, der aktuelle Eintrag noindex hat oder keine absolute Site-URL verfügbar ist.

Comments

Genehmigte Kommentare und deren Anzahl für einen Eintrag laden:

import { getCommentCount, getComments } from "emdash";

const { items: comments, total } = await getComments({
	collection: "posts",
	contentId: post.data.id,
	threaded: true,
	reactions: true,
	sort: "best",
});

const count = await getCommentCount("posts", post.data.id);

threaded verschachtelt Antworten unter dem Elternkommentar. Standard-sort ist "oldest"; "best" sortiert Top-Level-Kommentare nach Reactions und aktiviert Reaktionszähler automatisch. Serverseitige Kommentar-Abfragen liefern höchstens 500 genehmigte Kommentare; für Pagination nutzen Sie die REST-API.

Navigationsmenüs laden und deren Items iterieren, einschließlich verschachtelter Kinder:

function getMenu(name: string, options?: { locale?: string }): Promise<Menu | null>;
function getMenus(options?: { locale?: string }): Promise<MenuSummary[]>;
import { getMenu, getMenus } from "emdash";

// Get all menus
const menus = await getMenus();

// Get specific menu with items
const primaryMenu = await getMenu("primary");

if (primaryMenu) {
	primaryMenu.items.forEach(item => {
		console.log(item.label, item.url);
		// Nested items for dropdowns
		item.children.forEach(child => console.log("  -", child.label));
	});
}

getMenu(name, { locale? }) folgt der konfigurierten Locale-Fallback-Kette. getMenus({ locale? }) listet Menu-Zusammenfassungen für die aufgelöste Anfrage- oder konfigurierte Locale; ohne Internationalisierung listet es jede Locale. getMenuWithCacheHint() liefert { data, cacheHint } für Routen mit Astro-Cache.

Bylines

Autorenprofil per ID oder Slug laden oder Einträge laden, die einem Byline zugeschrieben sind:

import { getByline, getBylineBySlug, getEntriesByByline } from "emdash";

const profile = await getBylineBySlug("jane-doe", { locale: "en" });
const posts = profile
	? await getEntriesByByline("posts", profile.translationGroup ?? profile.id)
	: [];

getByline(id) liefert ein Profil oder null. Die Slug-Suche akzeptiert optional eine Locale und folgt der Locale-Fallback-Kette. Content-Abfragen hydratisieren die geordneten Credits eines Eintrags bereits in entry.data.bylines; nutzen Sie diese standalone Helfer für Autorenseiten und Byline-Archive.

Taxonomies

Taxonomie-Terms, einen einzelnen Term, die Terms eines Eintrags oder Einträge nach Term laden:

function getTaxonomyTerms(
	taxonomyName: string,
	options?: { locale?: string; includeCounts?: boolean },
): Promise<TaxonomyTerm[]>;

function getTerm(
	taxonomyName: string,
	slug: string,
	options?: { locale?: string; includeCounts?: boolean },
): Promise<TaxonomyTerm | null>;

function getTermsForEntries(
	collection: string,
	entryIds: string[],
	taxonomyName: string,
	options?: { locale?: string },
): Promise<Map<string, TaxonomyTerm[]>>;
import { getTaxonomyTerms, getTerm, getEntryTerms, getEntriesByTerm } from "emdash";

// Get all terms for a taxonomy (tree structure for hierarchical)
const categories = await getTaxonomyTerms("category");

// Get single term
const news = await getTerm("category", "news");

// Get terms assigned to a content entry
const postCategories = await getEntryTerms("posts", "post-123", "category");

// Get entries with a specific term
const newsPosts = await getEntriesByTerm("posts", "category", "news");

getTaxonomyDefs({ locale? }) listet eine Definition pro Taxonomie; getTaxonomyDef(name, { locale? }) liefert eine Definition oder null, wenn keine Locale die Taxonomie definiert. Beide lösen eine Taxonomie in jeder Locale auf; Taxonomien und Terms übersetzen erklärt, welches Locale-Label sie verwenden. Term-Lookups folgen der konfigurierten Fallback-Kette.

getTaxonomyTerms(name, { locale?, includeCounts? }) liefert für hierarchische Taxonomien einen Baum und enthält standardmäßig sichtbare Entry-Counts. Übergeben Sie includeCounts: false, wenn das Template keine Counts rendert. Content-Abfragen hydratisieren zugewiesene Terms auch in data.terms jedes Eintrags; nutzen Sie getEntryTerms(), wenn Sie nur Collection-Name und Entry-ID haben.

Für Archivseiten, die Terms neben vielen Einträgen rendern, bündeln Sie den Lookup statt getEntryTerms() in einer Schleife:

import { getAllTermsForEntries, getTermsForEntries } from "emdash";

const termsByPost = await getTermsForEntries(
	"posts",
	posts.map(post => post.data.id),
	"category",
);

const allTermsByPost = await getAllTermsForEntries(
	"posts",
	posts.map(post => post.data.id),
);

getTermsForEntries() liefert eine Map von Entry-ID zu Terms der angeforderten Taxonomie. getAllTermsForEntries() liefert eine Map von Entry-ID zu Terms gruppiert nach Taxonomie-Name. getTaxonomyTermsWithCacheHint() liefert { data, cacheHint } für eine Astro-gecachte Route.

Widget areas

Widget Areas und die enthaltenen Widgets laden:

import { getWidgetArea, getWidgetAreas } from "emdash";

// Get all widget areas
const areas = await getWidgetAreas();

// Get specific widget area with widgets
const sidebar = await getWidgetArea("sidebar");

if (sidebar) {
	sidebar.widgets.forEach(widget => {
		console.log(widget.type, widget.title);
	});
}

getWidgetAreaWithCacheHint(name) liefert { data, cacheHint } für Routen mit Astro-Cache. Widget Areas und ihre Widgets sind nach konfigurierter Sortierreihenfolge geordnet.

Sections

Sections laden und filtern:

import { getSection, getSections } from "emdash";

// Get all sections (paginated)
const { items, nextCursor } = await getSections();

// Filter sections
const { items: themeSections } = await getSections({ source: "theme" });
const { items: results } = await getSections({ search: "newsletter" });

// Get a single section by slug
const cta = await getSection("newsletter-cta");

getSections(options?) liefert { items: Section[]; nextCursor?: string }. Optionen sind source ("theme" | "user" | "import"), search, limit (Standard 50, max. 100) und cursor.

Globale Suche über Collections. Ergebnisse enthalten hervorgehobene Snippets:

function search(query: string, options?: SearchOptions): Promise<SearchResponse>;

interface SearchOptions {
	collections?: string[]; // Default: every searchable collection
	status?: string; // Default: "published"
	locale?: string; // Default: all locales
	limit?: number; // Default: 20
	cursor?: string;
	scope?: "all" | "title"; // Default: "all"
}

interface SearchResponse {
	items: SearchResult[];
	nextCursor?: string;
}
import { search } from "emdash";

const results = await search("hello world", {
	collections: ["posts", "pages"],
	status: "published",
	limit: 20,
});

// search() resolves to { items, nextCursor? }
results.items.forEach(result => {
	console.log(result.title);
	console.log(result.snippet); // Contains <mark> tags
	console.log(result.score);
});

// Paginate: pass the previous nextCursor back as `cursor` to get the next page.
// nextCursor is undefined once there are no more results.
if (results.nextCursor) {
	const next = await search("hello world", {
		collections: ["posts", "pages"],
		limit: 20,
		cursor: results.nextCursor,
	});
}

Mit scope: "title" matcht die Abfrage nur das Title-Feld jeder Collection statt des voll indexierten Texts — nützlich für Picker und Autocomplete, wo Body-Matches überraschen würden. Collections, deren Title-Feld nicht für die Suche indexiert ist, liefern unter diesem Scope keine Ergebnisse.

Error handling

Content-Abfragen liefern operative Fehler im Ergebnis statt zu werfen. Ein fehlender Eintrag ist kein operativer Fehler: entry ist null und error bleibt undefined.

const { entry, error } = await getEmDashEntry("posts", slug);

if (error) {
	return new Response("Content could not be loaded", { status: 500 });
}

if (!entry) {
	return Astro.redirect("/404");
}

Helfer ohne Result-Envelope können werfen, wenn die Eingabe ungültig ist oder eine Datenbankoperation fehlschlägt. Behandeln Sie diese Fehler an der Route-Grenze, wenn die Seite einen sinnvollen Fallback bieten kann. Die Repository- und Handler-Fehlerklassen tieferer Server-Integrationen liegen außerhalb dieser Site-Template-API.