John Dalesandro

Astro: Collection Pagination Using Rest Parameter Dynamic Route

Creating blog pages and page navigation using [...page].astro

Astro has built-in pagination to split collections into multiple pages, with each page containing a specified number of entries. This feature works with dynamic routes, allowing for URLs like /blog/, /blog/2/, /blog/3/, and so on.

Many examples of Astro pagination can be found in the Astro documentation and other online sources. However, I encountered an issue where I kept receiving the error Property 'data' does not exist on type 'never' when trying to access page properties. This error suggests that the page variable was not properly typed as Page. I could manually apply the Page type to the page variable to fix the error, but that didn’t seem like the right solution.

The following solution fixes the type issue by importing the GetStaticPaths type and using the satisfies operator. The code provides a basic post listing for the current page as well as simple page navigation. You will need to add formatting and styles as appropriate.

Astro Project Structure

In this demonstration, the Astro project structure is as follows: The collection, named blog, contains three entries. There is also a dynamic route called [...page].astro which contains all of the code provided below.

AstroProject/
└── src/
    ├── content/
    │   └── blog/
    │       ├── post1.mdx
    │       ├── post2.mdx
    │       └── post3.mdx
    └── pages/
        └── blog/
            ├── [...slug].astro
            └── [...page].astro

Pagination Using Dynamic Route

In the [...page].astro file, add the following code segment to the codefence. Adjust the collection name as needed.

Key parts of the code are:

The pageSize is set to 10 entries per page. With three entries in the collection, setting pageSize to 1 creates three pages.

---
import { getCollection } from 'astro:content';

import type { GetStaticPaths } from 'astro';

export const getStaticPaths = (async ({paginate}) => {
  const posts = (await getCollection('blog'))
    .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

  return paginate(posts, { pageSize: 10 } );
}) satisfies GetStaticPaths;

const { page } = Astro.props;

const pageNums = Array.from({length: page.lastPage}, (_, i) => i + 1);
---

Listing Current Page Entries

Add the following to [...page].astro to access and list each entry or post for the current page.

{
  page.data.map((post) => (
      <a href={`/blog/${post.slug}/`}>{post.data.title}</a>
  ))
}

Adding Page Navigation

Include the following in [...page].astro to add page navigation links, such as “previous page,” “next page,” and numbered pages.

An extra condition ensures that the first page links to /blog/ instead of /blog/1/.

{
  (page.lastPage > 1) && (
    <nav>
      { page.url.prev && ( <a href={page.url.prev}>Previous Page</a> ) }
      { pageNums.map((num) => ( <a href={`/blog/${num === 1 ? '' : (num) + '/'}`}>{num}</a> ) ) }
      { page.url.next && ( <a href={page.url.next}>Next Page</a> ) }
    </nav>
  )
}

Summary

Astro’s built-in pagination feature splits collections into multiple pages and works with dynamic routes, creating URLs like /blog/, /blog/2/, etc. The typing issue with the page variables was resolved by importing GetStaticPaths types and using the satisfies operator. With a few lines of code, it is relatively easy to provide paginated post listings and page navigation.