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
pageprop frompaginate(); the component readscurrentPage,lastPageandurl.- 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].astroroute 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"andrel="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.