komal.nakrani
Browse Themes
ThemesCategoriesDocsBlogTips & Tricks
Routing••By Komal Nakrani

A Reusable Astro Pagination Component

An Astro pagination component for paginate(): previous/next links, numbered pages with gaps, aria-current, and rel attributes. Works with any collection.

#Astro#Components#Pagination

This Astro pagination component takes the page object that paginate() gives a route, and renders accessible navigation: previous and next links, numbered page links with gaps for long lists (1 … 4 5 6 … 20), and aria-current="page" on the current one. It works for any paginated collection, including tag archives, with no JavaScript.

Key Takeaways

  • Pass the page prop from paginate(); the component reads currentPage, lastPage and url.
  • Show a window of pages around the current one, plus the first and last.
  • Mark the current page with aria-current="page" and don’t link it.
  • Use a [...page].astro route so page one is the clean parent URL.

The route

---
// src/pages/blog/[...page].astro
import type { GetStaticPaths, Page } from 'astro';
import type { CollectionEntry } from 'astro:content';
import { getCollection } from 'astro:content';
import Pagination from '../../components/Pagination.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: 12 });
}) satisfies GetStaticPaths;

interface Props { page: Page<CollectionEntry<'blog'>> }
const { page } = Astro.props;
---
<ul>{page.data.map((post) => <li><a href={`/blog/${post.id}/`}>{post.data.title}</a></li>)}</ul>
<Pagination page={page} base="/blog" />

The component

---
// src/components/Pagination.astro
import type { Page } from 'astro';

interface Props { page: Page<unknown>; base: string; window?: number }
const { page, base, window = 1 } = Astro.props;
const { currentPage, lastPage } = page;

const href = (n: number) => (n === 1 ? `${base}/` : `${base}/${n}/`);

// Pages to show: first, last, and a window around the current page
const numbers = [...new Set([1, lastPage, ...Array.from({ length: window * 2 + 1 }, (_, i) => currentPage - window + i)])]
  .filter((n) => n >= 1 && n <= lastPage)
  .sort((a, b) => a - b);
---
{lastPage > 1 && (
  <nav class="pagination" aria-label="Pagination">
    {page.url.prev
      ? <a href={page.url.prev} rel="prev">Newer</a>
      : <span class="disabled" aria-hidden="true">Newer</span>}

    <ol>
      {numbers.map((n, i) => (
        <>
          {i > 0 && n - numbers[i - 1] > 1 && <li class="gap" aria-hidden="true">…</li>}
          <li>
            {n === currentPage
              ? <span aria-current="page">{n}</span>
              : <a href={href(n)} aria-label={`Page ${n}`}>{n}</a>}
          </li>
        </>
      ))}
    </ol>

    {page.url.next
      ? <a href={page.url.next} rel="next">Older</a>
      : <span class="disabled" aria-hidden="true">Older</span>}
  </nav>
)}

<style>
  .pagination { display: flex; align-items: center; justify-content: center; gap: 1rem; margin: 2rem 0; }
  .pagination ol { display: flex; gap: 0.25rem; list-style: none; padding: 0; margin: 0; }
  .pagination a, .pagination span { display: inline-block; min-width: 2.25rem; padding: 0.4rem 0.6rem; text-align: center; border-radius: 0.4rem; text-decoration: none; color: inherit; }
  .pagination a:hover { background: var(--color-surface-2, #f3f1ea); }
  .pagination [aria-current='page'] { background: var(--color-text, #14140f); color: #fff; }
  .pagination .disabled { opacity: 0.4; }
</style>

href() builds URLs for the [...page].astro pattern, where page one lives at /blog/. If your route is [page].astro, use `${base}/${n}/` for every page.

Tag archives

Pass a different base:

<Pagination page={page} base={`/blog/tags/${tag}`} />

The tag route pattern with paginate() is in the Astro routing guide.

SEO notes

  • Each paginated page is its own URL with a self-referencing canonical. Don’t canonical page 2 to page 1.
  • rel="prev" and rel="next" on links are harmless and help some tools, though Google no longer uses them as an indexing signal.
  • Keep page size stable so URLs don’t shift content around unnecessarily.

The canonical rule is covered in canonical URLs in Astro, and every page property in the routing cheatsheet. The routing reference documents paginate().

FAQ

How do I change how many page numbers show?

Pass window={2} to show two pages either side of the current one.

Does this work for on-demand routes?

paginate() is for static routes. On demand, compute pages from a query parameter and pass equivalent values to the component.