Pagination is one of the most overlooked SEO challenges for large websites.
We built an SEO-friendly pagination solution on our Strapi-powered blog at Deploi.ca/blog, which has:
This pagination system keeps deep content crawlable while providing a clean and intuitive user experience.
Our pagination ensures that any listing page is reachable in just two clicks from the blog landing page while keeping the UI uncluttered.
We use anchor pages and dynamic pagination updates instead of showing dozens of individual page links.
When a user or search engine lands on a paginated page, they see a progressive pagination structure that adapts dynamically.
On the Deploi blog, here’s the pagination you see on the first page:
Users can navigate to the next page by clicking the second links. If we expand the ellipsis, here are the rendered links available to both users and search engines:
Google has access to all the pagination links within the current range (1-9), as well as anchor pages, which we defined in increments of ten.
In this example, let’s say Google follows anchor page 30, the pagination will dynamically update to look like this:
As you can see in the screenshot above, both users and Google have access to the previous page, the next page, the first page and the last page.
But that’s not all, let’s reveal the first ellipsis by the left:
Now we can easily access all the previous anchor pages, which are pages 20 and 10.
By expanding the second ellipsis, we see that Google can crawl all the pages within the current range (30-39) and also access all the remaining anchor pages (40, 50, 60, 70, and 80):
If we click on page 37 for example, which is in the current range, pagination will update accordingly:
The shows that Google could reach the deepest listing page with only two link follows from the blog landng page and access any of the 4100+ blog posts with just three follows.
Our smart pagination system dynamically reveals relevant pages while keeping the UI intuitive. Let’s break down the key components of our React-based Pagination component.
Our Pagination component uses the following props:
pageCount
– Total number of pages. gotoPage
– Callback function to navigate to a different page. searchString
– Optional search query parameter. startUrl
– Base URL for page navigation. pageInde
– Current active page.We use React’s useState
and useEffect
hooks to handle page changes and dynamic visibility:
1const [activePage, setActivePage] = useState(pageIndex);
2const [showLeftPages, setShowLeftPages] = useState(false);
3const [showRightPages, setShowRightPages] = useState(false);
4
5useEffect(() => {
6 setActivePage(pageIndex);
7 setShowLeftPages(false);
8 setShowRightPages(false);
9}, [pageIndex]);
activePage
: Keeps track of the currently selected page. showLeftPages
/ showRightPages
: Controls visibility of hidden anchor pages. useEffect
resets visibility states when the page index updates.To create an SEO-friendly pagination structure, we generate page numbers dynamically:
This ensures:
To avoid clutter, we only show pages based on specific conditions:
1const isVisible = (page) => {
2 if (page === 1 || page === pageCount) return true;
3 if (page === activePage - 1 || page === activePage || page === activePage + 1) return true;
4 if (showLeftPages && page < activePage) return true;
5 if (showRightPages && page > activePage) return true;
6 return false;
7};
When a user selects a page, we update the state and trigger the gotoPage
function:
1const handlePageChange = (page) => {
2 if (page >= 1 && page <= pageCount) {
3 setShowLeftPages(false);
4 setShowRightPages(false);
5 gotoPage && gotoPage(page);
6 }
7};
For ellipsis clicks:
1const handleLeftEllipsisClick = () => {
2 setShowLeftPages(true);
3 setShowRightPages(false);
4};
5
6const handleRightEllipsisClick = () => {
7 setShowRightPages(true);
8 setShowLeftPages(false);
9};
Each page number is displayed dynamically:
1const renderPageNumbers = () => {
2 const pages = generatePageNumbers();
3 let lastRenderedPage = 0;
4
5 return (
6 <ul className="flex items-center justify-center gap-2 flex-wrap">
7 {pages.map((page, index) => {
8 const shouldShowLeftEllipsis =
9 !showLeftPages &&
10 ((page > 2 && page <= activePage && lastRenderedPage === 1) ||
11 (activePage >= 3 && activePage <= 9 && page === 2));
12
13 const shouldShowRightEllipsis =
14 !showRightPages &&
15 page < pageCount &&
16 page >= activePage &&
17 pages[index + 1] === pageCount;
18
19 const isPageVisible = isVisible(page);
20 lastRenderedPage = page;
21
22 return (
23 <React.Fragment key={page}>
24 {shouldShowLeftEllipsis && (
25 <li
26 onClick={handleLeftEllipsisClick}
27 className="cursor-pointer text-primary-100 w-[40px] h-[40px] flex items-center justify-center hover:bg-primary-50 transition-colors rounded-full border border-primary-100"
28 >
29 ...
30 </li>
31 )}
32 <li
33 className={`${!isPageVisible ? "hidden" : ""} cursor-pointer border border-primary-100 text-base font-semibold text-primary-100 rounded-full w-[40px] h-[40px] flex items-center justify-center hover:bg-primary-50 transition-colors ${page === activePage ? "bg-primary-100 text-white" : ""}`}
34 >
35 <a
36 href={
37 searchString
38 ? `${startUrl}?p=${page}&q=${searchString}`
39 : `${startUrl}?p=${page}`
40 }
41 className="w-full h-full flex items-center justify-center"
42 >
43 {page < 10 ? `0${page}` : page}
44 </a>
45 </li>
46 {shouldShowRightEllipsis && (
47 <li
48 onClick={handleRightEllipsisClick}
49 className="cursor-pointer text-primary-100 w-[40px] h-[40px] flex items-center justify-center hover:bg-primary-50 transition-colors rounded-full border border-primary-100"
50 >
51 ...
52 </li>
53 )}
54 </React.Fragment>
55 );
56 })}
57 </ul>
58 );
59};
...
) is displayed before hidden pages. ...
) is displayed after the current range. Finally, the component renders the pagination UI:
1return (
2 <div className="flex flex-col md:flex-row justify-center items-center">
3 {renderPageNumbers()}
4 </div>
5);
This ensures our pagination works across different screen sizes.
By structuring pagination this way, we ensure:
This approach solves pagination SEO issues while maintaining a great user experience.
Want to implement this on your site? Get in touch with us at Deploi.ca.
Martin is the Director of Digital Strategy & Growth at Deploi. With 25+ years of building for the web, he helps brands grow through digital transformations and smart marketing. Outside of work, he enjoys hiking with his wife and kids and channeling his inner child on the field or court.