Paired Programming with Claude

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

help me with my todo on line 8
Spenpo
I'll help you implement pagination for the blog posts. Here's how we can modify the code to handle pagination:
Claude
// ... 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>
  )
}
Here are the key changes made:
Claude
  1. Added pagination support using MUI’s Pagination component
  2. Used useSearchParams to get the current page from the URL
  3. Modified the fetch call to include pagination parameters
  4. Added logic to get the total posts count from the WordPress API headers
  5. Updated the display text to show correct post counts
  6. Added a pagination component that updates the URL when clicked
Note: Since this is a Server Component, you might need to split the pagination handling into a client component. You could create a new component like PaginationControls and move the pagination-related UI there.
Also, make sure your WordPress API is configured to send the correct headers for pagination (X-WP-Total and X-WP-TotalPages).
this is a nextjs server component. is it possible to get the data you need without useSearchParams?
Spenpo
Ah yes, you're right! In Next.js server components, we can get the search params directly from the page props. Here's the corrected version:
// 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>
  )
}
Then create a new client component for the pagination controls:
Claude
'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>
  )
}
This approach is better because:
Claude
  1. It properly handles server-side props in Next.js
  2. Separates the client-side pagination controls into their own component
  3. Uses the Next.js router for client-side navigation
  4. Keeps the main page component as a server component
Don't forget to import the new `PaginationControls` component in your page file!

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.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *