CSS-First UI Patterns · Page structure · 17

SEO-Friendly Pagination

Crawlers don't scroll and don't click “Load more” buttons. If your list only grows through infinite scroll, everything after the first batch may never be found. Pagination with real links fixes that — and a “show more” link can look just as modern.

JavaScript
0 KB
Built with
Real URLs, <nav>, aria-current, server-side paging
Browsers
All — it's links
Updated
Is infinite scroll bad for SEO?

It can be. Search engine crawlers don't scroll down or press buttons, so items that only appear after scrolling or clicking “Load more” may never be crawled. Give every page of a list its own URL — ?page=2, ?page=3 — and link to it with a normal <a href>. A “Load more” control should be such a link too.

Live demo: real pagination

All 18 patterns of this library, six per page. This is not a simulation: each page is generated on the server and has its own URL — look at your address bar after clicking. “Show the next 6” and the page numbers are ordinary links.

Live demo 0 KB JavaScript Page 1 of 3 · real URLs

How SEO-friendly pagination works

  1. One URL per page

    Page 1 is the clean URL, page 2 is ?page=2, and so on. Each one can be crawled, linked, bookmarked and shared.

  2. Links, not buttons

    Every control is an <a href>. Crawlers follow links; they don't press buttons that run scripts.

  3. “Show more” is a link too

    It points to the next page. Without JavaScript it simply opens it. With JavaScript, you can catch the click and append the next items in place — the link stays for crawlers.

  4. An ordered list that keeps counting

    <ol start="7"> on page 2 numbers the items 7 to 12, so the position stays clear for every visitor and screen reader.

  5. The current page is marked

    aria-current="page" on the active number, plus labels like “Page 2, current page” for screen readers.

  6. No dead ends

    “Previous” on page 1 and “Next” on the last page aren't links. This demo shows page 1 for an unknown page number; on a real site, a page beyond the end should return a 404, so search engines don't index empty pages.

Copy the HTML and CSS

This is the HTML the server generated for page 1 — exactly what you see in the demo. Your CMS or shop system produces the same structure for each page. All class names start with ui-.

pagination-page-1.html
<div class="ui-paged">
  <p class="ui-paged__status">Page 1 of 3</p>
  <ol class="ui-paged__list" start="1">
    <li><a href="/lab/css-first-ui/dropdown-menu-without-javascript/">Multi-Level Dropdown Menu Without JavaScript</a></li>
    <li><a href="/lab/css-first-ui/css-mega-menu/">CSS-Only Mega Menu</a></li>
    <li><a href="/lab/css-first-ui/hamburger-menu-without-javascript/">Hamburger Menu Without JavaScript</a></li>
    <li><a href="/lab/css-first-ui/breadcrumb-navigation-schema/">Breadcrumb Navigation with Schema Markup</a></li>
    <li><a href="/lab/css-first-ui/accordion-without-javascript/">Accordion Without JavaScript</a></li>
    <li><a href="/lab/css-first-ui/tabs-without-javascript/">Tabs Without JavaScript</a></li>
  </ol>
  <a class="ui-paged__more" href="/lab/css-first-ui/pagination-seo/?page=2#demo">Show the next 6 patterns</a>
  <nav class="ui-pager" aria-label="Pattern list pages">
    <ul>
      <li><span aria-hidden="true">‹ Previous</span></li>
      <li><a href="/lab/css-first-ui/pagination-seo/#demo" aria-current="page" aria-label="Page 1, current page">1</a></li>
      <li><a href="/lab/css-first-ui/pagination-seo/?page=2#demo" aria-label="Page 2">2</a></li>
      <li><a href="/lab/css-first-ui/pagination-seo/?page=3#demo" aria-label="Page 3">3</a></li>
      <li><a href="/lab/css-first-ui/pagination-seo/?page=2#demo" rel="next">Next ›</a></li>
    </ul>
  </nav>
</div>
pagination.css
/* SEO-friendly pagination — no JavaScript.
   Every page is a real URL, every control a real link.
   "Show more" is a link to the next page, so crawlers can follow it. */
