Modo de Pré-visualização

Nesta página

O sistema de pré-visualização do EmDash permite que editores visualizem conteúdo não publicado através de URLs seguros com limite de tempo. 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.

Como Funciona

  1. O administrador gera uma URL de pré-visualização para uma postagem de 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. O middleware verifica o token e as funções de consulta o leem através de AsyncLocalStorage, então o mesmo código de template serve conteúdo de rascunho durante uma pré-visualização e conteúdo publicado de outra forma.

Configurando a Pré-visualização

A pré-visualização funciona assim que o EmDash é instalado. No primeiro uso, o EmDash gera um segredo de pré-visualização por site e o armazena no banco de dados, então o caso comum não precisa de configuração.

Defina EMDASH_PREVIEW_SECRET em seu ambiente apenas se você precisar:

  • Compartilhar o segredo entre vários processos (por exemplo, um Worker de pré-visualização separado que assina URLs e os envia para seu site principal para verificação)
  • Fixar o segredo a um valor que você controla por razões de conformidade/auditoria
  • Migrar para um valor conhecido ao restaurar de um backup
# Opcional: sobrescreve o segredo gerado automaticamente
EMDASH_PREVIEW_SECRET="your-random-secret-key-here"

Se definido, o valor do ambiente prevalece sobre o valor armazenado no BD.

Templates existentes funcionam com pré-visualização automaticamente, como na página seguinte:

---
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">
    Você está vendo uma pré-visualização. Este conteúdo não está publicado.
  </div>
)}

<article>
  <h1>{entry.data.title}</h1>
</article>

A flag isPreview é true quando o conteúdo de rascunho está sendo servido via um token de pré-visualização válido.

Gerando URLs de Pré-visualização

Use getPreviewUrl() para criar links de pré-visualização. A função recebe o segredo como um argumento explícito:

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

Quando EMDASH_PREVIEW_SECRET não está definido, o EmDash gera e armazena automaticamente um segredo por site no banco de dados para verificação de token. O helper de template getPreviewUrl() ainda requer que você passe o segredo explicitamente — fixe sua variável de ambiente se você o chamar de templates de página. A maioria dos sites usa o botão “Gerar link de pré-visualização” da interface de administração, que passa pela API e usa o segredo resolvido automaticamente.

Passe baseUrl para gerar uma URL absoluta:

const fullUrl = await getPreviewUrl({
	collection: "posts",
	id: "my-draft-post",
	secret: import.meta.env.EMDASH_PREVIEW_SECRET,
	baseUrl: "https://example.com",
});
// Retorna: https://example.com/posts/my-draft-post?_preview=eyJjaWQ...

Passe pathPattern para gerar uma URL com um caminho personalizado:

const blogUrl = await getPreviewUrl({
	collection: "posts",
	id: "my-draft-post",
	secret: import.meta.env.EMDASH_PREVIEW_SECRET,
	pathPattern: "/blog/{id}",
});
// Retorna: /blog/my-draft-post?_preview=eyJjaWQ...

Caminhos conscientes de locale

pathPattern também suporta um marcador {locale}. Passe um locale vazio quando a entrada estiver no locale padrão e prefixDefaultLocale for false; barras adjacentes deixadas pelo valor vazio são recolhidas automaticamente.

O exemplo seguinte constrói uma URL de pré-visualização com prefixo de locale:

await getPreviewUrl({
	collection: "posts",
	id: "hello",
	secret,
	pathPattern: "/{locale}/{id}",
	locale: "pt-br",
});
// Retorna: /pt-br/hello?_preview=...

await getPreviewUrl({
	collection: "posts",
	id: "hello",
	secret,
	pathPattern: "/{locale}/{id}",
	locale: "", // locale padrão, sem prefixo
});
// Retorna: /hello?_preview=...

O link “Ver no site” do administrador passa por POST /_emdash/api/content/{collection}/{id}/preview-url, que lê o locale da entrada, busca a configuração i18n do site e fornece o locale automaticamente. Para alterar o padrão usado por esse endpoint, defina EMDASH_PREVIEW_PATH_PATTERN (por exemplo /{locale}/{id}) — os corpos de requisição ainda ganham quando incluem seu próprio pathPattern.

Expiração de Token

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

// Válido por 1 hora (padrão)
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).

Verificando Tokens

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 é válido
	console.log(result.payload.cid); // "posts:my-draft-post"
	console.log(result.payload.exp); // Timestamp de expiração
	console.log(result.payload.iat); // Timestamp de emissão
} else {
	// Token é inválido
	console.log(result.error);
	// "none" - nenhum token presente
	// "malformed" - estrutura do token é inválida
	// "invalid" - verificação de assinatura falhou
	// "expired" - token expirou
}

Indicador de Pré-visualização

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

{isPreview && (
  <div class="preview-banner" role="alert">
    <strong>Pré-visualização</strong> — Você está vendo conteúdo não publicado.
    <a href={Astro.url.pathname}>Sair da pré-visualização</a>
  </div>
)}

Funções Auxiliares

isPreviewRequest(url)

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

import { isPreviewRequest } from "emdash";

if (isPreviewRequest(Astro.url)) {
	// Tratar requisição de pré-visualização
}

getPreviewToken(url)

Extrai a string do token de uma URL:

import { getPreviewToken } from "emdash";

const token = getPreviewToken(Astro.url);
// Retorna a string do token ou null

parseContentId(contentId)

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" }

Segurança de Token

Tokens de pré-visualização são assinados e têm limite de tempo. A CLI e as funções auxiliares os geram e verificam para você; você não os constrói ou analisa manualmente. Um token identifica uma entrada e para de funcionar após expirar.

Exemplo Completo

A página seguinte combina suporte de pré-visualização e edição visual em um template completo de postagem de blog:

---
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 trata a verificação de 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>Pré-visualização</strong> — Este conteúdo não está publicado.
    </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">Rascunho</span>
      )}
    </header>

    <div class="content" {...entry.edit.content}>
      <PortableText value={entry.data.content} />
    </div>
  </article>
</BaseLayout>

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

Referência da API

getPreviewUrl(options)

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

Opções:

  • collection — Slug da coleção (string)
  • id — ID do conteúdo ou slug (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 marcadores {collection}, {id} e {locale} (padrão: "/{collection}/{id}")
  • locale — Valor substituído por {locale}. String vazia omite o segmento de locale (barras são recolhidas).

Retorna: Promise<string>

verifyPreviewToken(options)

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" };

generatePreviewToken(options)

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>