Vorschaumodus

Auf dieser Seite

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

  1. Die Verwaltung erzeugt eine Vorschau-URL für einen Entwurfsbeitrag
  2. Die URL enthält einen signierten _preview-Abfrageparameter mit Ablaufzeit
  3. Die Middleware von EmDash prüft den Token automatisch und richtet den Request-Kontext ein
  4. 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 Format collection:id
  • exp — 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 Links
  • pathPattern — 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, ODER
  • token — 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 Format collection:id
  • expiresIn — Gültigkeitsdauer (Standard: "1h")
  • secret — Signatur-Geheimnis

Rückgabe: Promise<string>