Pages from Content

By Syncfusion®, Inc. · Last updated

Creating a page for each collection entry, listing pages, pagination with paginate() and the page prop, nested pagination for tags, and references between collections with reference(), getEntry() and getEntries().

Pages from Content

  • A collection holds Asha's posts. Now each post needs its own page.
  • She also needs a list of all posts, split into pages of 5, and a page for each tag.
  • All of these pages are made from the same collection, with dynamic routes.
  • In this lesson, you will combine Routing and Content Collections to build a real blog.

Simple lesson: one collection, a few dynamic page files, and Astro builds every blog page for you.

A Real-World Example

  • Asha adds a new Markdown file to src/content/blog.
  • She does not touch any page file.
  • At the next build, the new post gets its own page, appears in the list, and shows up on its tag pages.

Press ▶ Run. Open a post from the list. Then look at the page list in the address bar.

What happened:

  • getStaticPaths() asked the collection for every post.
  • Each post became one path: its id in params, the whole entry in props.
  • The page rendered the entry with render().

What You Will Learn

In this lesson, you will learn:

  1. How to build one page per entry.
  2. How to build a listing page.
  3. How to split a long list with paginate().
  4. How to use the page prop and its links.
  5. How to build tag pages, with pagination inside them.
  6. How to link collections with reference().

Generating Pages from Content

Creating a Page for Each Entry

  1. Make a dynamic route file, such as src/pages/blog/[id].astro.
  2. In getStaticPaths(), call getCollection().
  3. Return one item per entry: params: { id: post.id }, props: { post }.
  4. In the page, read Astro.props.post and render() it.

Animation: each entry in the blog collection passes through the [id].astro page file and comes out as its own HTML page.

blog collection

why-astrocollectionsmarkdown-tips

[id].astro getStaticPaths()

/blog/why-astro

/blog/collections

/blog/markdown-tips

One page file, one page per entry.

Building a Listing Page

  • A listing page is a normal page, such as src/pages/blog/index.astro.
  • It calls getCollection(), sorts the posts, and links to each one with "/blog/" + post.id.
  • A listing often shows a short summary. Add a description field to the schema for it.

Use the entry's id for the address, so the link on the listing page and the page's real address always match.

Simple lesson: a dynamic route with getStaticPaths() makes the post pages. A normal page lists them.

Pagination

paginate()

  • Pagination = splitting a long list over several pages: page 1, page 2, page 3.
  • getStaticPaths() receives a helper called paginate.
  • return paginate(posts, { pageSize: 5 }) makes one page per 5 posts.
  • The file name needs a page parameter, usually a rest parameter: src/pages/blog/[...page].astro.
  • Page 1 is at /blog. Page 2 is at /blog/2, and so on.

The page Prop

Each page gets a prop called page:

  • page.data = the posts on this page.
  • page.currentPage and page.lastPage = page numbers.
  • page.total = how many posts in all.
  • page.url.prev and page.url.next = links to the next and previous pages, or undefined.
  • page.url.first and page.url.last = links to the first and last pages.

Press ▶ Run. Seven posts, 3 per page, make 3 pages. Click Next.

  • page.start is the position of the first item on this page, counting from 0.
  • In a real blog, posts comes from getCollection(), sorted newest first.

[TS] Typing getStaticPaths with paginate

  • Import GetStaticPaths and Page from astro:
import type { GetStaticPaths, Page } from "astro";

export const getStaticPaths = (async ({ paginate }) => {
  return paginate(["a", "b", "c"], { pageSize: 2 });
}) satisfies GetStaticPaths;

const { page } = Astro.props as { page: Page<string> };

Simple lesson: paginate(list, { pageSize }) in a [...page] route. The page prop holds the items and the links.

Tag Pages and Nested Pagination

A Page per Tag

  • Collect every tag from every post, without repeats: new Set(posts.flatMap((p) => p.data.tags)).
  • Make one path per tag in src/pages/tags/[tag].astro.
  • Pass the matching posts as a prop.

Nested Pagination

  • Nested pagination = pagination inside another dynamic route, such as page 2 of the "astro" tag.
  • The file is src/pages/tags/[tag]/[...page].astro.
  • Call paginate() once per tag, with params: { tag }. Then join the results with flatMap().

Press ▶ Run. The tag list links to each tag page. The "astro" tag has more than 2 posts, so it has two pages.

Simple lesson: collect the tags, then paginate once per tag with params: { tag }.

References Between Collections

reference()

  • A post has an author. The author's details live in another collection.
  • In the schema, write author: reference("authors").
  • In the post's frontmatter, write only the author's ID: author: asha.
  • Astro checks that the ID exists, and stores a small reference: { collection: "authors", id: "asha" }.

Resolving Referenced Entries

  • A reference is not the author's data yet. Look it up:

    • one reference: await getEntry(post.data.author),
    • a list of references: await getEntries(post.data.related).

A reference keeps data in one place. When Ravi moves city, Asha changes one line in authors.json, and every post shows the new city.

A typo in a referenced ID may only show up when you look it up: getEntry() can return undefined. Use ?. or check for it, so one typo does not break the whole page.

Simple lesson: reference("authors") stores an ID. getEntry() and getEntries() turn it into full entries.

TL;DR

  1. Page per entry = [id].astro + getStaticPaths() returning params: { id } and props: { post }.
  2. Listing page = getCollection(), sort, link with "/blog/" + post.id.
  3. paginate() = paginate(list, { pageSize }) in a [...page].astro route. Page 1 has no number in its address.
  4. page prop:

    • page.data = items on this page,
    • page.currentPage, page.lastPage, page.total,
    • page.url.prev, page.url.next, page.url.first, page.url.last.
  5. Tag pages = collect tags with a Set, one path per tag.
  6. Nested pagination = paginate(tagged, { params: { tag }, pageSize }) per tag, joined with flatMap().
  7. reference("authors") = an ID in the schema. getEntry() / getEntries() = resolve it.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
params: { slug: post.id } in [id].astroThe param name does not matterUse the name in the brackets: idBuild error about a missing param
Not passing the entry as a propThe page can find itPass props: { post }post is undefined
paginate() in [page].astroAny param worksUse [...page] so page 1 has no number/blog is missing
Linking to /blog/1Page 1 has a numberPage 1 is at /blogThe link shows the 404 page
Using post.data.author.nameReferences hold the dataCall getEntry(post.data.author) firstname is undefined
Repeated tags in the tag listTags are already uniqueUse new Set()The same tag page is listed twice

Check Yourself

Exercises

What to Remember

  • [id].astro with getStaticPaths() gives every entry its own page.
  • A listing page sorts the collection and links with each entry's id.
  • paginate() splits a list into pages. The page prop has the items and the links.
  • Tag pages collect tags with a Set. Nested pagination passes params: { tag }.
  • reference() links collections. getEntry() and getEntries() resolve the links.

Next: Images and Fonts, where Asha's posts get optimised cover images and a custom font.