Consultar conteúdo

Nesta página

As páginas EmDash leem conteúdo no momento da solicitação com getEmDashCollection() e getEmDashEntry(). A primeira função retorna uma lista, e a segunda retorna uma entrada pelo slug ou 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. Portanto, um visitante recebe a revisão publicada mais recente no próximo render depois que um editor a publica. Um rascunho salvo permanece privado até ser publicado ou solicitado por uma URL de pré-visualização válida.

Consultar uma coleção

A página a seguir carrega as sete postagens publicadas mais recentemente. Consultas públicas de coleção usam por padrão status: "published", então o filtro não precisa repetir isso.

---
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 que o EmDash hidrate as entradas. Isso evita carregar uma coleção inteira e 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 para uma consulta com falha, mas não para um resultado vazio;
  • cacheHint, que carrega as tags e a hora de última modificação para o cache do Astro;
  • nextCursor, que é definido quando uma página de cursor limitada tem mais entradas; e
  • hasMore, que indica se uma página de cursor ou offset limitada tem outra página.

Identificadores de entrada

Cada resultado tem dois identificadores com funções diferentes:

  • entry.id é o identificador voltado à URL produzido pelo carregador de conteúdo. Normalmente é o slug. Quando a internacionalização prefixa um locale, o prefixo é incluído. Use este valor ao criar 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. Não muda quando um editor altera o slug. Use-o quando uma API, um helper de taxonomia, um contexto de página ou uma relação espera um ID de conteúdo.

getEmDashEntry() aceita o slug ou o ID de conteúdo estável. Uma busca por slug é limitada a um locale quando a internacionalização está habilitada; um ID de conteúdo identifica a linha diretamente.

Filtrar uma coleção

Passe filtros no segundo argumento. A consulta a seguir retorna postagens publicadas na categoria news cujo campo series é engineering:

const { entries } = await getEmDashCollection("posts", {
  where: {
    category: "news",
    series: "engineering",
  },
});

Chaves que nomeiam uma taxonomia correspondem a slugs de termos atribuídos. 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 valor listado, então { category: ["news", "updates"] } corresponde a qualquer uma das duas 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 solicitação e depois o locale padrão configurado. Veja Internacionalização para regras de fallback e rotas traduzidas.

status aceita "published", "draft" ou "archived". Não solicite rascunhos 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 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",
  },
});

Colunas do sistema usam seus nomes de banco de dados, como created_at, updated_at e published_at. Campos personalizados usam o slug da coleção, como title ou priority. Os dados retornados mapeiam 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 desempate estável. Sem orderBy, as coleções usam por padrão created_at descendente. Marque campos escalares personalizados como indexados quando o site ordena ou filtra regularmente por eles; 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 separados e não podem ser combinados em uma consulta tipada.

Paginação por cursor

A paginação por cursor continua após a última entrada retornada pela consulta anterior. Mantenha a ordenação inalterada entre solicitações e passe nextCursor de volta 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 página final. A paginação por cursor não calcula um total de páginas nem fornece um cursor da página anterior; preserve URLs anteriores no histórico do navegador se a interface precisar de navegação de volta.

Paginação por offset

A paginação por offset serve 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.redirect("/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 por número de página, mas entradas adicionadas entre solicitações podem deslocar páginas posteriores. Use cursores quando esse movimento for confuso.

Consultar e renderizar uma entrada

A rota de runtime a seguir lê uma postagem por slug, renderiza a imagem em destaque e o corpo Portable Text, e distingue uma falha de consulta de uma entrada ausente:

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Image, PortableText } from "emdash/ui";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/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.redirect("/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 e blocos HTML sanitizados. Passe componentes personalizados quando um site adiciona seus próprios blocos Portable Text. Se substituir o renderizador htmlBlock, sanitize o HTML e permita apenas os hosts de iframe em que o site confia.

PortableText mostra tabelas como placeholders 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 de tabelas publicado.

Ler campos de referência

Um campo reference vincula uma entrada a entradas de outra coleção, e Relações cobre a definição. Seu valor não faz parte de data. Passe uma opção references a getEmDashEntry nomeando os campos que a página renderiza, chaveados pelo slug do campo, 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.redirect("/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 no limite padrão de 50 entradas. Use { limit, cursor } para um campo que contém mais, até 100 por página.

Cada entrada referenciada é um ContentEntry com a mesma forma de uma carregada diretamente: um id, um objeto data com datas como objetos Date e valores de mídia resolvidos, e um proxy edit limitado à entrada referenciada, de modo que clicar em um card na edição visual abre a entrada de que o card trata. Bylines e termos de taxonomia são a exceção — o EmDash não os hidrata em entradas referenciadas, então leia data.bylines e data.terms da própria entrada.

As entradas chegam na ordem que o editor arranjou quando o campo está na extremidade pai da relação. Um campo na extremidade filha lista as entradas que apontam para ele, que não têm ordem própria.

A opção é opt-in em ambas as direções. Uma chamada sem 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 link por campo, mais uma consulta de entradas por coleção de destino distinta, independentemente de quantas entradas cada campo contém.

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 de rascunhos do mesmo contexto de solicitação que getEmDashEntry, então um percurso iniciado em pré-visualização continua vendo a seleção pendente. Um id de página carrega seu locale onde o i18n prefixa um (fr/about), e esse é o id a passar de volta.

Em uma rota que coloca a saída em cache, mescle o cacheHint que a página retorna com o da própria rota, para que uma gravação em uma das entradas nomeadas expire a página.

Campos de referência na pré-visualização

Um render público vê entradas publicadas e a seleção publicada. Uma pré-visualização da entrada, ou um editor na edição visual, vê 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 no-index do editor com fallbacks da entrada. O template de blog atual passa esse resultado para o 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 inclui <EmDashHead> pode aplicar os mesmos campos SEO e contribuições de plugins a 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 no-index do editor.

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 URL e as anotações entry.edit usadas para 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 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 escrever 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. Veja Gerar tipos para essas opções.

Cada coleção que tem um campo de referência vinculado a uma relação recebe uma segunda interface, {Collection}References, registrada sob o mesmo slug. getEmDashEntry restringe seu resultado aos campos nomeados pela opção references, 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 Node.js e Cloudflare definem output: "server" em astro.config.mjs. Suas consultas de conteúdo executam em cada render do servidor, então uma revisão recém-publicada é elegível para a próxima solicitação. Se você pré-renderizar deliberadamente uma rota, o HTML contém o conteúdo disponível no momento do build e só muda após 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 entrada para que a publicação possa invalidar páginas em cache afetadas. Evite substituir essa integração por um tempo de vida fixo e longo de Cache-Control, a menos que atualizações atrasadas sejam uma decisão de produto explícita.

Para assinaturas exatas e filtros menos comuns, veja a referência da API JavaScript. Para construir um exemplo funcional em torno dessas consultas, continue com Criar um blog.