DEV Community

Peter Jacxsens
Peter Jacxsens

Posted on • Edited on

Synchronous and asynchronous searchParams in Next 15

Next 15 changed how we use the searchParams props of the page.tsx file (the route root, /someroute/page.tsx). searchParams is now asynchronous.

export default async function SomeRoute({ searchParams }) {
  const currSearchParams = await searchParams;
  return 'hello world';
}
Enter fullscreen mode Exit fullscreen mode

Why asynchronous?

Next aims to further improve optimization. Prior, Next would have let the entire component wait for the searchParams request to be fulfilled before starting to render. By making the searchParams request asynchronous, Next can immediately start rendering the synchronous part of the code while it's running the searchParams request.

Small side note, Next 15 also made headers, cookies and the params page prop asynchronous. Read more on those in the release notes.

What about synchronous components?

In the above example, we used the await keyword, therefore we also had to use the async keyword. This makes our component asynchronous.

But, there is this line in the release notes:

For an easier migration, these APIs [headers, cookies, ... searchParams] can temporarily be accessed synchronously, but will show warnings in development and production until the next major version.

I found that line a bit confusing at first. It does not mean that you can do this:

// wrong
const currSearchParams = searchParams;
Enter fullscreen mode Exit fullscreen mode

In Next 15 the searchParams request always returns a promise and in above example, since we didn't await, it would be a pending promise. Not a searchParams object with actual values.

Examples

There is more confusion to be had but at this point I found some upgrade examples. I will walk you through them with my own code.

I made a fresh Next 15 install with TypeScript, ESLint, Tailwind CSS, app router and cleaned out the boilerplate code. Note: this code is available in a github repo.

I created a new route /asyncpage with following code:

// src/app/asyncpage/page.tsx

type Props = {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};

export default async function AsyncPage({ searchParams }: Props) {
  const currSearchParams = await searchParams;
  return (
    <>
      <h2 className='text-2xl font-bold mb-2'>async page ?foo=bar</h2>
      <div>searchParam foo is: {currSearchParams.foo}</div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Preview:

asynchronous searchParams

This is what we've seen before. It is the new way of using the searchParam request in Next 15.

Now, we also create a /syncpage route. The component has to be synchronous meaning it cannot have the async keyword. If we can't use async we can't use await and that leaves us with a pending promise as we've seen.

use

With Next 15 comes the new React 19 use hook.

use is a React API that lets you read the value of a resource like a Promise or context.

This obviously seems to fit our requirements so, here is our new component:

// src/app/syncpage/page.tsx

import { use } from 'react';

type Props = {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};

export default function SyncPage({ searchParams }: Props) {
  const currSearchParams = use(searchParams);
  return (
    <>
      <h2 className='text-2xl font-bold mb-2'>sync page ?foo=bar</h2>
      <div>searchParam foo is: {currSearchParams.foo}</div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Preview:

synchronous searchParams

We basically flipped out await with use but there are some things to note here. I knew about the new use hook but I haven't studied it in depth and some things surprised me:

  1. You don't need async on your component using use.
  2. Even though it's a hook, you don't need the 'use client' directive!! In other words, you can use the use hook in server components. I'm pretty sure this is the only exception to this rule and it is an extension of Next's new shift towards server components.

But, there is another question. Why use this? Why not make the page async and use await? I don't know. It this better? Is this faster? I'm not sure. An edge case could be when your page component needs to be a client component. Since client components can't be async you would have to use the synchronous searchParams version with the use hook. But making the page component a client component is probably a poor practice.

Remember the quote from the Next 15 release notes from earlier?

For an easier migration, these APIs [headers, cookies, ... searchParams] can temporarily be accessed synchronously, but will show warnings in development and production until the next major version.

I ran this app both in development and production mode and encountered no errors or warning on the synchronous page when using the searchParams directive. This leads me to believe that searchParams will be permanently available synchronously.

Conclusion

By now you should understand how to use searchParams synchronously and asynchronously. All and all it's not very difficult. Since Next recommends using the async version, let's just do that then.

This series on searchParams in Next 15 handles both using and testing/mocking. To properly test and mock we need a wee example which we will quickly do in part 2.


If you want to support my writing, you can donate with paypal.

Top comments (0)