Pular para o conteúdo

Modo de Visualização

O sistema de pré-visualização do EmDash permite que editores visualizem conteúdo não publicado por meio de URLs seguras e com tempo limitado. Os links de pré-visualização usam tokens assinados com HMAC-SHA256 que você pode compartilhar com revisores sem expor todo o seu conteúdo de rascunho.

  1. O administrador gera uma URL de pré-visualização para um post em rascunho
  2. A URL contém um parâmetro de consulta _preview assinado com um tempo de expiração
  3. O middleware do EmDash verifica automaticamente o token e configura o contexto da requisição
  4. Seu código de template chama getEmDashEntry() normalmente — o conteúdo de rascunho é servido automaticamente

A pré-visualização é implícita. Seu código de template não precisa lidar com tokens ou passar opções de pré-visualização — o middleware e as funções de consulta tratam de tudo via AsyncLocalStorage.

Adicione um segredo de pré-visualização ao seu ambiente:

.env
EMDASH_PREVIEW_SECRET="your-random-secret-key-here"

Gere uma string aleatória segura. Este segredo assina e verifica os tokens de pré-visualização.

É isso. Seus templates existentes funcionam com pré-visualização automaticamente:

astro title="src/pages/posts/[...slug].astro"
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
// Nenhum tratamento especial de pré-visualização necessário — o middleware
// detecta tokens _preview e serve conteúdo de rascunho automaticamente
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">
You are viewing a preview. This content is not published.
</div>
)}
<article>
<h1>{entry.data.title}</h1>
</article>

O sinalizador isPreview é true quando o conteúdo de rascunho está sendo servido por meio de um token de pré-visualização válido.

Use getPreviewUrl() para criar links de pré-visualização:

import { getPreviewUrl } from "emdash";
const previewUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
expiresIn: "1h",
});
// Retorna: /posts/my-draft-post?_preview=eyJjaWQ...

Com uma URL base para links absolutos:

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

Com um padrão de caminho personalizado:

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

Controle por quanto tempo os links de pré-visualização permanecem válidos:

// Valid for 1 hour (default)
await getPreviewUrl({ ..., expiresIn: "1h" });
// Válido por 30 minutos
await getPreviewUrl({ ..., expiresIn: "30m" });
// Válido por 1 dia
await getPreviewUrl({ ..., expiresIn: "1d" });
// Válido por 2 semanas
await getPreviewUrl({ ..., expiresIn: "2w" });
// Válido por 3600 segundos
await getPreviewUrl({ ..., expiresIn: 3600 });

Unidades suportadas: s (segundos), m (minutos), h (horas), d (dias), w (semanas).

Use verifyPreviewToken() para validar requisições de pré-visualização recebidas:

import { verifyPreviewToken } from "emdash";
// De uma URL (extrai o parâmetro de consulta _preview)
const result = await verifyPreviewToken({
url: Astro.url,
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
});
// Ou com um token diretamente
const result = await verifyPreviewToken({
token: someTokenString,
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
});

O resultado indica se o token é válido:

if (result.valid) {
// Token is valid
console.log(result.payload.cid); // "posts:my-draft-post"
console.log(result.payload.exp); // Expiry timestamp
console.log(result.payload.iat); // Issued-at timestamp
} else {
// Token is invalid
console.log(result.error);
// "none" - no token present
// "malformed" - token structure is invalid
// "invalid" - signature verification failed
// "expired" - token has expired
}

Você pode mostrar um indicador visual quando o conteúdo está sendo pré-visualizado. O sinalizador isPreview retornado por getEmDashEntry informa quando o conteúdo de rascunho está sendo servido:

{isPreview && (
<div class="preview-banner" role="alert">
<strong>Preview</strong>
— You are viewing unpublished content.
<a href={Astro.url.pathname}>Exit preview</a>
</div>
)}

Verifica se uma URL contém um token de pré-visualização:

import { isPreviewRequest } from "emdash";
if (isPreviewRequest(Astro.url)) {
// Handle preview request
}

Extrai a string do token de uma URL:

import { getPreviewToken } from "emdash";
const token = getPreviewToken(Astro.url);
// Retorna a string do token ou null

Analisa um ID de conteúdo em coleção e ID:

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

Os tokens de pré-visualização usam um formato compacto: base64url(payload).base64url(signature)

O payload contém:

  • cid — ID do conteúdo no formato collection:id
  • exp — Timestamp de expiração (segundos desde a época)
  • iat — Timestamp de emissão (segundos desde a época)

Os tokens são assinados com HMAC-SHA256 usando seu segredo de pré-visualização.

Uma página completa de postagem de blog com suporte a pré-visualização e edição visual:

astro title="src/pages/posts/[...slug].astro"
---
import { getEmDashEntry } from "emdash";
import BaseLayout from "../../../layouts/Base.astro";
import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
// A pré-visualização é automática — o middleware lida com a verificação do token
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>Preview</strong>
— This content is not published.
</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">Draft</span>
)}
</header>
<div class="content" {...entry.edit.content}>
<PortableText value={entry.data.content} />
</div>
</article>
</BaseLayout>

Observe os spreads {...entry.edit} e {...entry.edit.title} — eles adicionam atributos data-emdash-ref que habilitam a edição visual para editores autenticados. Em produção, eles não produzem saída.

Gera uma URL de pré-visualização com um token assinado.

Opções:

  • collection — Slug da coleção (string)
  • id — ID ou slug do conteúdo (string)
  • secret — Segredo de assinatura (string)
  • expiresIn — Duração de validade do token (padrão: "1h")
  • baseUrl — URL base opcional para links absolutos
  • pathPattern — Padrão de URL com placeholders {collection} e {id} (padrão: "/{collection}/{id}")

Retorna: Promise<string>

Verifica um token de pré-visualização.

Opções:

  • secret — Segredo de verificação (string)
  • url — URL para extrair o token, OU
  • token — String do token diretamente

Retorna: Promise<VerifyPreviewTokenResult>

type VerifyPreviewTokenResult =
| { valid: true; payload: PreviewTokenPayload }
| { valid: false; error: "invalid" | "expired" | "malformed" | "none" };

Gera um token sem construir uma URL.

Opções:

  • contentId — ID do conteúdo no formato collection:id
  • expiresIn — Duração de validade do token (padrão: "1h")
  • secret — Segredo de assinatura

Retorna: Promise<string>