Skip to content

Commit

Permalink
Merge pull request #1091 from timlrx/tag-paginated
Browse files Browse the repository at this point in the history
static paginated tag pages
  • Loading branch information
timlrx authored Jan 12, 2025
2 parents 3e1d43d + c39796e commit 94e87c9
Show file tree
Hide file tree
Showing 8 changed files with 1,830 additions and 1,672 deletions.
18 changes: 5 additions & 13 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
import ListLayout from '@/layouts/ListLayoutWithTags'
import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'
import { allBlogs } from 'contentlayer/generated'
import { genPageMetadata } from 'app/seo'
import { notFound } from 'next/navigation'
import ListLayout from '@/layouts/ListLayoutWithTags'

const POSTS_PER_PAGE = 5

export const metadata = genPageMetadata({ title: 'Blog' })

export default async function BlogPage(props: { searchParams: Promise<{ page: string }> }) {
const posts = allCoreContent(sortPosts(allBlogs))
const searchParams = await props.searchParams
const pageNumber = parseInt(searchParams.page || '1')
const initialDisplayPosts = posts.slice(
POSTS_PER_PAGE * (pageNumber - 1),
POSTS_PER_PAGE * pageNumber
)
if (initialDisplayPosts.length === 0) {
return notFound()
}

const pageNumber = 1
const totalPages = Math.ceil(posts.length / POSTS_PER_PAGE)
const initialDisplayPosts = posts.slice(0, POSTS_PER_PAGE * pageNumber)
const pagination = {
currentPage: pageNumber,
totalPages: Math.ceil(posts.length / POSTS_PER_PAGE),
totalPages: totalPages,
}

return (
Expand Down
42 changes: 42 additions & 0 deletions app/blog/page/[page]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import ListLayout from '@/layouts/ListLayoutWithTags'
import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'
import { allBlogs } from 'contentlayer/generated'
import { notFound } from 'next/navigation'

const POSTS_PER_PAGE = 5

export const generateStaticParams = async () => {
const totalPages = Math.ceil(allBlogs.length / POSTS_PER_PAGE)
const paths = Array.from({ length: totalPages }, (_, i) => ({ page: (i + 1).toString() }))

return paths
}

export default async function Page(props: { params: Promise<{ page: string }> }) {
const params = await props.params
const posts = allCoreContent(sortPosts(allBlogs))
const pageNumber = parseInt(params.page as string)
const totalPages = Math.ceil(posts.length / POSTS_PER_PAGE)

// Return 404 for invalid page numbers or empty pages
if (pageNumber <= 0 || pageNumber > totalPages || isNaN(pageNumber)) {
return notFound()
}
const initialDisplayPosts = posts.slice(
POSTS_PER_PAGE * (pageNumber - 1),
POSTS_PER_PAGE * pageNumber
)
const pagination = {
currentPage: pageNumber,
totalPages: totalPages,
}

return (
<ListLayout
posts={posts}
initialDisplayPosts={initialDisplayPosts}
pagination={pagination}
title="All Posts"
/>
)
}
27 changes: 6 additions & 21 deletions app/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { allBlogs } from 'contentlayer/generated'
import tagData from 'app/tag-data.json'
import { genPageMetadata } from 'app/seo'
import { Metadata } from 'next'
import { notFound } from 'next/navigation'

const POSTS_PER_PAGE = 5

Expand All @@ -30,37 +29,23 @@ export async function generateMetadata(props: {
export const generateStaticParams = async () => {
const tagCounts = tagData as Record<string, number>
const tagKeys = Object.keys(tagCounts)
const paths = tagKeys.map((tag) => ({
return tagKeys.map((tag) => ({
tag: encodeURI(tag),
}))
return paths
}

export default async function TagPage(props: {
params: Promise<{ tag: string }>
searchParams: Promise<{ page: string }>
}) {
export default async function TagPage(props: { params: Promise<{ tag: string }> }) {
const params = await props.params
const tag = decodeURI(params.tag)
const searchParams = await props.searchParams
const pageNumber = parseInt(searchParams.page || '1')
// Capitalize first letter and convert space to dash
const title = tag[0].toUpperCase() + tag.split(' ').join('-').slice(1)
const filteredPosts = allCoreContent(
sortPosts(allBlogs.filter((post) => post.tags && post.tags.map((t) => slug(t)).includes(tag)))
)
const initialDisplayPosts = filteredPosts.slice(
POSTS_PER_PAGE * (pageNumber - 1),
POSTS_PER_PAGE * pageNumber
)

if (initialDisplayPosts.length === 0) {
return notFound()
}

const totalPages = Math.ceil(filteredPosts.length / POSTS_PER_PAGE)
const initialDisplayPosts = filteredPosts.slice(0, POSTS_PER_PAGE)
const pagination = {
currentPage: pageNumber,
totalPages: Math.ceil(filteredPosts.length / POSTS_PER_PAGE),
currentPage: 1,
totalPages: totalPages,
}

return (
Expand Down
53 changes: 53 additions & 0 deletions app/tags/[tag]/page/[page]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { slug } from 'github-slugger'
import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'
import ListLayout from '@/layouts/ListLayoutWithTags'
import { allBlogs } from 'contentlayer/generated'
import tagData from 'app/tag-data.json'
import { notFound } from 'next/navigation'

const POSTS_PER_PAGE = 5

export const generateStaticParams = async () => {
const tagCounts = tagData as Record<string, number>
return Object.keys(tagCounts).flatMap((tag) => {
const postCount = tagCounts[tag]
const totalPages = Math.ceil(postCount / POSTS_PER_PAGE)
return Array.from({ length: totalPages - 1 }, (_, i) => ({
tag: encodeURI(tag),
page: (i + 2).toString(),
}))
})
}

export default async function TagPage(props: { params: Promise<{ tag: string; page: string }> }) {
const params = await props.params
const tag = decodeURI(params.tag)
const title = tag[0].toUpperCase() + tag.split(' ').join('-').slice(1)
const pageNumber = parseInt(params.page)
const filteredPosts = allCoreContent(
sortPosts(allBlogs.filter((post) => post.tags && post.tags.map((t) => slug(t)).includes(tag)))
)
const totalPages = Math.ceil(filteredPosts.length / POSTS_PER_PAGE)

// Return 404 for invalid page numbers or empty pages
if (pageNumber <= 0 || pageNumber > totalPages || isNaN(pageNumber)) {
return notFound()
}
const initialDisplayPosts = filteredPosts.slice(
POSTS_PER_PAGE * (pageNumber - 1),
POSTS_PER_PAGE * pageNumber
)
const pagination = {
currentPage: pageNumber,
totalPages: totalPages,
}

return (
<ListLayout
posts={filteredPosts}
initialDisplayPosts={initialDisplayPosts}
pagination={pagination}
title={title}
/>
)
}
6 changes: 5 additions & 1 deletion layouts/ListLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ interface ListLayoutProps {

function Pagination({ totalPages, currentPage }: PaginationProps) {
const pathname = usePathname()
const basePath = pathname.split('/')[1]
const segments = pathname.split('/')
const lastSegment = segments[segments.length - 1]
const basePath = pathname
.replace(/^\//, '') // Remove leading slash
.replace(/\/page\/\d+$/, '') // Remove any trailing /page
const prevPage = currentPage - 1 > 0
const nextPage = currentPage + 1 <= totalPages

Expand Down
11 changes: 9 additions & 2 deletions layouts/ListLayoutWithTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ interface ListLayoutProps {

function Pagination({ totalPages, currentPage }: PaginationProps) {
const pathname = usePathname()
const segments = pathname.split('/')
const lastSegment = segments[segments.length - 1]
const basePath = pathname
.replace(/^\//, '') // Remove leading slash
.replace(/\/page\/\d+$/, '') // Remove any trailing /page
console.log(pathname)
console.log(basePath)
const prevPage = currentPage - 1 > 0
const nextPage = currentPage + 1 <= totalPages

Expand All @@ -36,7 +43,7 @@ function Pagination({ totalPages, currentPage }: PaginationProps) {
)}
{prevPage && (
<Link
href={currentPage - 1 === 1 ? `${pathname}/` : `${pathname}/?page=${currentPage - 1}`}
href={currentPage - 1 === 1 ? `/${basePath}/` : `/${basePath}/page/${currentPage - 1}`}
rel="prev"
>
Previous
Expand All @@ -51,7 +58,7 @@ function Pagination({ totalPages, currentPage }: PaginationProps) {
</button>
)}
{nextPage && (
<Link href={`${pathname}/?page=${currentPage + 1}`} rel="next">
<Link href={`/${basePath}/page/${currentPage + 1}`} rel="next">
Next
</Link>
)}
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"dependencies": {
"@headlessui/react": "2.2.0",
"@next/bundle-analyzer": "15.0.2",
"@next/bundle-analyzer": "15.1.4",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"autoprefixer": "^10.4.13",
Expand All @@ -24,7 +24,7 @@
"gray-matter": "^4.0.2",
"hast-util-from-html-isomorphic": "^2.0.0",
"image-size": "1.0.0",
"next": "15.0.2",
"next": "15.1.4",
"next-contentlayer2": "0.5.3",
"next-themes": "^0.3.0",
"pliny": "0.4.0",
Expand Down Expand Up @@ -56,7 +56,7 @@
"@typescript-eslint/parser": "^8.12.0",
"cross-env": "^7.0.3",
"eslint": "^9.14.0",
"eslint-config-next": "15.0.2",
"eslint-config-next": "15.1.4",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.0",
"globals": "^15.12.0",
Expand Down
Loading

0 comments on commit 94e87c9

Please sign in to comment.