Part of the “Conversations with AI” series where I share interesting and useful discussions with AI assistants. These conversations are lightly edited for clarity.
The Problem
Now that I’ve posted to my blog over ten times, I ran into the scaling issue of only seeing the ten most recent posts on my homepage. When requesting the posts from my CMS, (WordPress), it responds with a maximum of ten posts by default. WordPress assumes that consumers will use pagination, meaning that page two of the blog will display the next ten posts. I hadn’t programmed that feature yet, so I bumped the per_page
limit to eleven and added a comment reminding myself to replace the bandaid with a scalable solution.
export default async function Blog() {
// TODO: add pagination and remove per_page query
const posts = await fetch(`${WP_REST_URI}/posts?per_page=11`)
.then((res) => res.json()
Last week I implemented pagination like I’ve done for plenty of other projects, by storing the page number in the URL search query and using that value to request the appropriate posts from WordPress. I added those “next page” and “last page” buttons we all know and love to the bottom of the page and was done. However, this time, I didn’t write any of the code. Features like this are so prolific in web development that AI can figure them out in seconds. And, if you code in an AI-enabled environment like Cursor, it can just do everything for you. Claude suggested all the code changes and all I had to do was apply and accept them.
So, I’m not even the engineer for my own site anymore! For features like this, my role is to delegate to the chatbots and review their code. This is a major productivity unlock that allows me to spend less time on backlog tasks and more on creative work. I used all the time it saved to build a component that displays AI chats for my blog. I’m going to start publishing “Conversations with AI” so that others can check out what I’m using it for. Here’s the conversation between the developer of this commit and me.
The Conversation
// ... existing imports ...
// Add these new imports at the top
import { Pagination } from '@mui/material'
import { useSearchParams } from 'next/navigation'
export default async function Blog() {
const searchParams = useSearchParams()
const page = Number(searchParams.get('page')) || 1
const postsPerPage = 10
const posts = await fetch(
`${WP_REST_URI}/posts?per_page=${postsPerPage}&page=${page}`
).then((res) => res.json())
// Get total posts count from WordPress API headers
const totalPosts = parseInt(res.headers.get('X-WP-Total') || '0')
const totalPages = Math.ceil(totalPosts / postsPerPage)
return (
<Stack p={{ sm: 5, xs: 2 }} gap={5} mx="auto" maxWidth="50em">
<Typography component="h1">Blog</Typography>
{posts ? (
<Stack gap={{ sm: 5, xs: 2 }}>
<PostList posts={posts} />
<Stack spacing={2} alignItems="center">
<Pagination
count={totalPages}
page={page}
onChange={(_, value) => {
window.location.href = `?page=${value}`
}}
/>
<Typography textAlign="right">
{`displaying ${posts.length} post${posts.length > 1 ? 's' : ''} of ${totalPosts}`}
</Typography>
</Stack>
</Stack>
) : (
// ... existing error component ...
)}
</Stack>
)
}
- Added pagination support using MUI’s
Pagination
component - Used
useSearchParams
to get the current page from the URL - Modified the fetch call to include pagination parameters
- Added logic to get the total posts count from the WordPress API headers
- Updated the display text to show correct post counts
- Added a pagination component that updates the URL when clicked
// Remove useSearchParams import
// Add this type for the page props
type BlogProps = {
searchParams: { page?: string }
}
export default async function Blog({ searchParams }: BlogProps) {
const page = Number(searchParams.page) || 1
const postsPerPage = 10
const res = await fetch(
`${WP_REST_URI}/posts?per_page=${postsPerPage}&page=${page}`
)
const posts = await res.json()
const totalPosts = parseInt(res.headers.get('X-WP-Total') || '0')
const totalPages = Math.ceil(totalPosts / postsPerPage)
return (
<Stack p={{ sm: 5, xs: 2 }} gap={5} mx="auto" maxWidth="50em">
// ... existing code ...
{posts ? (
<Stack gap={{ sm: 5, xs: 2 }}>
<PostList posts={posts} />
<PaginationControls
totalPages={totalPages}
currentPage={page}
totalPosts={totalPosts}
displayedPosts={posts.length}
/>
</Stack>
) : (
// ... existing error component ...
)}
</Stack>
)
}
'use client'
import { Stack, Pagination, Typography } from '@mui/material'
import { useRouter } from 'next/navigation'
type PaginationControlsProps = {
totalPages: number
currentPage: number
totalPosts: number
displayedPosts: number
}
export function PaginationControls({
totalPages,
currentPage,
totalPosts,
displayedPosts
}: PaginationControlsProps) {
const router = useRouter()
return (
<Stack spacing={2} alignItems="center">
<Pagination
count={totalPages}
page={currentPage}
onChange={(_, value) => {
router.push(`?page=${value}`)
}}
/>
<Typography textAlign="right">
{`displaying ${displayedPosts} post${displayedPosts > 1 ? 's' : ''} of ${totalPosts}`}
</Typography>
</Stack>
)
}
- It properly handles server-side props in Next.js
- Separates the client-side pagination controls into their own component
- Uses the Next.js router for client-side navigation
- Keeps the main page component as a server component
Key Points
- I didn’t even tell the chatbot what the feature was. I just referred them to my TODO comment and let it infer the task.
- Claude was aware it was a server component, yet it used a client-side hook on the first try anyway.
- It created a new BlogProps interface even though I have a generic PageProps one in my types directory. I missed this and will have to go back and fix it.
- Claude cannot guess whether or not I have an extremely common utility like that in Cursor. It can only make inferences based on the context window, which users are allowed to add files and other data to as the chat evolves.
- This begs the question of whether or not humans should be solely in control of the context. They are for security, but nonobvious mistakes like this will be hard to avoid if they stay that way.
Leave a Reply