Back to blog

Ditching State for searchParams: The Future of Next.js 13

Karl Koch's avatar

Karl Koch

August 17, 2023

cover image


In order to get the most out of this article, you'll need a few things:

  1. To be using Next.js 13 or later.
  2. To be utilizing the new App Router project structure.
  3. To be familiar with React's hook, and likely already be using it.
  4. To be aware of the underlying fundamentals of client and server components in React 18 and Next 13.
  5. To be using Cosmic as your headless CMS of choice (although these approaches will work for other data stores such as databases or other CMSs).

Getting started

In our case, we're going to migrate an existing client-side pagination to the server. But why would we want to do this?

Client-side pagination, in itself, isn't an issue. There's nothing wrong with holding your state in the browser DOM and updating it when things change or need to change. Fundamentally, this is how a lot of us have handled pagination in React projects in the past.

But with React Server Components (RSC), we're able to shift this logic over to the server. This means a few things:

  1. We're shipping less client-side JavaScript, which is great for page load times and SEO.
  2. We're able to store state in the URL params rather than in a state object, making these URL states shareable with others.
  3. We keep all of our app logic on the server, so re-fetching data is a server to server interaction rather than client-server.

Let's look at a typical use case.

useState-powered Pagination

In our existing structure, we have two things. A call to Cosmic to fetch our data, and a state that handles knowing what page we're on.

Our Cosmic content

So just to break this down if you're less familiar with the Cosmic SDK, here we're making an async request to the Cosmic SDK which allows us to fetch our data. We're passing in a single param, which is our optional param to get the current page number.

This number gets passed to our method's . When combined with , ensures that we can take the current page number in, remove 1, and then multiply by the number.

Let's break down this process:

  1. Start with the current page number, for example, 1.

  2. Subtract 1 from this number. In this case, 1 - 1 = 0.

  3. Multiply the result (0) by our set limit, which is 9. This gives us 0 again.

  4. We instruct our Cosmic data fetch to 'skip' by this result.

So, if we are on page one and use the 'skip' function, we will retrieve the first 9 posts. If we move to page 2, we take the number 2, subtract 1 to get 1, multiply by 9 to get 9, and then skip the first 9 posts. This will show us posts 10 through 18, effectively giving us "page 2".

Our State

In theory, this approach works. However, without client-side control, we'll only ever see the first 8 posts, which isn't very useful!

To handle this in our existing client-side state-powered world, we'll use from React.

We're going to keep things nice and simple.

Let's break this down now. We've got a simple state controller which holds an initial state of 1. We then have two simple functions which take in the current page value and then either remove 1 from the current page or add one.

For the function we have a little ternary guard that ensures we don't end up with negative page numbers.

So to get this into our page, we declare our fetch request like so, and pass in our current page from our state:

And now, we can pass the pagination functions to a simple button to update our state and handle the pagination transitions.

Switching to searchParams

First thing's first, we need to lose the state. So let's delete our state declaration and remove the import dependency.

So now we don't hold state, where does our state live? Well, thanks to Next.js 13, it lives in the URL params.

To make this work, we'll request the in the main Posts page. [Note: you can find out more about searchParams in the Next.js 13 Docs]

Next, we'll need to create the logic to validate the page received from our is a typeof and then convert it to a number so the data fetch won't complain. This is all type safety stuff, so if you're not using TypeScript, you can skip these bits (but I'd recommend you do use TypeScript!)

This is similar to how we declared our state and set a default value of 1. It's a little more verbose, but it ensures our params are type safe.

You'll notice that includes a property on it. This is because is a special type of parameter that has extra properties you can access. Thanks to it being a Next-specific parameter, Next has the type completions in place to make this obvious and accessible.


Now that we've ditched out and implemented , we need to implement the logic handling our pagination. We can remove our original function calls now too, as we'll handle this logic in a special component. This allows us to move our logic to the lowest possible level in our stack if we need to. In our case, we don't need that, but it's good practice in case you need to access the client at any point in the future.

Create a new component named . You can place it wherever you prefer in your project, whether it's alongside your pages or in a dedicated directory. Inside of this component, we'll to allow us to navigate. We'll also need to pass our and our as props.

So now let's handle that logic:

Include whatever classes you want to for styles here. For now we'll keep it simple and just apply styles for removing pointer events and reducing the opacity if the page is either the initial page or the final page. We've determined the final page by measuring the length.

To get this working in our main page, we just need to import our new component and pass the required props for and .

And that's how simple it is.

Putting it altogether

Note: I've applied some default styles using Tailwind CSS to a few things and assumed you have a specific Cosmic Bucket in place with the data you need.

Concluding things

So now you're able to pass all of your search logic into your URL parameters and not use any client-side JavaScript for it.

Try navigating to page 3, then copy the URL and paste it into an incognito window. It'll load up with the exact right data, and you'll be able to navigate from there back or forth. How cool is that!

By leveraging instead of , we introduce a significant advantage: the ability to persist and share specific application states through URLs. This means that a user can effortlessly share a URL containing certain parameters, and the recipient will see the exact same content or application state without any additional steps.

For more information about how to use Cosmic in your application, visit our documentation

Want to get started with Cosmic? Create an account for free.