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';
}
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;
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>
</>
);
}
Preview:
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>
</>
);
}
Preview:
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:
- You don't need async on your component using
use
. - Even though it's a hook, you don't need the
'use client'
directive!! In other words, you can use theuse
hook in server components. I'm pretty sure this is the only exception to this rule and it is an extension ofNext
'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.But, confusion remains. I'm pretty sure both will remain available. But I don't know which method is better or optimal.
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.