.ui-paged {
  --ui-text: #1b1f3b;
  --ui-muted: #5d6280;
  --ui-accent: #4f46e5;
  font: 500 0.9375rem/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--ui-text);
}
.ui-paged__status {
  margin: 0 0 0.6rem;
  color: rgb(255 255 255 / 0.75);
  font-size: 0.875rem;
}
.ui-paged__list {
  display: grid;
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}
.ui-paged__list a {
  display: block;
  padding: 0.8rem 1rem;
  border-radius: 10px;
  background: #fff;
  color: var(--ui-text);
  text-decoration: none;
}
.ui-paged__list a:hover {
  color: var(--ui-accent);
  text-decoration: underline;
}
.ui-paged a:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* "Show more": a link that looks like a button */
.ui-paged__more {
  display: block;
  margin: 0 0 1.25rem;
  padding: 0.75rem 1rem;
  border-radius: 10px;
  background: var(--ui-accent);
  color: #fff;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}
.ui-paged__end {
  margin: 0 0 1.25rem;
  color: rgb(255 255 255 / 0.75);
  text-align: center;
}

/* Page numbers */
.ui-pager ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.ui-pager a,
.ui-pager span {
  display: block;
  min-width: 2.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  text-align: center;
  text-decoration: none;
}
.ui-pager a {
  background: rgb(255 255 255 / 0.12);
  color: #fff;
}
.ui-pager a:hover {
  background: rgb(255 255 255 / 0.25);
}
.ui-pager a[aria-current="page"] {
  background: #fff;
  color: var(--ui-text);
  font-weight: 700;
}
.ui-pager span {
  color: rgb(255 255 255 / 0.35);
}

The server side, in a few lines

pagination.php (example)
<?php
// Minimal server-side pagination (PHP). The same idea works in any CMS.
$perPage = 6;
$page    = max(1, (int) ($_GET['page'] ?? 1));
$total   = count($items);
$pages   = (int) ceil($total / $perPage);
if ($page > $pages) { http_response_code(404); exit; }   // no empty pages

$visible = array_slice($items, ($page - 1) * $perPage, $perPage);

// In the <head>: every page canonicalises to ITSELF, not to page 1
$self = $page === 1 ? '/products/' : '/products/?page=' . $page;
echo '<link rel="canonical" href="https://www.example.com' . $self . '">';
// And give each page its own title: "Products – page 2 of 5"

Pagination rules for Google and Baidu

Every paginated page canonicalises to itself. Page 2 should not point its canonical tag to page 1 — that tells search engines to ignore page 2 and, with it, the links to the items listed there.

Don't noindex the pages of a real listing. Products that are only linked from page 4 need page 4 to be crawled. (This lab page is the exception: its ?page=2 views are copies of an article, so they carry noindex, follow — the links are still followed.)

rel="next" and rel="prev" are optional. Google stopped using them as an indexing signal in 2019. They do no harm, and the demo keeps them on its links.

Give each page a distinct title. “Industrial pumps – page 2 of 5” is clearer in search results than five identical titles.

Baidu needs plain links even more. Baidu often reads only the HTML it downloads and crawls far fewer pages of most foreign sites than Google. Lists that grow only through scrolling or scripted buttons are, for Baidu, often just the first page.

Pagination, infinite scroll and “Load more” compared

FeaturePagination“Show more” linkInfinite scroll
Crawlers reach every itemYesYesOften not
Works without JavaScriptYesYesNo
Shareable positionYesYesRarely
Footer reachableYesYesKeeps moving away
Keyboard and screen readersSimpleSimpleHard to do well
Feels seamlessPage loadsWith a little JSYes
The best of both

Build the paginated version first, with real links. Then, if you want, add a small script that turns “Show more” into in-place loading and updates the URL. Visitors get the seamless feel, crawlers keep the links, and the list still works when the script fails.

Questions about pagination and SEO

Is infinite scroll bad for SEO?

When it is the only way to reach items, yes: crawlers don't scroll, so later items may never be found. Combine it with paginated URLs that crawlers can follow.

Should page 2 have a canonical tag to page 1?

No. Each paginated page should point its canonical to itself. Pointing to page 1 hides the items on later pages from search engines.

Does Google still use rel="next" and rel="prev"?

Not since 2019. They are harmless, and other tools may still read them, but plain links between pages are what matters.

Is a “Load more” button bad for SEO?

A <button> that loads items with JavaScript gives crawlers nothing to follow. Make it an <a href> to the next page — it can still load items in place for visitors.

How many items should one page show?

Enough to be useful, few enough to load fast — often 12 to 48 in shops. Fewer pages mean every item is fewer clicks from the first page.

This FAQ is built without JavaScript — see the accordion pattern.

Every product reachable for Baidu

Can Baidu reach all of your products?

We audit Chinese shop and catalogue sites for items crawlers can't reach — infinite scroll, scripted filters, missing pagination — and fix the structure. Then we run Baidu SEO and marketplace placements for Western companies.

CSS-First UI Patterns are written and maintained by Jademond Digital, a Chinese digital marketing agency founded in Guangzhou in 2012. We build and audit websites for Baidu, Google and AI search — which is why we care what works without JavaScript. About us →