Das Vorschausystem von EmDash ermöglicht Redakteuren, unveröffentlichte Inhalte über sichere, zeitlich begrenzte URLs anzuzeigen. Vorschau-Links verwenden HMAC-SHA256-signierte Token, die Sie mit Prüfern teilen können, ohne Ihre gesamten Entwurfsinhalte offenzulegen.
Funktionsweise
- Der Admin generiert eine Vorschau-URL für einen Entwurfsbeitrag
- Die URL enthält einen signierten
_preview-Queryparameter mit einer Ablaufzeit - Die Middleware von EmDash verifiziert das Token automatisch und richtet den Anfrage-Kontext ein
- Ihr Template-Code ruft
getEmDashEntry()wie gewohnt auf — Entwurfsinhalte werden automatisch ausgeliefert
Die Vorschau ist implizit. Die Middleware verifiziert das Token und die Abfragefunktionen lesen es über AsyncLocalStorage, sodass derselbe Template-Code Entwurfsinhalte während einer Vorschau und veröffentlichte Inhalte anderweitig ausliefert.
Vorschau einrichten
Die Vorschau funktioniert sobald EmDash installiert ist. Bei der ersten Verwendung generiert EmDash ein seitenspezifisches Vorschau-Geheimnis und speichert es in der Datenbank, sodass der Standardfall keine Konfiguration benötigt.
Setzen Sie EMDASH_PREVIEW_SECRET in Ihrer Umgebung nur, wenn Sie:
- Das Geheimnis über mehrere Prozesse teilen müssen (z.B. ein separater Vorschau-Worker, der URLs signiert und an Ihre Hauptseite zur Verifizierung sendet)
- Das Geheimnis aus Compliance-/Audit-Gründen auf einen von Ihnen kontrollierten Wert festlegen möchten
- Bei der Wiederherstellung aus einem Backup zu einem bekannten Wert migrieren müssen
# Optional: das automatisch generierte Geheimnis überschreiben
EMDASH_PREVIEW_SECRET="your-random-secret-key-here"
Wenn gesetzt, hat der Umgebungswert Vorrang vor dem in der DB gespeicherten Wert.
Bestehende Templates funktionieren automatisch mit der Vorschau, wie auf der folgenden Seite:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
// Keine spezielle Vorschaubehandlung nötig — die Middleware
// erkennt _preview-Token und liefert Entwurfsinhalte automatisch
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Serverfehler", { status: 500 });
}
if (!entry) {
return Astro.redirect("/404");
}
---
{isPreview && (
<div class="preview-banner">
Sie sehen eine Vorschau. Dieser Inhalt ist nicht veröffentlicht.
</div>
)}
<article>
<h1>{entry.data.title}</h1>
</article>
Das isPreview-Flag ist true, wenn Entwurfsinhalte über ein gültiges Vorschau-Token ausgeliefert werden.
Vorschau-URLs generieren
Verwenden Sie getPreviewUrl(), um Vorschau-Links zu erstellen. Die Funktion nimmt das Geheimnis als explizites Argument:
import { getPreviewUrl } from "emdash";
const previewUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
expiresIn: "1h",
});
// Gibt zurück: /posts/my-draft-post?_preview=eyJjaWQ...
Wenn EMDASH_PREVIEW_SECRET nicht gesetzt ist, generiert und speichert EmDash automatisch ein seitenspezifisches Geheimnis in der Datenbank für die Token-Verifizierung. Der getPreviewUrl()-Template-Helfer erfordert weiterhin, dass Sie das Geheimnis explizit übergeben — setzen Sie Ihre Umgebungsvariable, wenn Sie ihn aus Seitentemplates aufrufen. Die meisten Seiten verwenden stattdessen den „Vorschau-Link generieren”-Button der Admin-UI, der über die API geht und das aufgelöste Geheimnis automatisch verwendet.
Übergeben Sie baseUrl, um eine absolute URL zu generieren:
const fullUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
baseUrl: "https://example.com",
});
// Gibt zurück: https://example.com/posts/my-draft-post?_preview=eyJjaWQ...
Übergeben Sie pathPattern, um eine URL mit einem benutzerdefinierten Pfad zu generieren:
const blogUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
pathPattern: "/blog/{id}",
});
// Gibt zurück: /blog/my-draft-post?_preview=eyJjaWQ...
Locale-bewusste Pfade
pathPattern unterstützt auch einen {locale}-Platzhalter. Übergeben Sie ein leeres locale, wenn der Eintrag in der Standard-Locale ist und prefixDefaultLocale false ist; benachbarte Schrägstriche, die durch den leeren Wert entstehen, werden automatisch zusammengefasst.
Das folgende Beispiel erstellt eine locale-prefixierte Vorschau-URL:
await getPreviewUrl({
collection: "posts",
id: "hello",
secret,
pathPattern: "/{locale}/{id}",
locale: "pt-br",
});
// Gibt zurück: /pt-br/hello?_preview=...
await getPreviewUrl({
collection: "posts",
id: "hello",
secret,
pathPattern: "/{locale}/{id}",
locale: "", // Standard-Locale, kein Präfix
});
// Gibt zurück: /hello?_preview=...
Der „Auf der Seite anzeigen”-Link des Admins geht über POST /_emdash/api/content/{collection}/{id}/preview-url, der die Locale des Eintrags liest, die i18n-Konfiguration der Seite nachschlägt und die locale automatisch liefert. Um das Standardmuster zu ändern, das von diesem Endpunkt verwendet wird, setzen Sie EMDASH_PREVIEW_PATH_PATTERN (z.B. /{locale}/{id}) — Anfrage-Bodies haben weiterhin Vorrang, wenn sie ihr eigenes pathPattern enthalten.
Token-Ablauf
Kontrollieren Sie, wie lange Vorschau-Links gültig bleiben:
// Gültig für 1 Stunde (Standard)
await getPreviewUrl({ ..., expiresIn: "1h" });
// Gültig für 30 Minuten
await getPreviewUrl({ ..., expiresIn: "30m" });
// Gültig für 1 Tag
await getPreviewUrl({ ..., expiresIn: "1d" });
// Gültig für 2 Wochen
await getPreviewUrl({ ..., expiresIn: "2w" });
// Gültig für 3600 Sekunden
await getPreviewUrl({ ..., expiresIn: 3600 });
Unterstützte Einheiten: s (Sekunden), m (Minuten), h (Stunden), d (Tage), w (Wochen).
Token verifizieren
Verwenden Sie verifyPreviewToken(), um eingehende Vorschau-Anfragen zu validieren:
import { verifyPreviewToken } from "emdash";
// Aus einer URL (extrahiert den _preview-Queryparameter)
const result = await verifyPreviewToken({
url: Astro.url,
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
});
// Oder direkt mit einem Token
const result = await verifyPreviewToken({
token: someTokenString,
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
});
Das Ergebnis zeigt an, ob das Token gültig ist:
if (result.valid) {
// Token ist gültig
console.log(result.payload.cid); // "posts:my-draft-post"
console.log(result.payload.exp); // Ablaufzeitstempel
console.log(result.payload.iat); // Ausstellungszeitstempel
} else {
// Token ist ungültig
console.log(result.error);
// "none" - kein Token vorhanden
// "malformed" - Token-Struktur ist ungültig
// "invalid" - Signaturverifizierung fehlgeschlagen
// "expired" - Token ist abgelaufen
}
Vorschau-Indikator
Sie können einen visuellen Indikator anzeigen, wenn Inhalte in der Vorschau angezeigt werden. Das isPreview-Flag, das von getEmDashEntry zurückgegeben wird, zeigt an, wenn Entwurfsinhalte ausgeliefert werden:
{isPreview && (
<div class="preview-banner" role="alert">
<strong>Vorschau</strong> — Sie sehen unveröffentlichte Inhalte.
<a href={Astro.url.pathname}>Vorschau beenden</a>
</div>
)}
Hilfsfunktionen
isPreviewRequest(url)
Prüfen, ob eine URL ein Vorschau-Token enthält:
import { isPreviewRequest } from "emdash";
if (isPreviewRequest(Astro.url)) {
// Vorschau-Anfrage behandeln
}
getPreviewToken(url)
Token-String aus einer URL extrahieren:
import { getPreviewToken } from "emdash";
const token = getPreviewToken(Astro.url);
// Gibt den Token-String oder null zurück
parseContentId(contentId)
Eine Content-ID in Sammlung und ID parsen:
import { parseContentId } from "emdash";
const { collection, id } = parseContentId("posts:my-draft-post");
// { collection: "posts", id: "my-draft-post" }
Token-Sicherheit
Vorschau-Token sind signiert und zeitlich begrenzt. Die CLI und die Hilfsfunktionen generieren und verifizieren sie für Sie; Sie konstruieren oder parsen sie nicht manuell. Ein Token identifiziert einen Eintrag und funktioniert nach Ablauf nicht mehr.
Vollständiges Beispiel
Die folgende Seite kombiniert Vorschau- und visuelles Editing-Support in einem vollständigen Blogbeitrags-Template:
---
import { getEmDashEntry } from "emdash";
import BaseLayout from "../../layouts/Base.astro";
import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
// Vorschau ist automatisch — Middleware behandelt die Token-Verifizierung
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Serverfehler", { status: 500 });
}
if (!entry) {
return Astro.redirect("/404");
}
---
<BaseLayout title={entry.data.title}>
{isPreview && (
<div class="preview-banner" role="alert">
<strong>Vorschau</strong> — Dieser Inhalt ist nicht veröffentlicht.
</div>
)}
<article {...entry.edit}>
<header>
<h1 {...entry.edit.title}>{entry.data.title}</h1>
{entry.data.publishedAt && (
<time datetime={entry.data.publishedAt.toISOString()}>
{entry.data.publishedAt.toLocaleDateString()}
</time>
)}
{isPreview && entry.data.status === "draft" && (
<span class="draft-indicator">Entwurf</span>
)}
</header>
<div class="content" {...entry.edit.content}>
<PortableText value={entry.data.content} />
</div>
</article>
</BaseLayout>
Beachten Sie die {...entry.edit}- und {...entry.edit.title}-Spreads — diese fügen data-emdash-ref-Attribute hinzu, die visuelles Editing für authentifizierte Redakteure ermöglichen. In der Produktion erzeugen sie keine Ausgabe.
API-Referenz
getPreviewUrl(options)
Generiert eine Vorschau-URL mit einem signierten Token.
Optionen:
collection— Sammlungs-Slug (string)id— Content-ID oder Slug (string)secret— Signiergeheimnis (string)expiresIn— Token-Gültigkeitsdauer (Standard:"1h")baseUrl— Optionale Basis-URL für absolute LinkspathPattern— URL-Muster mit{collection},{id}und{locale}-Platzhaltern (Standard:"/{collection}/{id}")locale— Wert, der für{locale}eingesetzt wird. Leerer String lässt das Locale-Segment weg (Schrägstriche werden zusammengefasst).
Gibt zurück: Promise<string>
verifyPreviewToken(options)
Verifiziert ein Vorschau-Token.
Optionen:
secret— Verifizierungsgeheimnis (string)url— URL, aus der das Token extrahiert wird, ODERtoken— Token-String direkt
Gibt zurück: Promise<VerifyPreviewTokenResult>
type VerifyPreviewTokenResult =
| { valid: true; payload: PreviewTokenPayload }
| { valid: false; error: "invalid" | "expired" | "malformed" | "none" };
generatePreviewToken(options)
Generiert ein Token ohne eine URL zu erstellen.
Optionen:
contentId— Content-ID im Formatcollection:idexpiresIn— Token-Gültigkeitsdauer (Standard:"1h")secret— Signiergeheimnis
Gibt zurück: Promise<string>