Le pagine EmDash leggono i contenuti al momento della richiesta con getEmDashCollection() e getEmDashEntry().
La prima funzione restituisce un elenco, la seconda restituisce una voce tramite il suo slug o il suo ID del contenuto. Entrambe
restituiscono gli errori come dati, così la pagina può decidere come rispondere.
I template inclusi usano l’output server di Astro. Un visitatore riceve quindi l’ultima revisione pubblicata al rendering successivo, dopo che un editor l’ha pubblicata. Una bozza salvata resta privata finché non viene pubblicata o richiesta tramite un URL di anteprima valido.
Interrogare una collezione
La pagina seguente carica i sette post pubblicati più di recente. Le query pubbliche sulle collezioni usano per impostazione predefinita
status: "published", quindi il filtro non deve ripeterlo.
---
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>
L’ordinamento e il limite avvengono nel database prima che EmDash assembli le voci. Questo evita di caricare un’intera collezione per poi ordinarla o ritagliarla nella pagina.
Il risultato contiene:
entries, che è un array vuoto quando non ci sono corrispondenze o la query è fallita;error, che viene impostato quando la query fallisce, ma non per un risultato vuoto;cacheHint, che porta i tag e l’ora dell’ultima modifica per la cache di Astro;nextCursor, che viene impostato quando una pagina a cursore limitata ha altre voci; ehasMore, che indica se una pagina a cursore o a offset limitata ha un’altra pagina.
Identificatori della voce
Ogni risultato ha due identificatori con compiti diversi:
entry.idè l’identificatore rivolto all’URL prodotto dal content loader. Normalmente è lo slug. Quando l’internazionalizzazione antepone un locale, il prefisso è incluso. Usa questo valore quando costruisci un link a partire da un risultato di collezione.entry.data.idè l’ID del contenuto stabile memorizzato nel database. Non cambia quando un editor cambia lo slug. Usalo quando un’API, un helper di tassonomia, il contesto della pagina o una relazione si aspetta un ID del contenuto.
getEmDashEntry() accetta sia lo slug sia l’ID del contenuto stabile. Una ricerca per slug è limitata a un
locale quando l’internazionalizzazione è abilitata; un ID del contenuto identifica direttamente la riga.
Filtrare una collezione
Passa i filtri nel secondo argomento. La query seguente restituisce i post pubblicati nella categoria
news il cui campo series è engineering:
const { entries } = await getEmDashCollection("posts", {
where: {
category: "news",
series: "engineering",
},
});
Le chiavi che nominano una tassonomia corrispondono agli slug dei termini assegnati. Le altre chiavi corrispondono ai campi della collezione. Più
chiavi sono combinate con logica AND. Un array su una chiave corrisponde a uno qualsiasi dei valori elencati, quindi
{ category: ["news", "updates"] } corrisponde a entrambe le categorie.
Usa locale per richiedere esplicitamente una lingua:
const { entries: frenchPosts } = await getEmDashCollection("posts", {
locale: "fr",
orderBy: { published_at: "desc" },
});
Se locale è omesso, EmDash usa il locale della richiesta e poi il locale predefinito configurato. Vedi
Internazionalizzazione (i18n) per le regole di fallback
e le route tradotte.
status accetta "published", "draft" o "archived". Non richiedere bozze da una route
pubblica. Usa il flusso di anteprima quando un visitatore ha bisogno di un accesso temporaneo a una voce non pubblicata.
Ordinare i risultati nel database
orderBy associa i nomi dei campi a "asc" o "desc". Usa i nomi dei campi memorizzati, non i nomi in camelCase
restituiti in entry.data:
const { entries } = await getEmDashCollection("posts", {
orderBy: {
published_at: "desc",
title: "asc",
},
});
Le colonne di sistema usano i loro nomi nel database, come created_at, updated_at e published_at.
I campi personalizzati usano lo slug della collezione, come title o priority. I dati restituiti associano le date di sistema
a createdAt, updatedAt e publishedAt, ma questi nomi di proprietà in camelCase non sono
campi orderBy validi.
EmDash usa il primo campo orderBy valido come chiave di paginazione e l’ID del contenuto come
criterio di spareggio stabile. Senza orderBy, le collezioni sono ordinate per impostazione predefinita per created_at decrescente. Contrassegna i campi scalari
personalizzati come indicizzati quando il sito ordina o filtra regolarmente per essi; l’indice evita una scansione completa della tabella
man mano che la collezione cresce.
Paginare una collezione
Usa un cursore per un feed continuo o un offset per pagine numerate. Sono modelli di paginazione separati e non possono essere combinati in un’unica query tipizzata.
Paginazione a cursore
La paginazione a cursore prosegue dopo l’ultima voce restituita dalla query precedente. Mantieni l’ordinamento
invariato tra le richieste e restituisci nextCursor senza ispezionarlo né modificarlo.
La route seguente renderizza un link Older posts quando esiste un’altra pagina:
---
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 è assente nell’ultima pagina. La paginazione a cursore non calcola il numero totale di pagine e non
fornisce un cursore per la pagina precedente; conserva gli URL precedenti nella cronologia del browser se l’interfaccia ha bisogno
della navigazione all’indietro.
Paginazione a offset
La paginazione a offset è adatta a route come /posts/page/3. Converti il numero di pagina in un offset e usa
hasMore per il link alla pagina successiva:
---
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>
Un offset deve essere un intero non negativo. La pagina 1 usa un offset pari a zero, che significa «parti dalla prima voce». La paginazione a offset è facile da indirizzare tramite il numero di pagina, ma le voci aggiunte tra una richiesta e l’altra possono spostare le pagine successive. Usa i cursori quando questo spostamento creerebbe confusione.
Interrogare e renderizzare una voce
La route runtime seguente legge un post tramite slug, ne renderizza l’immagine in evidenza e il corpo in Portable Text, e distingue un errore della query da una voce mancante:
---
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 fornisce i renderer per i blocchi standard di EmDash, tra cui immagini, video, gallerie, codice,
tabelle, blocchi HTML e blocchi iframe. Passa componenti personalizzati quando un sito aggiunge i propri blocchi Portable Text.
Un blocco HTML con isolated: true esegue il rendering di html, css e js in un iframe in sandbox che adatta le
proprie dimensioni al contenuto. Gli altri blocchi HTML eseguono il rendering del proprio html, sanificato, nella pagina
stessa. Se sostituisci il renderer htmlBlock, passa i blocchi isolati a HtmlBlock di emdash/ui oppure
renderizzali in un iframe il cui sandbox non includa allow-same-origin. Sanifica tu stesso l’HTML inline e consenti
solo gli host iframe di cui il sito intende fidarsi.
Un blocco iframe viene renderizzato tramite Iframe di emdash/ui come iframe responsive a caricamento differito, con un
sandbox e la referrer policy strict-origin-when-cross-origin. Le pagine sull’host del tuo sito
perdono l’accesso same-origin, e vengono renderizzate solo le sorgenti https.
Un blocco video viene renderizzato tramite Video di emdash/ui. Un video della Libreria multimediale viene riprodotto nel player integrato del browser, con i controlli e la didascalia, dall’URL pubblico del tuo storage quando ne è configurato uno. Senza un URL pubblico, i video vengono serviti tramite /_emdash/api/media/file/. Un video di un provider multimediale viene renderizzato tramite quel provider. Un blocco video vuoto, privo di asset, non renderizza nulla.
Con la Content Security Policy di Astro
attiva, il browser blocca gli stili e gli script dei blocchi HTML isolati, che quindi vengono visualizzati senza il loro
CSS, il loro JavaScript e l’altezza automatica. Anche i blocchi iframe perdono la dimensione personalizzata, e il
frame-src della policy deve consentire gli host che incorporano. I blocchi video i cui contenuti si trovano su un altro host, come l’URL pubblico di uno storage o un provider multimediale, richiedono quell’host in media-src.
PortableText mostra le tabelle come segnaposto di sola lettura in modalità di modifica. Per localizzare la loro etichetta iniziale, passa tablePlaceholder={translatedLabel}. Il valore predefinito è "Table (edit in admin)" e non influisce sul contenuto delle tabelle pubblicate.
Leggere i campi di riferimento
Un campo reference collega una voce a voci di un’altra collezione, e Relazioni spiega come definirne uno. Il suo valore non fa parte di data. Passa a getEmDashEntry un’opzione references che nomini i campi che la pagina renderizza, con lo slug del campo come chiave, e ognuno torna come pagina di voci:
---
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 richiede la prima pagina con il limite predefinito di 50 voci. Usa { limit, cursor } per un campo che ne contiene di più, fino a 100 per pagina.
Ogni voce referenziata è un ContentEntry con la stessa forma di una caricata direttamente: un id, un oggetto data con le date come oggetti Date e i valori multimediali risolti, e un proxy edit limitato alla voce referenziata, così cliccando una scheda nella modifica visuale si apre la voce a cui la scheda si riferisce. Le byline e i termini di tassonomia sono l’eccezione: EmDash non li assembla sulle voci referenziate, quindi leggi data.bylines e data.terms dalla voce stessa.
Le voci arrivano nell’ordine stabilito dall’editor quando il campo si trova sul lato padre della sua relazione. Un campo sul lato figlio elenca le voci che puntano a esso, le quali non hanno un ordine proprio.
L’opzione è opt-in in entrambe le direzioni. Una chiamata che non passa references non esegue query aggiuntive, e un campo lasciato fuori dalla selezione non viene letto. Una chiamata che seleziona dei campi costa una query di collegamenti per campo, più una query di voci per ogni collezione di destinazione distinta, indipendentemente da quante voci contenga ciascun campo.
Paginare un campo di riferimento
getEmDashReferences recupera la pagina successiva di un singolo campo, usando il cursore restituito dalla pagina precedente:
import { getEmDashReferences } from "emdash";
const { entries, nextCursor } = await getEmDashReferences("posts", post.id, "related_posts", {
cursor,
limit: 20,
});
Legge la visibilità delle bozze dallo stesso contesto di richiesta di getEmDashEntry, quindi un percorso avviato in anteprima continua a vedere la selezione in sospeso. Un id di pagina porta con sé il suo locale dove i18n ne antepone uno (fr/about), ed è quell’id che va ripassato.
Su una route che mette in cache il proprio output, unisci il cacheHint restituito dalla pagina a quello della route stessa, in modo che una scrittura su una delle voci che ha nominato faccia scadere la pagina.
Campi di riferimento in anteprima
Un rendering pubblico vede le voci pubblicate e la selezione pubblicata. Un’anteprima della voce, o un editor nella modifica visuale, vede le voci non pubblicate e la selezione preparata nella bozza della voce, quindi un link di anteprima mostra i riferimenti che la pagina avrà una volta pubblicata.
Applicare funzioni SEO e di editing
Per una collezione con supporto SEO, getSeoMeta() risolve il titolo SEO, la descrizione,
l’immagine, l’URL canonico e la scelta no-index dell’editor, con valori di fallback dalla voce. L’attuale template del blog
passa quel risultato al suo layout di 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>
Un layout che include <EmDashHead> può applicare gli stessi campi SEO e i contributi dei plugin alle
pagine di contenuto renderizzate sul server. I meta tag scritti a mano che leggono solo data.title o data.excerpt
non applicano l’URL canonico né l’impostazione no-index dell’editor.
Gli URL di anteprima non richiedono una query separata. Il middleware convalida il token _preview e
getEmDashEntry() restituisce la bozza corrispondente con isPreview: true. La
guida all’anteprima e alla modifica visuale spiega la generazione degli URL e le annotazioni entry.edit
usate per la modifica in linea.
Generare tipi TypeScript
Il server di sviluppo genera emdash-env.d.ts dallo schema attivo. Mantieni quel file incluso nella
configurazione TypeScript del progetto, così un nome di collezione come "posts" seleziona automaticamente il
tipo di dati Post generato.
Per un’istanza EmDash remota, la CLI può recuperare lo schema e scrivere i tipi in .emdash/types.ts:
npx emdash types --url https://cms.example.com
Il comando accetta un token API o header di autenticazione personalizzati. Vedi Generare i tipi per queste opzioni.
Ogni collezione che ha un campo di riferimento collegato a una relazione ottiene una seconda interfaccia,
{Collection}References, registrata sotto lo stesso slug. getEmDashEntry restringe il suo risultato ai
campi nominati dall’opzione references, e le voci di ogni pagina portano l’interfaccia della
collezione di destinazione:
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
Rendering runtime e caching
I template del blog per Node.js e Cloudflare impostano output: "server" in astro.config.mjs. Le loro query
sui contenuti vengono eseguite a ogni rendering sul server, quindi una revisione appena pubblicata è idonea per la richiesta
successiva. Se prerenderizzi deliberatamente una route, il suo HTML contiene il contenuto disponibile al momento
della build e cambia solo dopo un’altra build.
Quando la cache di Astro è abilitata, passa il cacheHint della query a Astro.cache.set(). EmDash associa
la risposta ai tag di collezione e di voce, così la pubblicazione può invalidare le pagine in cache interessate.
Evita di sostituire quell’integrazione con una durata fissa e lunga di Cache-Control, a meno che gli aggiornamenti ritardati
non siano una decisione di prodotto esplicita.
Per le firme esatte e i filtri meno comuni, vedi il Riferimento API JavaScript. Per costruire un esempio funzionante attorno a queste query, continua con Creare un Blog.