O sistema de pré-visualização do EmDash permite que os editores vejam 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 em rascunho.
Como funcionam as requisições de pré-visualização
- O administrador gera uma URL de pré-visualização para um post em rascunho
- A URL contém um parâmetro de consulta
_previewassinado, com um horário de expiração - O middleware do EmDash verifica o token automaticamente e configura o contexto da requisição
- O código do seu template chama
getEmDashEntry()normalmente — o conteúdo em rascunho é servido automaticamente
O middleware verifica o token antes de renderizar a página e registra qual entrada de conteúdo ele autoriza. O getEmDashEntry() lê esse contexto da requisição automaticamente. Por isso, o mesmo template serve o rascunho para um link de pré-visualização válido e correspondente e a entrada publicada para uma requisição comum. Um token de outra coleção ou de outra entrada nunca concede acesso ao rascunho da página solicitada.
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, de modo que o caso comum não exige nenhuma configuração.
Defina EMDASH_PREVIEW_SECRET no seu ambiente somente se precisar:
- Compartilhar o segredo entre vários processos (por exemplo, um Worker de pré-visualização separado que assina as URLs e as envia ao seu site principal para verificação)
- Fixar o segredo em um valor que você controla por motivos de conformidade ou auditoria
- Migrar para um valor conhecido ao restaurar a partir de um backup
# Optional: override the auto-generated secret
EMDASH_PREVIEW_SECRET="your-random-secret-key-here"
Se definido, o valor do ambiente prevalece sobre o valor armazenado no banco de dados.
Os templates existentes funcionam com a pré-visualização automaticamente, como na página a seguir:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Server error", { status: 500 });
}
if (!entry) {
return Astro.rewrite("/404");
}
---
{isPreview && (
<div class="preview-banner">
You are viewing the preview version of this page.
</div>
)}
<article>
<h1>{entry.data.title}</h1>
</article>
O sinalizador isPreview é true quando um token de pré-visualização válido corresponde à entrada retornada pela consulta. Tokens inválidos, expirados e não correspondentes não expõem o conteúdo em rascunho.
Gerar URLs de pré-visualização
A maioria dos editores usa View on site no administrador. Essa ação chama o endpoint de URL de pré-visualização, que resolve o segredo do site sem expô-lo ao navegador.
Use getPreviewUrl() quando o código de aplicação no servidor precisar criar um link. O helper exige o segredo de assinatura explicitamente. Leia os segredos de process.env e falhe antes de gerar um link se a variável estiver ausente:
import { getPreviewUrl } from "emdash";
const secret = process.env.EMDASH_PREVIEW_SECRET;
if (!secret) throw new Error("EMDASH_PREVIEW_SECRET is required");
const previewUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret,
expiresIn: "1h",
});
// Returns: /posts/my-draft-post?_preview=eyJjaWQ...
Quando EMDASH_PREVIEW_SECRET não está definido, o endpoint do EmDash gera e armazena no banco de dados um segredo por site. O helper independente não consegue ler esse valor armazenado, portanto configure a variável de ambiente se o código da aplicação chamar o helper.
O valor id identifica o conteúdo autorizado e também substitui {id} no caminho da URL. Pode ser um ID do banco de dados ou um slug. Use o mesmo identificador que a página de destino passa para getEmDashEntry().
Passe baseUrl para gerar uma URL absoluta:
const fullUrl = await getPreviewUrl({
collection: "posts",
id: "my-draft-post",
secret,
baseUrl: "https://example.com",
});
// Returns: 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,
pathPattern: "/blog/{id}",
});
// Returns: /blog/my-draft-post?_preview=eyJjaWQ...
Caminhos com reconhecimento de localidade
pathPattern também oferece suporte a um marcador {locale}. Passe um locale vazio quando a entrada estiver na localidade padrão e prefixDefaultLocale for false; as barras adjacentes deixadas pelo valor vazio são recolhidas automaticamente.
O exemplo a seguir constrói uma URL de pré-visualização com prefixo de localidade:
await getPreviewUrl({
collection: "posts",
id: "hello",
secret,
pathPattern: "/{locale}/{id}",
locale: "pt-br",
});
// Returns: /pt-br/hello?_preview=...
await getPreviewUrl({
collection: "posts",
id: "hello",
secret,
pathPattern: "/{locale}/{id}",
locale: "", // default locale, no prefix
});
// Returns: /hello?_preview=...
O link View on site do administrador passa por POST /_emdash/api/content/{collection}/{id}/preview-url. O endpoint usa o ID do banco de dados da entrada e fornece a localidade dela automaticamente. Defina EMDASH_PREVIEW_PATH_PATTERN quando a sua rota pública for diferente do padrão /{collection}/{id}. Por exemplo, /{locale}/posts/{id} produz /pt-br/posts/01ABC... para uma entrada em português e /posts/01ABC... para uma entrada sem prefixo na localidade padrão. A rota de destino pode passar esse ID do banco de dados para getEmDashEntry(). Um pathPattern no corpo da requisição substitui o valor do ambiente.
O endpoint não consegue substituir o slug da entrada separadamente do ID do banco de dados. Se uma URL de pré-visualização precisar usar o slug, gere-a no código de aplicação no servidor com getPreviewUrl() e passe o slug como id.
O marcador {locale} recebe o código de localidade configurado. Ele não aplica os mapeamentos personalizados de path de localidade do Astro. Se a rota pública usar um segmento diferente, chame o helper com esse segmento ou forneça uma rota que aceite o código de localidade usado pelo endpoint do administrador.
Definir a expiração do token
Controle por quanto tempo os links de pré-visualização permanecem válidos:
const preview = {
collection: "posts",
id: "my-draft-post",
secret,
};
// Valid for 1 hour (default)
await getPreviewUrl(preview);
// Valid for 30 minutes
await getPreviewUrl({ ...preview, expiresIn: "30m" });
// Valid for 1 day
await getPreviewUrl({ ...preview, expiresIn: "1d" });
// Valid for 2 weeks
await getPreviewUrl({ ...preview, expiresIn: "2w" });
// Valid for 3600 seconds
await getPreviewUrl({ ...preview, expiresIn: 3600 });
Unidades compatíveis: s (segundos), m (minutos), h (horas), d (dias), w (semanas).
Verificar tokens em um fluxo personalizado
As páginas Astro normais não chamam verifyPreviewToken(); o middleware do EmDash já verifica _preview. Use este helper somente quando o código fora desse middleware precisar validar um token:
import { verifyPreviewToken } from "emdash";
// From a URL (extracts _preview query parameter)
const fromUrl = await verifyPreviewToken({
url: Astro.url,
secret,
});
// Or with a token directly
const fromToken = await verifyPreviewToken({
token: Astro.url.searchParams.get("_preview"),
secret,
});
O resultado indica se o token é válido:
if (fromUrl.valid) {
// Token is valid
console.log(fromUrl.payload.cid); // "posts:my-draft-post"
console.log(fromUrl.payload.exp); // Expiry timestamp
console.log(fromUrl.payload.iat); // Issued-at timestamp
} else {
// Token is invalid
console.log(fromUrl.error);
// "none" - no token present
// "malformed" - token structure is invalid
// "invalid" - signature verification failed
// "expired" - token has expired
}
Exibir um indicador de pré-visualização
Use o sinalizador isPreview retornado por getEmDashEntry para mostrar que a página veio de uma requisição de pré-visualização:
{isPreview && (
<div class="preview-banner" role="alert">
<strong>Preview</strong> — You are viewing the preview version of this page.
<a href={Astro.url.pathname}>Exit preview</a>
</div>
)}
Inspecionar URLs de pré-visualização
isPreviewRequest(url)
Verifica se uma URL contém um parâmetro _preview. Isso não verifica o token:
import { isPreviewRequest } from "emdash";
if (isPreviewRequest(Astro.url)) {
// Handle preview request
}
getPreviewToken(url)
Extrai a string do token de uma URL:
import { getPreviewToken } from "emdash";
const token = getPreviewToken(Astro.url);
// Returns the token string or 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 dos tokens
Os tokens de pré-visualização são assinados e têm tempo limitado. O endpoint do administrador e as funções helper os geram e os verificam para você; você não os constrói nem os analisa manualmente. Um token identifica uma entrada e deixa de funcionar quando expira.
Exemplo completo
A página a seguir combina a pré-visualização e o suporte à edição visual em um template completo de post de blog:
---
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("Server error", { status: 500 });
}
if (!entry) {
return Astro.rewrite("/404");
}
---
<BaseLayout title={entry.data.title}>
{isPreview && (
<div class="preview-banner" role="alert">
<strong>Preview</strong> — You are viewing the preview version of this page.
</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">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 nenhuma 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 os marcadores{collection},{id}e{locale}(padrão:"/{collection}/{id}")locale— Valor que substitui{locale}. Uma string vazia omite o segmento de localidade (as 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 da qual extrair o token, OUtoken— A 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 de conteúdo no formatocollection:idexpiresIn— Duração de validade do token (padrão:"1h")secret— Segredo de assinatura
Retorna: Promise<string>