Mit dem Vorschausystem von EmDash können Redakteur:innen unveröffentlichte Inhalte über sichere, zeitlich begrenzte URLs ansehen. Vorschau-Links nutzen HMAC-SHA256-signierte Tokens, die Sie mit Prüfer:innen teilen können, ohne den gesamten Entwurf offenzulegen.
Funktionsweise
- Die Verwaltung erzeugt eine Vorschau-URL für einen Entwurfsbeitrag
- Die URL enthält einen signierten
_preview-Abfrageparameter mit Ablaufzeit - Die Middleware von EmDash prüft den Token automatisch und richtet den Request-Kontext ein
- Ihr Template ruft
getEmDashEntry()wie gewohnt auf — Entwurfsinhalte werden automatisch ausgeliefert
Die Vorschau ist implizit. Ihr Template muss keine Tokens verarbeiten oder Vorschau-Optionen übergeben — Middleware und Abfragefunktionen erledigen alles über AsyncLocalStorage.
Vorschau einrichten
Fügen Sie ein Vorschau-Geheimnis in Ihre Umgebung ein:
EMDASH_PREVIEW_SECRET="your-random-secret-key-here"
Erzeugen Sie eine sichere Zufallszeichenkette. Dieses Geheimnis signiert und verifiziert Vorschau-Tokens.
Das war’s. Ihre bestehenden Templates funktionieren mit der Vorschau automatisch:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
// Keine spezielle Vorschau-Logik nötig — die Middleware
// erkennt _preview-Tokens und liefert Entwurfsinhalte automatisch
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Server error", { 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 Flag isPreview ist true, wenn Entwurfsinhalte über einen gültigen Vorschau-Token ausgeliefert werden.
Vorschau-URLs erzeugen
Nutzen Sie getPreviewUrl(), um Vorschau-Links zu erzeugen:
import { getPreviewUrl } from "emdash";
const previewUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
expiresIn: "1h",
});
// Returns: /posts/my-draft-post?_preview=eyJjaWQ...
Mit Basis-URL für absolute Links:
const fullUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
baseUrl: "https://example.com",
});
// Returns: https://example.com/posts/my-draft-post?_preview=eyJjaWQ...
Mit benutzerdefiniertem Pfadmuster:
const blogUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
pathPattern: "/blog/{id}",
});
// Returns: /blog/my-draft-post?_preview=eyJjaWQ...
Token-Ablauf
Steuern Sie, wie lange Vorschau-Links gültig bleiben:
// 1 Stunde gültig (Standard)
await getPreviewUrl({ ..., expiresIn: "1h" });
// 30 Minuten gültig
await getPreviewUrl({ ..., expiresIn: "30m" });
// 1 Tag gültig
await getPreviewUrl({ ..., expiresIn: "1d" });
// 2 Wochen gültig
await getPreviewUrl({ ..., expiresIn: "2w" });
// 3600 Sekunden gültig
await getPreviewUrl({ ..., expiresIn: 3600 });
Unterstützte Einheiten: s (Sekunden), m (Minuten), h (Stunden), d (Tage), w (Wochen).
Tokens verifizieren
Nutzen Sie verifyPreviewToken(), um eingehende Vorschau-Anfragen zu prüfen:
import { verifyPreviewToken } from "emdash";
// Aus einer URL (extrahiert den _preview-Abfrageparameter)
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, ob der 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); // Ablauf-Zeitstempel
console.log(result.payload.iat); // Ausstellungs-Zeitstempel
} else {
// Token ist ungültig
console.log(result.error);
// "none" - kein Token vorhanden
// "malformed" - Token-Struktur ungültig
// "invalid" - Signaturprüfung fehlgeschlagen
// "expired" - Token abgelaufen
}
Vorschau-Hinweis
Sie können einen visuellen Hinweis anzeigen, wenn Inhalte in der Vorschau angezeigt werden. Das von getEmDashEntry zurückgegebene Flag isPreview 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 einen Vorschau-Token enthält:
import { isPreviewRequest } from "emdash";
if (isPreviewRequest(Astro.url)) {
// Vorschau-Anfrage verarbeiten
}
getPreviewToken(url)
Token-Zeichenkette aus einer URL extrahieren:
import { getPreviewToken } from "emdash";
const token = getPreviewToken(Astro.url);
// Gibt die Token-Zeichenkette oder null zurück
parseContentId(contentId)
Content-ID in Collection und ID zerlegen:
import { parseContentId } from "emdash";
const { collection, id } = parseContentId("posts:my-draft-post");
// { collection: "posts", id: "my-draft-post" }
Token-Format
Vorschau-Tokens nutzen ein kompaktes Format: base64url(payload).base64url(signature)
Die Nutzdaten enthalten:
cid— Content-ID im Formatcollection:idexp— Ablauf-Zeitstempel (Sekunden seit Epoch)iat— Ausstellungs-Zeitstempel (Sekunden seit Epoch)
Tokens werden mit HMAC-SHA256 unter Verwendung Ihres Vorschau-Geheimnisses signiert.
Vollständiges Beispiel
Eine vollständige Blogbeitragsseite mit Vorschau und Unterstützung für visuelles Bearbeiten:
---
import { getEmDashEntry } from "emdash";
import BaseLayout from "../../layouts/Base.astro";
import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
// Vorschau ist automatisch — Middleware übernimmt Token-Prüfung
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Server error", { 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.publishedAt && (
<span class="draft-indicator">Entwurf</span>
)}
</header>
<div class="content" {...entry.edit.content}>
<PortableText value={entry.data.content} />
</div>
</article>
</BaseLayout>
Die Spreads {...entry.edit} und {...entry.edit.title} fügen data-emdash-ref-Attribute hinzu, die visuelles Bearbeiten für authentifizierte Redakteur:innen ermöglichen. In der Produktion erzeugen sie keine Ausgabe.
API-Referenz
getPreviewUrl(options)
Erzeugt eine Vorschau-URL mit signiertem Token.
Optionen:
collection— Collection-Slug (string)id— Content-ID oder Slug (string)secret— Signatur-Geheimnis (string)expiresIn— Gültigkeitsdauer des Tokens (Standard:"1h")baseUrl— Optionale Basis-URL für absolute LinkspathPattern— URL-Muster mit Platzhaltern{collection}und{id}(Standard:"/{collection}/{id}")
Rückgabe: Promise<string>
verifyPreviewToken(options)
Verifiziert einen Vorschau-Token.
Optionen:
secret— Verifizierungs-Geheimnis (string)url— URL zur Token-Extraktion, ODERtoken— Token-Zeichenkette direkt
Rückgabe: Promise<VerifyPreviewTokenResult>
type VerifyPreviewTokenResult =
| { valid: true; payload: PreviewTokenPayload }
| { valid: false; error: "invalid" | "expired" | "malformed" | "none" };
generatePreviewToken(options)
Erzeugt einen Token ohne URL zu bauen.
Optionen:
contentId— Content-ID im Formatcollection:idexpiresIn— Gültigkeitsdauer (Standard:"1h")secret— Signatur-Geheimnis
Rückgabe: Promise<string>