Logo Xingxin on Bug

Diagram Search and Pagination in Next.js

May 11, 2025
3 min read

This is what I learned from App Router: Adding Search and Pagination | Next.js. I believe the diagrams greatly helped me understand the framework.


🔭Overview

search-invoice.webp

In short, the function described here is to search for an invoice in the database and display it in a table. The pattern uses URL search parameters, meaning that when you search, the URL will look like /dashboard/invoices?page=1&query=pending. This approach is beneficial for SEO and bookmarking.

🧪UI Composition

The UI layout could be simplified like so:

<Page>
  <Search />
  <Table />
  <Pagination />
</Page>

seach-invoice-UI-composition.webp

  • 🟥: search (client component)
  • 🟩: table (server component)
  • 🟦: pagination (client component)

✏️Workflow

Let me give you a big picture of how each .tsx affect the URL. how-search-invoice-work.webp

0️⃣ url

Since 🟥🟩🟦 are all composed inside the <Page />, when we use usePathname() to get the current URL, it always return the www.example.com/dashboard/invoice.

Whenever the user initiates a search, the text is parsed by the <Search/> component. For example, if I type “D” and the <Search/> component will updates the URL to /dashboard/invoices?page=1&query=D using the next.js function useRouter().(see the red arrow)

2️⃣ navigate

How does the navigation component know the number of pages?

Great question! The <Pagination /> is a client component and we don’t want to expose the database secrets to it, as that would be dangerous!
The solution is to calculate the number of pages in the <Page /> component (a server component) and pass the value to the <Pagination />.

When user click the ⬅️ or ➡️, the <Pagination /> updates the page in the search parameters(see the blue arrow).

3️⃣ the Page

export default async function Page(props: {
  searchParams?: Promise<{
    query?: string;
    page?: string;
  }>;
}) {
  const searchParams = await props.searchParams;
  const query = searchParams?.query || '';
  const currentPage = Number(searchParams?.page) || 1;
  const totalPages = await fetchInvoicesPages(query);
 
  return (
    <div>
        <Search placeholder="Search invoices..." />
        <Table query={query} currentPage={currentPage} />
        <Pagination totalPages={totalPages} />
    </div>
  );
}

Whenever the <Search /> or <Pagination /> changes the URL, the <Page/> parses the searchParams. The query and page values are then passed to the <Table /> to display.

4️⃣ table

Finally, the <Table /> is responsible for displaying the data.