Reading time, and other numbers worth being honest about
A reading-time estimate is a guess presented as a fact. That is fine, as long as the guess is not wrong in a way the reader will notice.
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
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.
/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.
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.
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.
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.
A reading-time estimate is a guess presented as a fact. That is fine, as long as the guess is not wrong in a way the reader will notice.
A decision that only exists in a conversation gets re-argued every few months. Writing it down is cheaper than remembering it.
What this site is for, what is still missing from it, and why we would rather publish it in that state than wait.
A consent checkbox that starts checked is not consent. A few small decisions separate a form that asks from one that assumes.
We would rather answer a direct question than write a follow-up post nobody asked for.