24 mars 2025
How does the use API work with Next 15 and React 19?

7 minutes de lecture

Data fetching
and mutations
are the cornerstones of many websites. For frontend developers, the goal is often to implement efficient interfaces while ensuring a smooth user experience. However, as there are increasingly numerous solutions for data management, how do you choose the right one?
In this article, we will explore the different solutions available with Next 15 and React 19. We will discover few new React 19 features like the use API, which was recently introduced and has now been stable for a few months.
Some reminders
Here, we will mainly focus on Client Components
, Server Components
, Server Actions
, and how they work together to serve and manipulate data. If you are not familiar with these concepts, I recommend reading the articles Next 13.4: Overview of server actions and Focus on the new router app of Next 13. We should note that the data mutation aspect hasn't changed much since version 13, therefore we will focus on the part related to data fetching.
Key Concepts
Until version 15, there were four ways to fetch data:
Client Components
fetch
: Retrieve data directly on the client side
This approach often involves a third-party library like React Query or use within an useEffect
hook coupled with state management. This method allows the retrieval of data directly on the client side without going through the server. However, this method suffers from some limitations, especially in terms of performance since data will only be retrieved when the component mounts, which may give the user an impression of latency.
As a reminder, the fetch API is overloaded by NextJS and has additional features compared to the native fetch API, such as cache management and data revalidation.
server actions
: Called from the client, executed on the server
Similar to a client-side fetch, this method offers a more streamlined developer experience. But it suffers from the same limitations as the client-side fetch. Moreover, server actions
can open the door to security issues if one allows oneself to be lulled by its easy implementation and forgets to secure these functions - they are indeed endpoints, hence the need to protect them.
The use of server actions
or the fetch
API will be determined by the context of the application.
If you are using an external API, you will need to use the fetch
API on the client side.
On the contrary, if you have an ORM like Prisma or
Drizzle, you can use server actions
which can
use your data manipulation functions.
Server Components
fetch
: Retrieve data on the server and then pass it to the UIserver function
: Executed on the server and then passed to the UI
☝️ Here we specify that these are server functions
because they are not necessarily
server actions
(without the use server
directive), which avoids creating an endpoint that
would not be used by a Client Component.
In both cases, we are dealing with asynchronous functions, which will be waited for by the server before being passed on to the UI. Thus, the page rendering will be blocked until the function is completed.
Assessment of traditional methods
Depending on the needs, data may need to be fetched client-side or server-side. If data fetching involves a prior user interaction, a Client Component
will have to be used and thus a fetch
or a server action
, with the limitations this implies on performance.
If no user interaction is required, a Server Component
can be used and hence a server function
or a server-side fetch
, with the drawback of blocking the page rendering until the data is fetched.
For the part regarding dynamic data retrieval, little has changed since version 13. However, for the part involving static data retrieval, we now have greater flexibility, mainly around the user experience, which we will explore with the use of the use
API.
The use
API and the passage of Promises
As we have just seen, server-side data retrieval has limitations, especially in terms of performance, mainly due to the fact that page rendering is blocked until data is fetched because this data is fetched synchronously (using await
). This can be problematic with a large amount of data or significant latency.
Since the introduction of Server Components, it has been possible to pass Promises
to a Client Component, provided that the result of the Promise
is a serializable value (so no functions). However, until now, it wasn't possible to block the rendering of the Client Component as long as the Promise
is not resolved.
To address this issue, React 19 introduces a new API: use
. We will see how this new API allows targeting the component that should wait for the Promise
to be resolved, display a fallback
until the Promise
is resolved, or an error if the Promise
is rejected.
Optimizing user experience
How does the transfer of Promises work with Next?
To understand the functionality of the use
API, let's examine how Next 15 handles the transfer of Promises
to a Client Component.
For the example, we will retrieve data from all the planets in the solar system.
import { getPlanets, type Planet } from '@/lib/planets'
import Planets from './Planets'
export default function PlanetsPage() {
const planetsPromise: Promise<Planet[]> = getPlanets() // Asynchronous data retrieval
return <Planets planetsPromise={planetsPromise} />
}
In this example, when the user navigates to the /planets
page, the Next server will:
-
Render the page, not blocked as the
Promise
is sent asynchronously to the client. -
Send the HTML to the client + the JavaScript bundle that will enable to build the page (the Client Component), without accounting for the
Promise
(in reality the resolution of thePromise
has already started on the server side).
Server responses carry the header Transfer-Encoding: chunked to indicate that the response is divided into several parts. At this stage, the connection is kept open to receive future chunks.
-
When the
Promise
is resolved on the server side, it sends a last chunk on the connection kept open until then. -
The client will receive the last chunk and will be able to build the page with the fetched data.
At this point, we can already see the major advantage of this approach which allows executing an asynchronous function on the server-side without blocking the page rendering and without using server actions
implying a new HTTP call.
⚠️ Note that the asynchronous function will be executed even if it is not consumed by the Client
Component. Make sure not to leave unnecessary Promises
in the code, as this could impact
performance.
How does the use
API work?
Visually, there is not much change... for now, because we are not taking advantage of having non-blocking rendering, but this is where the use
API comes into play. It will allow us to target the component that must wait for the Promise
to be resolved and display a fallback
until then or an error if the Promise
is rejected.
If the PlanetsPage
component contains metadata, such as a title or a description, these will be displayed before the Promise
is resolved. The same applies to possible layouts or other elements other than the Planets
component. This will make the page visually smoother for the user.
To take advantage of the benefits of the use
API, we will therefore wrap the Planets
component in a Suspense
and/or an ErrorBoundary
and use use
within the client Planets
component.
import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import Planets from './Planets'
import { getPlanets, type Planet } from '@/lib/planets'
export default function PlanetsPage() {
const planetsPromise: Promise<Planet[]> = getPlanets() // Asynchronous data fetching
return (
<ErrorBoundary fallback={<div>Error</div>}>
<Suspense fallback={<div>Loading...</div>}>
<Planets planetsPromise={planetsPromise} />
</Suspense>
</ErrorBoundary>
)
}
'use client'
import { use } from 'react'
export default function Planets({ planetsPromise }) {
const planets = use(planetsPromise)
return (
<ul>
{planets.map(planet => (
<li key={planet.id}>{planet.name}</li>
))}
</ul>
)
}
Here, React will detect that the Planets
component uses the use
API on a Promise
, it will replace the Planets
component with the Suspense
fallback in the component tree and restore the Planets
component when the Promise
is resolved, or display the error if the Promise
is rejected.
Passing Promises between Client Components
The primary objective of this new API is to be able to control loading and error states while retrieving data, but it can also be used to go further in the granularity of data fetching and drive our loading state closer to the concerned components. Sometimes, this might prompt us to reorganize our components to make them smaller and more independent, but in the end, it will help us improve readability, maintainability, and enhance user experience.
Other use cases
Among other use cases of the use
API, we can mention:
- Can be used in
Server Components
- Can be used conditionally
- Can be used to fetch data from a
Context
Downsides
Unfortunately, the use
API requires implementing a Suspense
and/or an ErrorBoundary
for every component using it. This can quickly become tedious to manage, and may give a sense of boilerplate.
The loading.tsx
and error.tsx
files can be used to manage loading and error states globally or
per page. They do not address the same level of granularity as a component-specific Suspense
,
but they can prevent potential oversights.
Conclusion
If you want to optimize the user experience of your pages, the use
API is a solution not to be overlooked, provided your asynchronous functions aren't dependent on a user interaction. This will allow you to target the components affected by a loading or error state and avoid "flash" states that could occur with traditional loading state management.
Another significant advantage is that now your Server Components
can be rendered immediately without waiting for the Promise
to be resolved.