feat: enhance blog pagination and related posts functionality

- Added pagination size and related posts size configurations to the website config.
- Updated blog components to utilize the new pagination settings.
- Introduced a new feature to display related posts on individual blog pages, improving user engagement.
- Added new translations for "More Posts" in English and Chinese.
This commit is contained in:
javayhu 2025-04-01 18:17:36 +08:00
parent b1a93a6e49
commit dd12afcce0
9 changed files with 65 additions and 24 deletions

View File

@ -178,7 +178,8 @@
"readTime": " min read",
"all": "All",
"noPostsFound": "No posts found",
"allPosts": "All Posts"
"allPosts": "All Posts",
"morePosts": "More Posts"
},
"DocsPage": {
"toc": "Table of Contents",

View File

@ -174,7 +174,8 @@
"readTime": " 分钟阅读",
"all": "全部",
"noPostsFound": "没有找到文章",
"allPosts": "全部文章"
"allPosts": "全部文章",
"morePosts": "更多文章"
},
"DocsPage": {
"toc": "目录",

View File

@ -1,7 +1,7 @@
import BlogGrid from '@/components/blog/blog-grid';
import EmptyGrid from '@/components/shared/empty-grid';
import CustomPagination from '@/components/shared/pagination';
import { POSTS_PER_PAGE } from '@/constants';
import { websiteConfig } from '@/config';
import { constructMetadata } from '@/lib/metadata';
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
import { NextPageProps } from '@/types/next-page-props';
@ -48,13 +48,14 @@ export default async function BlogCategoryPage({
params,
searchParams,
}: NextPageProps) {
const paginationSize = websiteConfig.blog.paginationSize;
const resolvedParams = await params;
const { slug, locale } = resolvedParams;
const resolvedSearchParams = await searchParams;
const { page } = (resolvedSearchParams as { [key: string]: string }) || {};
const currentPage = page ? Number(page) : 1;
const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
const endIndex = startIndex + POSTS_PER_PAGE;
const startIndex = (currentPage - 1) * paginationSize;
const endIndex = startIndex + paginationSize;
// Find category with matching slug and locale
const category = allCategories.find(
@ -81,7 +82,7 @@ export default async function BlogCategoryPage({
// Paginate posts
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
const totalCount = filteredPosts.length;
const totalPages = Math.ceil(totalCount / POSTS_PER_PAGE);
const totalPages = Math.ceil(totalCount / paginationSize);
// console.log("BlogCategoryPage, totalCount", totalCount, ", totalPages", totalPages);

View File

@ -1,7 +1,7 @@
import BlogGrid from '@/components/blog/blog-grid';
import EmptyGrid from '@/components/shared/empty-grid';
import CustomPagination from '@/components/shared/pagination';
import { POSTS_PER_PAGE } from '@/constants';
import { websiteConfig } from '@/config';
import { constructMetadata } from '@/lib/metadata';
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
import { NextPageProps } from '@/types/next-page-props';
@ -29,13 +29,14 @@ export default async function BlogPage({
params,
searchParams,
}: NextPageProps) {
const paginationSize = websiteConfig.blog.paginationSize;
const resolvedParams = await params;
const { locale } = resolvedParams;
const resolvedSearchParams = await searchParams;
const { page } = (resolvedSearchParams as { [key: string]: string }) || {};
const currentPage = page ? Number(page) : 1;
const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
const endIndex = startIndex + POSTS_PER_PAGE;
const startIndex = (currentPage - 1) * paginationSize;
const endIndex = startIndex + paginationSize;
// Filter posts by locale
const localePosts = allPosts.filter(
@ -56,7 +57,7 @@ export default async function BlogPage({
// Paginate posts
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
const totalCount = filteredPosts.length;
const totalPages = Math.ceil(totalCount / POSTS_PER_PAGE);
const totalPages = Math.ceil(totalCount / paginationSize);
// console.log("BlogPage, totalCount", totalCount, ", totalPages", totalPages,);

View File

@ -1,20 +1,22 @@
import AllPostsButton from '@/components/blog/all-posts-button';
import BlogGrid from '@/components/blog/blog-grid';
import { BlogToc } from '@/components/blog/blog-toc';
import { NewsletterCard } from '@/components/newsletter/newsletter-card';
import { CustomMDXContent } from '@/components/shared/custom-mdx-content';
import { websiteConfig } from '@/config';
import { LocaleLink } from '@/i18n/navigation';
import { getTableOfContents } from '@/lib/blog/toc';
import { constructMetadata } from '@/lib/metadata';
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
import { getLocaleDate } from '@/lib/utils';
import type { NextPageProps } from '@/types/next-page-props';
import { allPosts } from 'content-collections';
import { CalendarIcon, ClockIcon } from 'lucide-react';
import { allPosts, Post } from 'content-collections';
import { CalendarIcon, ClockIcon, FileTextIcon } from 'lucide-react';
import type { Metadata } from 'next';
import { Locale } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import Image from 'next/image';
import { notFound } from 'next/navigation';
import { CustomMDXContent } from '@/components/shared/custom-mdx-content';
import '@/styles/mdx.css';
@ -24,15 +26,10 @@ import '@/styles/mdx.css';
* @returns The blog post
*
* How it works:
* 1. /[locale]/blog/first-post:
* /[locale]/blog/first-post:
* params.slug = ["first-post"]
* slug becomes "first-post" after join('/')
* Matches post where slugAsParams === "first-post" AND locale === params.locale
*
* 2. /[locale]/blog/2023/year-review:
* params.slug = ["2023", "year-review"]
* slug becomes "2023/year-review" after join('/')
* Matches post where slugAsParams === "2023/year-review" AND locale === params.locale
*/
async function getBlogPostFromParams(props: NextPageProps) {
const params = await props.params;
@ -65,6 +62,20 @@ async function getBlogPostFromParams(props: NextPageProps) {
return post;
}
/**
* get related posts, random pick from all posts with same locale, different slug,
* max size is websiteConfig.blog.relatedPostsSize
*/
async function getRelatedPosts(post: Post) {
const relatedPosts = allPosts
.filter((p) => p.locale === post.locale)
.filter((p) => p.slugAsParams !== post.slugAsParams)
.sort(() => Math.random() - 0.5)
.slice(0, websiteConfig.blog.relatedPostsSize);
return relatedPosts;
}
export async function generateMetadata({
params,
}: {
@ -103,6 +114,9 @@ export default async function BlogPostPage(props: NextPageProps) {
const toc = await getTableOfContents(post.content);
const t = await getTranslations('BlogPage');
// get related posts
const relatedPosts = await getRelatedPosts(post);
return (
<div className="flex flex-col gap-8">
{/* content section */}
@ -219,8 +233,24 @@ export default async function BlogPostPage(props: NextPageProps) {
</div>
</div>
{/* Footer section shows related posts */}
{relatedPosts && relatedPosts.length > 0 && (
<div className="flex flex-col gap-8 mt-8">
<div className="flex items-center gap-2">
<FileTextIcon className="size-4 text-muted-foreground" />
<h2 className="text-lg tracking-wider font-semibold text-gradient_indigo-purple">
{t('morePosts')}
</h2>
</div>
<BlogGrid posts={relatedPosts} />
</div>
)}
{/* newsletter */}
<NewsletterCard />
<div className="flex items-center justify-start my-8">
<NewsletterCard />
</div>
</div>
);
}

View File

@ -1,5 +1,5 @@
import BlogCard, { BlogCardSkeleton } from '@/components/blog/blog-card';
import { POSTS_PER_PAGE } from '@/constants';
import { websiteConfig } from '@/config';
import { Post } from 'content-collections';
interface BlogGridProps {
@ -22,7 +22,7 @@ export default function BlogGrid({ posts }: BlogGridProps) {
}
export function BlogGridSkeleton({
count = POSTS_PER_PAGE,
count = websiteConfig.blog.paginationSize,
}: { count?: number }) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">

View File

@ -58,6 +58,10 @@ export const websiteConfig: WebsiteConfig = {
from: 'support@mksaas.com',
to: 'support@mksaas.com',
},
blog: {
paginationSize: 6,
relatedPostsSize: 3,
},
social: {
github: 'https://github.com/MkSaaSHQ',
twitter: 'https://x.com/MkSaaSHQ',

View File

@ -1 +0,0 @@
export const POSTS_PER_PAGE = 6;

View File

@ -11,7 +11,11 @@ export type WebsiteConfig = {
mail: {
from?: string;
to?: string;
}
};
blog: {
paginationSize: number;
relatedPostsSize: number;
};
social: {
twitter?: string;
github?: string;