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
- O administrador gera uma URL de pré-visualização para uma postagem de rascunho
- A URL contém um parâmetro de consulta
_previewassinado com um tempo de expiração - O middleware do EmDash verifica automaticamente o token e configura o contexto da requisição
- 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 absolutospathPattern— 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, OUtoken— 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 formatocollection:idexpiresIn— Duração de validade do token (padrão:"1h")secret— Segredo de assinatura
Retorna: Promise<string>