O sistema de pré-visualização do EmDash permite que editores visualizem conteúdo não publicado através de URLs seguros e com tempo limitado. Links de pré-visualização usam tokens assinados com HMAC-SHA256 que você pode compartilhar com revisores sem expor todo o seu conteúdo rascunho.
Como funciona
- O admin gera uma URL de pré-visualização para um post 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 — conteúdo rascunho é servido automaticamente
A pré-visualização é implícita. O middleware verifica o token e as funções de consulta o leem através do AsyncLocalStorage, de modo que o mesmo código de template serve conteúdo rascunho durante uma pré-visualização e conteúdo publicado caso contrário.
Configurar 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 no seu ambiente apenas se precisar:
- Compartilhar o segredo entre múltiplos processos (ex.: um Worker de pré-visualização separado que assina URLs e as envia ao seu site principal para verificação)
- Fixar o segredo a um valor que você controle por razões de conformidade/auditoria
- Migrar para um valor conhecido ao restaurar de um backup
# Opcional: sobrescrever 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 automaticamente com a pré-visualização:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Erro do servidor", { status: 500 });
}
if (!entry) {
return Astro.redirect("/404");
}
---
{isPreview && (
<div class="preview-banner">
Você está visualizando uma pré-visualização. Este conteúdo não está publicado.
</div>
)}
<article>
<h1>{entry.data.title}</h1>
</article>
A flag isPreview é true quando conteúdo rascunho está sendo servido via um token de pré-visualização válido.
Gerar URLs de pré-visualização
Use getPreviewUrl() para criar links de pré-visualização. A função recebe o segredo como 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",
});
Quando EMDASH_PREVIEW_SECRET não está definido, o EmDash auto-gera e armazena um segredo por site no banco de dados para verificação de tokens. O helper de template getPreviewUrl() ainda requer que você passe o segredo explicitamente. A maioria dos sites usa o botão “Gerar link de pré-visualização” da UI admin, 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",
});
Passe pathPattern para gerar uma URL com caminho personalizado:
const blogUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
pathPattern: "/blog/{id}",
});
Caminhos com reconhecimento de locale
pathPattern também suporta um placeholder {locale}. Passe um locale vazio quando a entrada está no locale padrão e prefixDefaultLocale é false; barras adjacentes deixadas pelo valor vazio são automaticamente colapsadas.
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: "",
});
// Retorna: /hello?_preview=...
O link “Ver no site” do admin 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 (ex.: /{locale}/{id}).
Expiração do token
Controle por quanto tempo os links de pré-visualização permanecem válidos:
await getPreviewUrl({ ..., expiresIn: "1h" }); // 1 hora (padrão)
await getPreviewUrl({ ..., expiresIn: "30m" }); // 30 minutos
await getPreviewUrl({ ..., expiresIn: "1d" }); // 1 dia
await getPreviewUrl({ ..., expiresIn: "2w" }); // 2 semanas
await getPreviewUrl({ ..., expiresIn: 3600 }); // 3600 segundos
Unidades suportadas: s (segundos), m (minutos), h (horas), d (dias), w (semanas).
Verificar tokens
Use verifyPreviewToken() para validar requisições de pré-visualização recebidas:
import { verifyPreviewToken } from "emdash";
const result = await verifyPreviewToken({
url: Astro.url,
secret: import.meta.env.EMDASH_PREVIEW_SECRET,
});
if (result.valid) {
console.log(result.payload.cid);
console.log(result.payload.exp);
console.log(result.payload.iat);
} else {
console.log(result.error);
// "none" | "malformed" | "invalid" | "expired"
}
Indicador de pré-visualização
{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)
import { isPreviewRequest } from "emdash";
if (isPreviewRequest(Astro.url)) { /* ... */ }
getPreviewToken(url)
import { getPreviewToken } from "emdash";
const token = getPreviewToken(Astro.url);
parseContentId(contentId)
import { parseContentId } from "emdash";
const { collection, id } = parseContentId("posts:my-draft-post");
Segurança dos tokens
Os tokens de pré-visualização são assinados e com tempo limitado. A CLI e as funções auxiliares os geram e verificam para você. Não os construa ou parse manualmente. Um token identifica uma entrada e para de funcionar após expirar.
Exemplo completo
---
import { getEmDashEntry } from "emdash";
import BaseLayout from "../../layouts/Base.astro";
import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) return new Response("Erro do servidor", { 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.status === "draft" && (
<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} — eles adicionam atributos data-emdash-ref que habilitam edição visual para editores autenticados. Em produção, não produzem saída.
Referência da API
getPreviewUrl(options)
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 placeholders{collection},{id}e{locale}(padrão:"/{collection}/{id}")locale— Valor substituído por{locale}. String vazio omite o segmento de locale.
Retorna: Promise<string>
verifyPreviewToken(options)
secret— Segredo de verificação (string)url— URL da qual 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)
contentId— ID do conteúdo no formatocollection:idexpiresIn— Duração de validade do token (padrão:"1h")secret— Segredo de assinatura
Retorna: Promise<string>