Skip to content

SSG Pagination — Splitting Content Across Multiple Pages

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about SSG Pagination. We cover key concepts, practical examples, and best practices to help you master this topic.

SSG pagination splits large content collections into multiple static pages with next/previous navigation, improving load time and user experience.

What You'll Learn

By the end of this tutorial, you'll understand how to implement pagination in major SSG frameworks, handle page numbering, create navigation components, and optimize pagination for SEO.

Why It Matters

A blog with 500 posts on a single page is unusable. Pagination breaks content into chunks, improves load time, and helps search engines index your content structure.

Real-World Use

A news site with 10,000 articles paginates by month and category. Each page shows 20 articles with numbered navigation. Google crawls each paginated page separately, and users navigate easily through years of content.

Pagination Architecture

graph TD
    A[Content Collection
500 posts] --> B[Pagination Logic] B --> C[Page 1
posts 1-10] B --> D[Page 2
posts 11-20] B --> E[Page 3
posts 21-30] B --> F[... 50 pages total] C --> G[Generated URLs
/blog/page/1] D --> G E --> G F --> G G --> H[Static HTML files] H --> I[CDN Deploy] I --> J[User navigates
between pages] style B fill:#4a90d9,color:#fff style H fill:#27ae60,color:#fff style J fill:#e67e22,color:#fff

Next.js Pagination

// pages/blog/page/[page].js — Dynamic pagination
export default function BlogPage({ posts, currentPage, totalPages }) {
    return (
        <div>
            <h1>Blog  Page {currentPage}</h1>

            <div className="posts">
                {posts.map(post => (
                    <article key={post.id}>
                        <h2>{post.title}</h2>
                        <p>{post.excerpt}</p>
                        <a href={`/blog/${post.slug}`}>Read more</a>
                    </article>
                ))}
            </div>

            <nav className="pagination">
                {currentPage > 1 && (
                    <a href={`/blog/page/${currentPage - 1}`}>Previous</a>
                )}

                {Array.from({ length: totalPages }, (_, i) => i + 1)
                    .map(page => (
                        <a
                            key={page}
                            href={`/blog/page/${page}`}
                            className={page === currentPage ? 'active' : ''}
                        >
                            {page}
                        </a>
                    ))
                }

                {currentPage < totalPages && (
                    <a href={`/blog/page/${currentPage + 1}`}>Next</a>
                )}
            </nav>
        </div>
    );
}

const POSTS_PER_PAGE = 10;

export async function getStaticPaths() {
    const res = await fetch('https://api.example.com/posts');
    const allPosts = await res.json();
    const totalPages = Math.ceil(allPosts.length / POSTS_PER_PAGE);

    const paths = Array.from({ length: totalPages }, (_, i) => ({
        params: { page: String(i + 1) },
    }));

    return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
    const page = parseInt(params.page);
    const res = await fetch('https://api.example.com/posts');
    const allPosts = await res.json();

    const start = (page - 1) * POSTS_PER_PAGE;
    const posts = allPosts.slice(start, start + POSTS_PER_PAGE);

    return {
        props: {
            posts,
            currentPage: page,
            totalPages: Math.ceil(allPosts.length / POSTS_PER_PAGE),
        },
    };
}

Eleventy Pagination

---
# src/blog.njk — Eleventy paginated blog listing
pagination:
    data: collections.posts
    size: 5
    alias: posts
    reverse: true
permalink: "blog{% if pagination.pageNumber > 0 %}/{{ pagination.pageNumber + 1 }}{% endif %}/"
---

<h1>Blog</h1>

<div class="posts">
    {% for post in posts %}
    <article>
        <h2><a href="{{ post.url }}">{{ post.data.title }}</a></h2>
        <time>{{ post.date | readableDate }}</time>
        <p>{{ post.data.description }}</p>
    </article>
    {% endfor %}
</div>

