Vorschaumodus

Auf dieser Seite

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

  1. Der Admin generiert eine Vorschau-URL für einen Entwurfsbeitrag
  2. Die URL enthält einen signierten _preview-Queryparameter mit einer Ablaufzeit
  3. Die Middleware von EmDash verifiziert das Token automatisch und richtet den Anfrage-Kontext ein
  4. 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 Links
  • pathPattern — 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, ODER
  • token — 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 Format collection:id
  • expiresIn — Token-Gültigkeitsdauer (Standard: "1h")
  • secret — Signiergeheimnis

Gibt zurück: Promise<string>