Post Pagination

Every post type has a paginated index page.

Custom posts can be displayed on any page but each type you create has a special index page that has the power to paginate your post list.

How Pagination Works

Client side pagination fetches a single pre-built JSON index once, then renders page changes instantly in the browser with no reloads or extra requests. You still get fully rendered static HTML at build time so search engines get crawlable content.

/blog/               → Page 1 (posts 1-10)
/blog/page/2/        → Page 2 (posts 11-20)
/blog/page/3/        → Page 3 (posts 21-30)

A static HTML file is generated for each page of posts.

Paginated Template

Here's a basic template for a paginated post index page.

<div data-posts="blog" data-per-page="6">
  <div data-posts-list>
    {{#each (posts "blog")}}
      <article data-post>
        <a href="{{url}}" data-field-href="url" data-field="title">
          {{title}}
        </a>
      </article>
    {{/each}}
  </div>

  <nav data-posts-pagination>
    <button data-page="prev">Previous</button>
    <button data-page="next">Next</button>
  </nav>
</div>

data-posts="blog"

Marks the container and names the post type (blog).

data-posts-list

The element that gets re-rendered on each page change. Its contents are wiped and rebuilt, so only the repeating items belong inside it.

data-per-page="6"

Posts shown per page. The list is sliced into pages of this size (the default is 10).

{{#each (posts "blog")}}

Build-time Handlebars loop that renders the initial page server-side (so the list works without JavaScript).

data-post

Marks one item as a single post. The controller clones the first data-post element as its template for redrawing each post on page change.

data-field="title"

Binds the element's text to the post's title field when the item is redrawn client-side.

data-field-href="url"

Binds the element's href to the post's url field on redraw.

data-posts-pagination

Marks the pagination controls container; the prev/next wiring and the auto enable/disable logic are scoped to elements inside it.

data-page="prev"

The previous page button. Auto-disables on the first page.

data-page="next"

The next page button. Auto-disables on the last page.

Page Numbers

Add clickable page numbers between previous and next. Page numbers are generated automatically. For more than seven pages, the results are truncated.

<nav data-posts-pagination>
  <button data-page="prev">Prev</button>

  <span data-posts-page-numbers>
    <button data-page-number>1</button>
    <button data-page-number-active>1</button>
    <span data-page-ellipsis>...</span>
  </span>

  <button data-page="next">Next</button>
</nav>

data-posts-page-numbers (container), data-page-number, data-page-number-active, data-page-ellipsis (optional).

data-posts-page-numbers

Container for the numbered buttons.

data-page-number

The template for the non-current page button.

data-page-number-active

Template for the current page's button. If omitted the data-page-number template gets the active class.

data-page-ellipsis

Template for the gap indicator. If omitted a plain … span is generated.

Navigating without JavaScript

Most users will have JavaScript enabled. You should still support the users that don't by adding href links to your pagination.

<nav data-posts-pagination>
  {{#if (hasPrevPage)}}
    <a href="{{paginationPrevUrl}}" data-page="prev">Previous</a>
  {{else}}
    <span class="disabled">Previous</span>
  {{/if}}

  <span>Page {{paginationCurrentPage}} of {{paginationTotalPages}}</span>

  {{#if (hasNextPage)}}
    <a href="{{paginationNextUrl}}" data-page="next">Next</a>
  {{else}}
    <span class="disabled">Next</span>
  {{/if}}
</nav>

The href attributes ensure these work without JavaScript

Page Number Links

Generate links for each paginated page. The {{paginationPages}} helper returns an array with number, url, and isCurrent for each page.

<nav data-posts-pagination>
  {{#each (paginationPages)}}
    {{#if isCurrent}}
      <span class="current">{{number}}</span>
    {{else}}
      <a href="{{url}}" data-page="{{number}}">{{number}}</a>
    {{/if}}
  {{/each}}
</nav>

The href attributes ensure these work without JavaScript

A Complete Example

Let's put it all together. Here's a block that handles pagination with and without JavaScript. It's the official gold-stamp recommendation.

<div data-posts="blog" data-per-page="6">
  <div data-posts-list>
    {{#each (posts "blog")}}
      <article data-post>
        <a href="{{url}}" data-field-href="url" data-field="title">{{title}}</a>
      </article>
    {{/each}}
  </div>

  <nav data-posts-pagination>
    {{#if (hasPrevPage)}}
      <a href="{{paginationPrevUrl}}" data-page="prev">Previous</a>
    {{else}}
      <span class="disabled">Previous</span>
    {{/if}}

    {{#each (paginationPages)}}
      {{#if isCurrent}}
        <span class="current">{{number}}</span>
      {{else}}
        <a href="{{url}}">{{number}}</a>
      {{/if}}
    {{/each}}

    {{#if (hasNextPage)}}
      <a href="{{paginationNextUrl}}" data-page="next">Next</a>
    {{else}}
      <span class="disabled">Next</span>
    {{/if}}
  </nav>
</div>
Thanks — your message has been sent.