{# Pagination navigation #}
<nav class="pagination">
    {% if pagination.previousPageHref %}
    <a href="{{ pagination.previousPageHref }}" class="prev">Previous</a>
    {% endif %}

    {% set totalPages = pagination.pages.length %}
    {% for pageEntry in pagination.pages %}
        {% set pageNum = loop.index %}
        <a href="{{ pagination.hrefs[loop.index0] }}"
           class="{% if page.url == pagination.hrefs[loop.index0] %}active{% endif %}">
            {{ pageNum }}
        </a>
    {% endfor %}

    {% if pagination.nextPageHref %}
    <a href="{{ pagination.nextPageHref }}" class="next">Next</a>
    {% endif %}
</nav>

Hugo Pagination

<!-- layouts/_default/list.html — Hugo pagination -->
{{ define "main" }}
<h1>{{ .Title }}</h1>

<div class="posts">
    {{ range .Paginator.Pages }}
    <article>
        <h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
        <time>{{ .Date.Format "Jan 2, 2006" }}</time>
        <p>{{ .Summary }}</p>
    </article>
    {{ end }}
</div>

<!-- Pagination navigation -->
<nav class="pagination">
    {{ if .Paginator.HasPrev }}
    <a href="{{ .Paginator.Prev.URL }}" class="prev">Previous</a>
    {{ end }}

    {{ .Paginator.PageNumber }} / {{ .Paginator.TotalPages }}

    {{ if .Paginator.HasNext }}
    <a href="{{ .Paginator.Next.URL }}" class="next">Next</a>
    {{ end }}
</nav>

<!-- Full page number navigation -->
<nav class="pagination-numbers">
    {{ range .Paginator.Pagers }}
    <a href="{{ .URL }}"
       class="page {{ if eq .PageNumber $.Paginator.PageNumber }}active{{ end }}">
        {{ .PageNumber }}
    </a>
    {{ end }}
</nav>
{{ end }}

<!-- config.toml — Pagination settings -->
[pagination]
    pagerSize = 10
    path = "page"

SEO for Paginated Pages

<!-- Add rel=next and rel=prev for SEO -->
<link rel="prev" href="https://example.com/blog/page/2/" />
<link rel="next" href="https://example.com/blog/page/4/" />

<!-- views/partials/seo-pagination.html -->
{{ if .Paginator }}
    {{ if .Paginator.HasPrev }}
    <link rel="prev" href="{{ .Paginator.Prev.URL | absURL }}">
    {{ end }}
    {{ if .Paginator.HasNext }}
    <link rel="next" href="{{ .Paginator.Next.URL | absURL }}">
    {{ end }}

    <!-- Canonical URL for all pagination pages -->
    <link rel="canonical" href="{{ .Paginator.URL | absURL }}">
{{ end }}

<!-- pagination.jsonld — Structured data for pagination -->
<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "ItemList",
    "itemListElement": [
        {{ range $index, $post := .Posts }}
        {
            "@type": "ListItem",
            "position": {{ add $index 1 }},
            "url": "{{ $post.Permalink }}"
        }{{ if not (last $index $.Posts) }},{{ end }}
        {{ end }}
    ],
    "numberOfItems": {{ .TotalPosts }},
    "itemListOrder": "Descending"
}
</script>

Common Mistakes

  1. Not handling page 1 as the root URL. /blog/ should show page 1, not /blog/page/1/. Use canonical to avoid duplicate content.
  2. Missing noindex for pagination pages. Search engines may index pagination pages as thin content. Use noindex for page 2+ if content is minimal.
  3. Broken navigation when pages are 0 or negative. Validate the page parameter. Return 404 for non-existent pages.
  4. Not using rel=prev/rel=next. These tell search engines paginated pages are part of a series, preventing duplicate content issues.
  5. Generating too many pagination pages. 500 pages of 10 each = 50 pages. That's fine. But if each page has 1 result, increase the page size.

Practice Questions

  1. How does pagination differ between Next.js, Eleventy, and Hugo?
  2. What SEO considerations apply to paginated pages?
  3. How do you calculate total pages from a content collection size?
  4. What is the role of rel=prev and rel=next in pagination SEO?
  5. How do you handle edge cases like empty pages or invalid page numbers?

Challenge: Build a paginated blog with 50 posts: implement pagination (10 per page) with numbered page navigation, add previous/next links, set up proper SEO tags (rel prev/next, canonical), and ensure page 1 is at /blog/ not /blog/page/1/.

FAQ

Should I use infinite scroll or pagination?

Pagination is better for SEO and accessibility. Infinite scroll improves engagement but makes footer navigation and search engine crawling harder.

How many items should I show per page?

10-20 items is standard. Fewer items means more pages (worse UX). More items means slower page loads. Consider 20 for listing pages, 10 for detailed previews.

Do search engines index paginated pages?

Yes, but they may treat page 2+ as lower priority. Use rel=prev/next to indicate sequence. Consider noindex for very thin pagination pages.

How does pagination affect build time?

Each pagination page is a separate static file. More pages = longer build time. Compress by showing more items per page if build time is a concern.

Can I paginate by date (monthly archives) instead of page numbers?

Yes. Date-based pagination (/2026/01/, /2026/02/) is common for blogs. It provides context and helps with SEO for time-sensitive content.

Mini Project

Create a paginated documentation site with 100+ pages: group content into sections with 10 items per page, implement numbered pagination with URL structure /docs/section/page/N, add previous/next navigation, configure rel=prev/rel=next for SEO, and test with Google Search Console.

What's Next

Content is organized with pagination. Now explore SSG Internationalization to build multilingual static sites that serve global audiences.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro