As páginas do EmDash leem o conteúdo no momento da requisição com getEmDashCollection() e getEmDashEntry().
A primeira função retorna uma lista, e a segunda retorna uma entrada pelo seu slug ou pelo seu ID de conteúdo. Ambas
retornam erros como dados, para que a página decida como responder.
Os templates incluídos usam a saída de servidor do Astro. Um visitante, portanto, recebe a revisão publicada mais recente na próxima renderização depois que um editor a publica. Um rascunho salvo permanece privado até ser publicado ou solicitado por meio de uma URL de pré-visualização válida.
Consultar uma coleção
A página a seguir carrega os sete posts publicados mais recentemente. As consultas públicas de coleção usam por padrão
status: "published", então o filtro não precisa repeti-lo.
---
import { getEmDashCollection } from "emdash";
const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
orderBy: { published_at: "desc" },
limit: 7,
});
if (error) {
console.error("Failed to load posts:", error);
return new Response("Unable to load posts", { status: 500 });
}
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
<h1>Posts</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/posts/${post.id}`}>{post.data.title}</a>
</li>
))}
</ul>
A ordenação e o limite acontecem no banco de dados antes de o EmDash montar as entradas. Isso evita carregar uma coleção inteira e depois ordená-la ou fatiá-la na página.
O resultado contém:
entries, que é um array vazio quando não há correspondências ou a consulta falhou;error, que é definido quando a consulta falha, mas não para um resultado vazio;cacheHint, que carrega as tags e o horário da última modificação para o cache do Astro;nextCursor, que é definido quando uma página limitada por cursor tem mais entradas; ehasMore, que informa se uma página limitada por cursor ou offset tem outra página.
Identificadores de entrada
Cada resultado tem dois identificadores com funções diferentes:
entry.idé o identificador voltado para a URL produzido pelo carregador de conteúdo. Normalmente é o slug. Quando a internacionalização antepõe um locale, o prefixo é incluído. Use esse valor ao construir um link a partir de um resultado de coleção.entry.data.idé o ID de conteúdo estável armazenado no banco de dados. Ele não muda quando um editor altera o slug. Use-o quando uma API, um helper de taxonomia, o contexto da página ou uma relação esperar um ID de conteúdo.
getEmDashEntry() aceita o slug ou o ID de conteúdo estável. Uma busca por slug fica restrita a um
locale quando a internacionalização está habilitada; um ID de conteúdo identifica a linha diretamente.
Filtrar uma coleção
Passe os filtros no segundo argumento. A consulta a seguir retorna os posts publicados na categoria
news cujo campo series é engineering:
const { entries } = await getEmDashCollection("posts", {
where: {
category: "news",
series: "engineering",
},
});
As chaves que nomeiam uma taxonomia correspondem aos slugs dos termos atribuídos. As outras chaves correspondem a campos da coleção. Várias
chaves são combinadas com lógica AND. Um array em uma chave corresponde a qualquer um dos valores listados, de modo que
{ category: ["news", "updates"] } corresponde a qualquer uma das categorias.
Use locale para solicitar um idioma explicitamente:
const { entries: frenchPosts } = await getEmDashCollection("posts", {
locale: "fr",
orderBy: { published_at: "desc" },
});
Se locale for omitido, o EmDash usa o locale da requisição e depois o locale padrão configurado. Consulte
Internacionalização (i18n) para ver as regras de fallback
e as rotas traduzidas.
status aceita "published", "draft" ou "archived". Não solicite rascunhos a partir de uma rota
pública. Use o fluxo de pré-visualização quando um visitante precisar de acesso temporário a uma entrada não publicada.
Ordenar resultados no banco de dados
orderBy mapeia nomes de campo para "asc" ou "desc". Use os nomes de campo armazenados, não os nomes em camelCase
retornados em entry.data:
const { entries } = await getEmDashCollection("posts", {
orderBy: {
published_at: "desc",
title: "asc",
},
});
As colunas do sistema usam seus nomes no banco de dados, como created_at, updated_at e published_at.
Os campos personalizados usam o slug da coleção, como title ou priority. Os dados retornados mapeiam as datas do sistema
para createdAt, updatedAt e publishedAt, mas esses nomes de propriedade em camelCase não são
campos orderBy válidos.
O EmDash usa o primeiro campo orderBy válido como chave de paginação e o ID de conteúdo como
critério de desempate estável. Sem orderBy, as coleções são ordenadas por padrão por created_at em ordem decrescente. Marque os campos escalares
personalizados como indexados quando o site ordenar ou filtrar por eles regularmente; o índice evita uma varredura completa da tabela
à medida que a coleção cresce.
Paginar uma coleção
Use um cursor para um feed contínuo ou um offset para páginas numeradas. São modelos de paginação distintos e não podem ser combinados em uma mesma consulta tipada.
Paginação por cursor
A paginação por cursor continua depois da última entrada retornada pela consulta anterior. Mantenha a ordenação
inalterada entre as requisições e devolva nextCursor sem inspecioná-lo ou alterá-lo.
A rota a seguir renderiza um link Older posts quando existe outra página:
---
import { getEmDashCollection } from "emdash";
const cursor = Astro.url.searchParams.get("cursor") ?? undefined;
const { entries: posts, nextCursor, error } = await getEmDashCollection("posts", {
limit: 10,
cursor,
orderBy: { published_at: "desc" },
});
if (error) return new Response("Unable to load posts", { status: 500 });
---
<ul>
{posts.map((post) => <li><a href={`/posts/${post.id}`}>{post.data.title}</a></li>)}
</ul>
{nextCursor && (
<a href={`/posts?cursor=${encodeURIComponent(nextCursor)}`}>Older posts</a>
)}
nextCursor está ausente na última página. A paginação por cursor não calcula o total de páginas nem
fornece um cursor de página anterior; preserve as URLs anteriores no histórico do navegador se a interface precisar
de navegação para trás.
Paginação por offset
A paginação por offset é adequada para rotas como /posts/page/3. Converta o número da página em um offset e use
hasMore para o link da próxima página:
---
import { getEmDashCollection } from "emdash";
const parsedPage = Number(Astro.params.page ?? "1");
if (!Number.isInteger(parsedPage) || parsedPage < 1) {
return Astro.rewrite("/404");
}
const perPage = 10;
const { entries: posts, hasMore, error } = await getEmDashCollection("posts", {
limit: perPage,
offset: (parsedPage - 1) * perPage,
orderBy: { published_at: "desc" },
});
if (error) return new Response("Unable to load posts", { status: 500 });
---
<ul>
{posts.map((post) => <li><a href={`/posts/${post.id}`}>{post.data.title}</a></li>)}
</ul>
<nav aria-label="Post pages">
{parsedPage > 1 && <a href={`/posts/page/${parsedPage - 1}`}>Newer posts</a>}
{hasMore && <a href={`/posts/page/${parsedPage + 1}`}>Older posts</a>}
</nav>
Um offset deve ser um inteiro não negativo. A página 1 usa um offset de zero, o que significa “começar na primeira entrada”. A paginação por offset é fácil de endereçar pelo número da página, mas entradas adicionadas entre requisições podem deslocar as páginas seguintes. Use cursores quando esse deslocamento for confuso.
Consultar e renderizar uma entrada
A rota de runtime a seguir lê um post pelo slug, renderiza a imagem de destaque e o corpo em Portable Text, e distingue uma falha de consulta de uma entrada inexistente:
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Image, PortableText } from "emdash/ui";
const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.rewrite("/404");
const { entry: post, error, isPreview, cacheHint } = await getEmDashEntry("posts", slug);
if (error) {
console.error("Failed to load post:", error);
return new Response("Unable to load post", { status: 500 });
}
if (!post) return Astro.rewrite("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
{isPreview && <p>This is an unpublished preview.</p>}
<article>
{post.data.featured_image && <Image image={post.data.featured_image} priority />}
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
PortableText fornece renderizadores para os blocos padrão do EmDash, incluindo imagens, galerias, código,
tabelas, blocos HTML e blocos iframe. Passe componentes personalizados quando um site adicionar seus próprios blocos de Portable Text.
Um bloco HTML com isolated: true renderiza seu html, css e js em um iframe em sandbox que ajusta o próprio
tamanho ao conteúdo. Os demais blocos HTML renderizam o html, sanitizado, na própria página. Se você substituir o
renderizador htmlBlock, passe os blocos isolados para HtmlBlock de emdash/ui ou renderize-os em um iframe cujo
sandbox omita allow-same-origin. Sanitize você mesmo o HTML inline e permita apenas os hosts de iframe em que o site
pretende confiar.
Um bloco iframe é renderizado por meio de Iframe de emdash/ui como um iframe responsivo e de carregamento
adiado, com um sandbox e a política de referenciador strict-origin-when-cross-origin. As páginas no host do seu
site perdem o acesso de mesma origem, e apenas origens https são renderizadas.
Com a política de segurança de conteúdo do Astro
ativada, o navegador bloqueia os estilos e scripts dos blocos HTML isolados, que então são renderizados sem o CSS,
o JavaScript ou a altura automática. Os blocos iframe também perdem o tamanho personalizado, e o frame-src da
política deve permitir os hosts que eles incorporam.
PortableText mostra as tabelas como espaços reservados somente leitura no modo de edição. Para localizar o rótulo inicial, passe tablePlaceholder={translatedLabel}. O padrão é "Table (edit in admin)" e não afeta o conteúdo das tabelas publicadas.
Ler campos de referência
Um campo reference vincula uma entrada a entradas de outra coleção, e Relações explica como definir um. Seu valor não faz parte de data. Passe uma opção references para getEmDashEntry nomeando os campos que a página renderiza, com o slug do campo como chave, e cada um volta como uma página de entradas:
---
import { getEmDashEntry } from "emdash";
const { entry: post } = await getEmDashEntry("posts", Astro.params.slug, {
references: { author: true, related_posts: { limit: 6 } },
});
if (!post) return Astro.rewrite("/404");
const author = post.references?.author.entries[0];
---
<article>
<h1>{post.data.title}</h1>
{author && <p>By {author.data.name}</p>}
<ul>
{post.references?.related_posts.entries.map((related) => (
<li><a href={`/posts/${related.data.slug}`}>{related.data.title}</a></li>
))}
</ul>
</article>
true solicita a primeira página com o limite padrão de 50 entradas. Use { limit, cursor } para um campo que contenha mais, até 100 por página.
Cada entrada referenciada é um ContentEntry com o mesmo formato de uma carregada diretamente: um id, um objeto data com as datas como objetos Date e os valores de mídia resolvidos, e um proxy edit restrito à entrada referenciada, de modo que clicar em um cartão na edição visual abre a entrada a que o cartão se refere. Bylines e termos de taxonomia são a exceção: o EmDash não os monta nas entradas referenciadas, então leia data.bylines e data.terms a partir da própria entrada.
As entradas chegam na ordem em que o editor as organizou quando o campo está no lado pai da sua relação. Um campo no lado filho lista as entradas que apontam para ele, as quais não têm uma ordem própria.
A opção é opt-in nos dois sentidos. Uma chamada que não passa references não executa consultas extras, e um campo deixado de fora da seleção não é lido. Uma chamada que seleciona campos custa uma consulta de links por campo, mais uma consulta de entradas por coleção de destino distinta, independentemente de quantas entradas cada campo contenha.
Paginar um campo de referência
getEmDashReferences busca a próxima página de um único campo, usando o cursor que a página anterior retornou:
import { getEmDashReferences } from "emdash";
const { entries, nextCursor } = await getEmDashReferences("posts", post.id, "related_posts", {
cursor,
limit: 20,
});
Ele lê a visibilidade dos rascunhos do mesmo contexto de requisição que getEmDashEntry, de modo que uma navegação iniciada na pré-visualização continua vendo a seleção pendente. Um id de página carrega seu locale quando o i18n antepõe um (fr/about), e esse é o id a ser devolvido.
Em uma rota que coloca sua saída em cache, mescle o cacheHint retornado pela página com o da própria rota, para que uma escrita em uma das entradas que ele nomeou expire a página.
Campos de referência na pré-visualização
Uma renderização pública vê as entradas publicadas e a seleção publicada. Uma pré-visualização da entrada, ou um editor na edição visual, vê as entradas não publicadas e a seleção preparada no rascunho da entrada, de modo que um link de pré-visualização mostra as referências que a página terá depois de publicada.
Aplicar recursos de SEO e edição
Para uma coleção com suporte a SEO, getSeoMeta() resolve o título SEO, a descrição,
a imagem, a URL canônica e a escolha de no-index do editor, com fallbacks vindos da entrada. O template de blog atual
passa esse resultado para o seu layout base:
---
import { getSeoMeta } from "emdash";
const seo = getSeoMeta(post, {
siteTitle: "My Blog",
siteUrl: Astro.url.origin,
path: Astro.url.pathname,
});
---
<Base
title={seo.title}
pageTitle={seo.ogTitle}
description={seo.description}
image={seo.ogImage}
canonical={seo.canonical}
robots={seo.robots}
>
<!-- Post content -->
</Base>
Um layout que inclua <EmDashHead> pode aplicar os mesmos campos de SEO e as contribuições de plugins às
páginas de conteúdo renderizadas no servidor. Meta tags escritas à mão que leem apenas data.title ou data.excerpt
não aplicam a URL canônica nem a configuração de no-index do editor.
As URLs de pré-visualização não precisam de uma consulta separada. O middleware valida o token _preview, e
getEmDashEntry() retorna o rascunho correspondente com isPreview: true. O
guia de pré-visualização e edição visual explica a geração de URLs e as anotações entry.edit
usadas na edição inline.
Gerar tipos TypeScript
O servidor de desenvolvimento gera emdash-env.d.ts a partir do esquema ativo. Mantenha esse arquivo incluído na
configuração do TypeScript do projeto para que um nome de coleção como "posts" selecione automaticamente o
tipo de dados Post gerado.
Para uma instância remota do EmDash, a CLI pode buscar o esquema e gravar os tipos em .emdash/types.ts:
npx emdash types --url https://cms.example.com
O comando aceita um token de API ou cabeçalhos de autenticação personalizados. Consulte Gerar tipos para essas opções.
Cada coleção que tem um campo de referência vinculado a uma relação ganha uma segunda interface,
{Collection}References, registrada sob o mesmo slug. getEmDashEntry restringe seu resultado aos
campos que a opção references nomeou, e as entradas de cada página carregam a interface da
coleção de destino:
const { entry: post } = await getEmDashEntry("posts", "my-post", {
references: { author: true },
});
// post?.references?.author.entries[0] carries the Author interface
// post?.references?.related_posts is a type error: it was not selected
Renderização em runtime e cache
Os templates de blog para Node.js e Cloudflare definem output: "server" em astro.config.mjs. Suas consultas
de conteúdo são executadas a cada renderização no servidor, então uma revisão recém-publicada fica elegível para a próxima
requisição. Se você pré-renderizar uma rota deliberadamente, o HTML dela contém o conteúdo disponível no momento
do build e só muda depois de outro build.
Quando o cache do Astro está habilitado, passe o cacheHint da consulta para Astro.cache.set(). O EmDash associa
a resposta às tags de coleção e de entrada para que a publicação possa invalidar as páginas em cache afetadas.
Evite substituir essa integração por uma duração fixa e longa de Cache-Control, a menos que atualizações atrasadas
sejam uma decisão de produto explícita.
Para as assinaturas exatas e os filtros menos comuns, consulte a Referência da API JavaScript. Para construir um exemplo funcional em torno dessas consultas, continue com Criar um Blog.