The Wix Blog has built-in pagination with links. Links are required for good SEO.
I have reproduced this pagination element that uses unique URLs and creates my own implementation. (Lots of code )
For that, I use a wix-router on the server part and I generating custom pagination by Repeater on the front-end part.
So pagination is a difficult component. I share my solution I hope it will be helpful (interesting) to take a look at how it works.
DEMO : https://alexanderz5.wixsite.com/pagination/custom-blog
backend/routers.js
Route logic for pagination
import wixData from 'wix-data';
import { ok, redirect, WixRouterSitemapEntry } from 'wix-router';
import urlJoin from 'url-join';
const hasContent = (val) => typeof val === 'string' && val.trim() !== '';
const isNumeric = (val) => hasContent(val) && /^[\d]+$/.test(val);
const parseNumber = (val) => ~~Math.abs(+val);
const getCategories = () => {
return wixData
.query('Blog/Categories')
.find()
.then((result) => result.items);
};
const getCategory = (label) => {
return wixData
.query('Blog/Categories')
.eq('label', label)
.limit(1)
.find()
.then((reslut) => reslut.items[0]);
};
const getPosts = async (pageSize, skipPages, categoryId = null) => {
let dataQuery = wixData.query('Blog/Posts');
if (hasContent(categoryId)) {
dataQuery = dataQuery.hasAll('categories', categoryId);
}
return dataQuery
.skip(skipPages)
.limit(pageSize)
.find();
};
const getParams = async (path) => {
const length = path.length;
const [one, two] = path.map((i) => i.toLowerCase());
if (length === 1) {
if (one === '') {
return {
page: 0,
label: '',
};
}
if (isNumeric(one)) {
return {
page: parseNumber(one),
label: '',
};
}
const category = await getCategory(one);
if (typeof category !== 'undefined') {
return {
page: 0,
categoryId: category._id,
label: category.label,
};
}
}
if (length === 2 && isNumeric(two)) {
const category = await getCategory(one);
if (typeof category !== 'undefined') {
return {
page: parseNumber(two),
categoryId: category._id,
label: category.label,
};
}
}
return { hasError: true };
};
/**
* @param {wix_router.WixRouterRequest} request
*/
export async function custom_blog_Router({ path, baseUrl, prefix }) {
const params = await getParams(path);
if (params.hasError) {
return redirect(urlJoin(baseUrl, prefix), '301');
}
const pageSize = 2;
const skip = (params.page === 0 ? 0 : params.page - 1) * pageSize;
const postsData = await getPosts(
pageSize,
skip,
params.categoryId,
);
return ok('custom-blog-page', {
pageSize,
posts: postsData.items,
currentPage: postsData.currentPage,
totalCount: postsData.totalCount,
totalPages: postsData.totalPages,
label: params.label,
});
}
/**
* @param {wix_router.WixRouterSitemapRequest} sitemapRequest
* @returns {Promise<wix_router.WixRouterSitemapEntry[]>}
*/
export async function custom_blog_SiteMap(sitemapRequest) {
const categories = await getCategories();
return categories.map((i) => {
const entry = new WixRouterSitemapEntry(i.label);
return Object.assign(entry, {
title: i.label,
pageName: i.label,
url: urlJoin('/', sitemapRequest.prefix, i.label),
});
});
}
Custom Blog Page (code):
Route page with repeater and custom pagination.
import { getRouterData } from 'wix-window';
import { prefix } from 'wix-location';
import urlJoin from 'url-join';
import { paginate } from 'public/paginate';
$w.onReady(function () {
const {
posts,
currentPage,
totalCount,
pageSize,
label,
} = getRouterData();
const { data } = paginate({
totalCount,
currentPage,
maxPages: 4,
pageSize,
});
$w('#repeaterPosts').data = posts;
$w('#repeaterPosts').forEachItem(($item, itemData) => {
$item('#textTitle').text = itemData.title;
});
$w('#repeaterPagination').data = data;
$w('#repeaterPagination').forEachItem(($item, itemData) => {
$item('#button1').label = itemData.label;
if (itemData.isActive) {
$item('#button1').link = urlJoin('/', prefix, label, String(itemData.number));
} else {
$item('#button1').disable();
}
});
$w('#buttonLinkAll').link = urlJoin('/', prefix);
$w('#buttonLinkCss').link = urlJoin('/', prefix, 'css');
$w('#buttonLinkHtml').link = urlJoin('/', prefix, 'html');
$w('#buttonLinkJs').link = urlJoin('/', prefix, 'js');
});
public/paginate.js
Here is a logic for creating repeater items for custom pagination
export const paginate = ({
totalCount,
currentPage,
pageSize,
maxPages,
}) => {
const totalPages = Math.ceil(totalCount / pageSize);
let startPage = 1;
let endPage = totalPages;
if (currentPage > totalPages) {
currentPage = totalPages;
}
if (totalPages > maxPages) {
const maxPagesBeforeCurrentPage = Math.floor(maxPages / 2);
const maxPagesAfterCurrentPage = Math.ceil(maxPages / 2);
if (currentPage <= maxPagesBeforeCurrentPage) {
endPage = maxPages;
} else if (currentPage + maxPagesAfterCurrentPage >= totalPages) {
startPage = totalPages - maxPages + 1;
} else {
startPage = currentPage - maxPagesBeforeCurrentPage;
endPage = currentPage + maxPagesAfterCurrentPage;
}
}
const length = (endPage + 1) - startPage;
const data = Array.from({ length },
(_, index) => {
const number = startPage + index;
const id = String(number);
return {
_id: id,
label: id,
number,
isActive: number !== (currentPage + 1),
};
},
);
data.unshift({
_id: 'first',
label: '<<',
number: 1,
isActive: currentPage > 0,
}, {
_id: 'prev',
label: '<',
number: currentPage,
isActive: currentPage > 0,
});
data.push({
_id: 'next',
label: '>',
number: currentPage + 2,
isActive: (currentPage + 1) < totalPages,
}, {
_id: 'last',
label: '>>',
number: totalPages,
isActive: (currentPage + 1) < totalPages,
});
return {
data,
};
};