Data, content, and assets
Query and render a collection
Load entries, filter drafts, sort dates, and pass one consistent content shape into the page.
Once a collection is defined, pages query it with getCollection() from astro:content. It returns an array of entries. Each entry has an id and its frontmatter under data.
A listing page
Here is src/pages/notes/index.astro:
---
import { getCollection } from 'astro:content'
const notes = await getCollection('notes', ({ data }) => !data.draft)
notes.sort(
(a, b) => b.data.publishedAt.valueOf() - a.data.publishedAt.valueOf()
)
---
<ul>
{notes.map(note => (
<li><a href={`/notes/${note.id}/`}>{note.data.title}</a></li>
))}
</ul>
The second argument to getCollection() is a filter. Any entry it rejects is gone from the result. Drafts never get a link here, because they never enter the array.
Filter before you build links or routes, not after. It’s the only way to be sure an unpublished URL is never generated anywhere.
Then sort. The loader returns entries in whatever order it read the files. That’s not a publishing policy. The publishedAt comparison above makes “newest first” explicit, and it works because the schema turned the date into a real Date.
A detail page
For one page per note, use a dynamic route at src/pages/notes/[id].astro:
---
import { getCollection, render } from 'astro:content'
export async function getStaticPaths() {
const notes = await getCollection('notes', ({ data }) => !data.draft)
return notes.map(note => ({ params: { id: note.id }, props: { note } }))
}
const { note } = Astro.props
const { Content } = await render(note)
---
<h1>{note.data.title}</h1>
<Content />
getStaticPaths() runs once at build time. It queries the collection with the same draft filter and returns one route per note, passing the entry along as a prop. Each page gets its note for free. No second query.
render() compiles the entry’s Markdown and gives back a Content component. Drop it in the template like any other component and the body appears as HTML.
Keep the policy in the route
Filtering and sorting belong here, in the route. A NoteCard component should receive a title, a date, and a URL. It shouldn’t know that drafts exist, or how the collection is stored. Small props in, HTML out.
The missing await
The mistake I see most often is this:
const notes = getCollection('notes')
No await. You get a Promise, not an array. The symptom is notes.sort is not a function, or a listing that’s empty for no visible reason. Check the query line before you suspect the collection.
Build and count the folders under dist/notes/. The number should match the published notes, not the total files. If a draft shows up, the filter is missing in one of the two routes.
Lesson completed