Vorschau-Modus

Auf dieser Seite

Das Vorschau-System von EmDash ermöglicht es Redakteuren, unveröffentlichte Inhalte über sichere, zeitlich begrenzte URLs anzuzeigen. Vorschau-Links verwenden HMAC-SHA256-signierte Tokens, die Sie mit Prüfern teilen können, ohne Ihren gesamten Entwurfsinhalt offenzulegen.

Wie es funktioniert

  1. Der Administrator generiert eine Vorschau-URL für einen Entwurfsbeitrag
  2. Die URL enthält einen signierten _preview Query-Parameter mit einer Ablaufzeit
  3. Die Middleware von EmDash verifiziert automatisch das Token und richtet den Request-Kontext ein
  4. Ihr Template-Code ruft getEmDashEntry() wie gewohnt auf — Entwurfsinhalte werden automatisch bereitgestellt

Die Vorschau ist implizit. Die Middleware verifiziert das Token und die Query-Funktionen lesen es über AsyncLocalStorage, sodass derselbe Template-Code während einer Vorschau Entwurfsinhalte und ansonsten veröffentlichte Inhalte bereitstellt.

Einrichtung der Vorschau

Die Vorschau funktioniert, sobald EmDash installiert ist. Bei der ersten Verwendung generiert EmDash ein standortspezifisches Vorschau-Secret und speichert es in der Datenbank, sodass im Normalfall keine Konfiguration erforderlich ist.

Setzen Sie EMDASH_PREVIEW_SECRET in Ihrer Umgebung nur, wenn Sie:

  • Das Secret über mehrere Prozesse hinweg teilen müssen (z.B. einen separaten Vorschau-Worker, der URLs signiert und sie zur Verifizierung an Ihre Hauptseite sendet)
  • Das Secret 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öchten
# Optional: Überschreibt das automatisch generierte Secret
EMDASH_PREVIEW_SECRET="your-random-secret-key-here"

Wenn gesetzt, hat der Umgebungswert Vorrang vor dem in der Datenbank gespeicherten Wert.

Bestehende Templates funktionieren automatisch mit der Vorschau, wie auf der folgenden Seite:

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;

// Keine spezielle Vorschau-Behandlung erforderlich — die Middleware
// erkennt _preview Tokens und stellt Entwurfsinhalte automatisch bereit
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 isPreview Flag ist true, wenn Entwurfsinhalte über ein gültiges Vorschau-Token bereitgestellt werden.

Generieren von Vorschau-URLs

Verwenden Sie getPreviewUrl(), um Vorschau-Links zu erstellen. Die Funktion nimmt das Secret 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 standortspezifisches Secret in der Datenbank zur Token-Verifizierung. Der getPreviewUrl() Template-Helper erfordert dennoch, dass Sie das Secret explizit übergeben — fixieren Sie Ihre Umgebungsvariable, wenn Sie es von Seiten-Templates aus aufrufen. Die meisten Websites verwenden stattdessen die Schaltfläche “Vorschau-Link generieren” der Admin-Oberfläche, die über die API geht und das aufgelöste Secret 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 Standardsprache ist und prefixDefaultLocale false ist; benachbarte Schrägstriche, die durch den leeren Wert übrig bleiben, werden automatisch zusammengeführt.

Das folgende Beispiel erstellt eine locale-präfixierte 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: "", // Standardsprache, kein Präfix
});
// Gibt zurück: /hello?_preview=...

Der “Auf Website ansehen”-Link des Administrators geht über POST /_emdash/api/content/{collection}/{id}/preview-url, der die Locale des Eintrags liest, die i18n-Konfiguration der Website nachschlägt und die locale automatisch bereitstellt. Um das von diesem Endpunkt verwendete Standardmuster zu ändern, setzen Sie EMDASH_PREVIEW_PATH_PATTERN (z.B. /{locale}/{id}) — Request-Bodies haben Vorrang, wenn sie ihr eigenes pathPattern enthalten.

Token-Ablauf

Steuern 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).

Verifizieren von Tokens

Verwenden Sie verifyPreviewToken(), um eingehende Vorschau-Anfragen zu validieren:

import { verifyPreviewToken } from "emdash";

// Von einer URL (extrahiert den _preview Query-Parameter)
const result = await verifyPreviewToken({
	url: Astro.url,
	secret: import.meta.env.EMDASH_PREVIEW_SECRET,
});

// Oder mit einem Token direkt
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); // 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 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, teilt Ihnen mit, wann Entwurfsinhalte bereitgestellt 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)

Überprüfen Sie, ob eine URL ein Vorschau-Token enthält:

import { isPreviewRequest } from "emdash";

if (isPreviewRequest(Astro.url)) {
	// Vorschau-Anfrage behandeln
}

getPreviewToken(url)

Extrahieren Sie den Token-String aus einer URL:

import { getPreviewToken } from "emdash";

const token = getPreviewToken(Astro.url);
// Gibt den Token-String oder null zurück

parseContentId(contentId)

Parsen Sie eine Content-ID in Collection und ID:

import { parseContentId } from "emdash";

const { collection, id } = parseContentId("posts:my-draft-post");
// { collection: "posts", id: "my-draft-post" }

Token-Sicherheit

Vorschau-Tokens sind signiert und zeitlich begrenzt. Die CLI und die Hilfsfunktionen generieren und verifizieren sie für Sie; Sie konstruieren oder parsen sie nicht von Hand. Ein Token identifiziert einen Eintrag und funktioniert nicht mehr, nachdem es abgelaufen ist.

Vollständiges Beispiel

Die folgende Seite kombiniert Vorschau- und visuelle Bearbeitungsunterstützung in einem vollständigen Blog-Post-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("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>

Beachten Sie die {...entry.edit} und {...entry.edit.title} Spreads — diese fügen data-emdash-ref Attribute hinzu, die die visuelle Bearbeitung für authentifizierte Redakteure ermöglichen. In der Produktion erzeugen sie keine Ausgabe.

API-Referenz

getPreviewUrl(options)

Generieren Sie eine Vorschau-URL mit einem signierten Token.

Optionen:

  • collection — Collection-Slug (string)
  • id — Content-ID oder Slug (string)
  • secret — Signatur-Secret (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 zusammengeführt).

Gibt zurück: Promise<string>

verifyPreviewToken(options)

Verifizieren Sie ein Vorschau-Token.

Optionen:

  • secret — Verifizierungs-Secret (string)
  • url — URL, aus der das Token extrahiert werden soll, 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)

Generieren Sie ein Token ohne URL-Erstellung.

Optionen:

  • contentId — Content-ID im Format collection:id
  • expiresIn — Token-Gültigkeitsdauer (Standard: "1h")
  • secret — Signatur-Secret

Gibt zurück: Promise<string>