Skip to main content
Engineering

Pagination a crawler can follow

Page two of a blog should be a URL, not a state. On a static host that constraint turns out to simplify the code rather than complicate it.

2 min read

ST

Written by

Seratlas Team

Engineering and delivery

2 min read

We build and operate assessment software. We write here about the parts that are harder than they look.

The tempting version of blog pagination is a button that appends more cards. It is fewer files and no routing, and it is wrong for an index page, because the second page of posts then has no address. Nobody can link to it, and a crawler that does not execute the click never sees the posts on it.

Page one has no number

/blog/ is page one. There is no /blog/page/1/, and this matters more on a static host than elsewhere: two URLs with identical HTML need a redirect between them, and there is no server here to issue one. So the route that generates page numbers starts at two.

The consequence is a build-time constraint that is easy to hit while a blog is young. If every post fits on page one, the paginated route has nothing to build, and the export fails rather than quietly emitting a directory with no pages in it. That is the right failure — but it means the first few posts are load-bearing.

The window is pure

Which page numbers to render, given a current page and a total, is a function with no dependencies:

pageNumbers(1, 2);  // [1, 2]
pageNumbers(5, 12); // [1, "…", 4, 5, 6, "…", 12]

It has boundary cases at both ends — near the start, near the end, and when the total is small enough that no ellipsis is needed — and every one of them is a unit test rather than something to verify by clicking.

Disabled controls stay in the layout

On page one the "previous" control renders as inert text of the same size rather than disappearing. Removing it shifts the whole row sideways between pages, which makes the next target move under the cursor of someone clicking through.

Categories are URLs too, but only the real ones

Each category with posts in it is its own route. The ones without posts still appear in the tab strip, as disabled text, because the vocabulary is worth showing even when a slot is empty. Building a route for each would publish a set of near identical thin pages, each needing its own "nothing here yet" copy.

Tags

  • engineering
  • seo

Questions about any of this?

We would rather answer a direct question than write a follow-up post nobody asked for.