How React 18’s Concurrent Features Supercharge App Performance
React 18 introduces concurrent rendering, transitions, Suspense, and Server Components, which together reduce long tasks, lower total blocking time, and improve user interaction by allowing non‑urgent updates to run in the background, ultimately delivering smoother, more responsive web applications.
This article is translated from "How React 18 Improves Application Performance" and mainly introduces how React's concurrent features—such as Transitions, Suspense, and React Server Components—enhance application performance.
React 18 introduces concurrent features, fundamentally changing how React applications render. We will explore how these new features affect and improve application performance.
First, let’s briefly review the basics of long tasks and related performance metrics.
Main Thread and Long Tasks
When JavaScript runs in the browser, the JavaScript engine executes code in a single‑threaded environment, commonly called the main thread. In addition to executing JavaScript, the main thread handles user interactions (clicks, key presses), network events, timers, animation updates, and manages layout and repaint.
While the browser can smoothly execute small tasks to provide a seamless user experience, longer tasks can become problematic because they block the processing of other tasks.
Any task that runs longer than 50 ms is considered a “long task.”
This 50 ms threshold is based on the need for devices to create a new frame every 16 ms (60 fps) to maintain smooth visual experience. Devices also need time for other work such as responding to user input and executing JavaScript.
The 50 ms benchmark allows devices to allocate resources between rendering frames and other work, providing roughly an extra 33.33 ms for other tasks while keeping visual smoothness. You can read the RAIL model blog post for more details about the 50 ms baseline.
To maintain optimal performance, it is crucial to minimize the number of long tasks. Two metrics can measure the impact of long tasks on performance: Total Blocking Time (TBT) and Interaction to Next Paint (INP).
Total Blocking Time measures the sum of time spent on tasks that exceed 50 ms between First Contentful Paint (FCP) and Time to Interactive (TTI). High TBT can significantly affect user experience.
Interaction to Next Paint (INP) is a new Core Web Vitals metric that measures the time from a user's first interaction (e.g., a click) to the next paint where the interaction becomes visible. It is especially important for pages with many user interactions such as e‑commerce or social media platforms.
Traditional React Rendering
React’s visual updates consist of two phases: the render phase and the commit phase. In the render phase, React creates a virtual DOM tree and computes differences between the current DOM and the new component tree.
After the render phase comes the commit phase, where React applies the computed updates to the actual DOM, creating, updating, or deleting nodes.
In synchronous rendering, every component in the tree receives the same priority. React continues rendering until the entire tree is processed, then commits the result to the DOM. This process is uninterruptible.
Synchronous rendering is an “all‑or‑nothing” operation; the main thread is blocked until rendering completes, causing the UI to become unresponsive if a large render task is in progress.
For example, a text input that filters a list of thousands of cities will cause a noticeable delay between keystroke and UI update during synchronous rendering.
Concurrent Rendering
React 18 introduces a new concurrent renderer that runs in the background. It allows certain renders to be marked as non‑urgent, letting the main thread check for higher‑priority tasks (such as user input) every few milliseconds.
During low‑priority component rendering, React yields control back to the main thread at roughly 5 ms intervals, enabling the browser to handle more important tasks promptly.
Unlike a single, uninterruptible task, the concurrent renderer can render multiple versions of the component tree in the background without immediately committing the result.
When a user interacts with a high‑priority component, React can pause the current render, prioritize the interaction, and later resume the background work.
Transitions
The
useTransitionhook provides a
startTransitionfunction to mark state updates as non‑urgent. This tells React that the update may cause visual changes that can be deferred, preserving a smooth interaction experience.
<code>import { useTransition } from "react";
function Button() {
const [isPending, startTransition] = useTransition();
return (
<button
onClick={() => {
urgentUpdate();
startTransition(() => {
nonUrgentUpdate();
});
}}
>...</button>
);
}
</code>When a transition starts, the concurrent renderer prepares a new component tree in the background. Once rendering finishes, the result is kept in memory until the scheduler can efficiently update the DOM, typically when the browser is idle and no higher‑priority tasks are pending.
Using
startTransitionin the CitiesList demo dramatically reduces the number of long tasks and total blocking time compared with updating state synchronously on every keystroke.
React Server Components
React Server Components (RSC) are an experimental feature in React 18 that allow the server to send a serialized component tree to the client. The client‑side React renderer can reconstruct the tree without sending a full HTML payload or JavaScript bundle.
RSC enables developers to combine
renderToPipeableStreamfrom
react-dom/serverwith
createRootfrom
react-dom/clientto stream server‑rendered components.
<code>// server/index.js
import App from '../src/App.js';
app.get('/rsc', async function(req, res) {
const { pipe } = renderToPipeableStream(React.createElement(App));
return pipe(res);
});
// src/index.js
import { createRoot } from 'react-dom/client';
import { createFromFetch } from 'react-server-dom-webpack/client';
export function Index() {
// ...
return createFromFetch(fetch('/rsc'));
};
const root = createRoot(document.getElementById('root'));
root.render(<Index />);
</code>By default, React does not hydrate Server Components. They should not use client‑only APIs such as
window,
useState, or
useEffect. To make a component interactive, add the "use client" directive at the top of the file, which tells the bundler to include it in the client bundle and enables hydration.
Suspense
Suspense, originally introduced in React 16 for code‑splitting, gains new data‑fetching capabilities in React 18. It allows developers to declaratively specify a fallback UI while waiting for asynchronous data.
<code>async function BlogPosts() {
const posts = await db.posts.findAll();
return '...';
}
export default function Page() {
return (
<Suspense fallback={<Skeleton />}>
<BlogPosts />
</Suspense>
);
}
</code>When a component is suspended, React pauses its rendering and focuses on other work, such as higher‑priority updates, then resumes the suspended component once the data becomes available.
Data Fetching
React 18 adds a built‑in caching API. The
cachefunction memoizes the result of an async function for the duration of a render, preventing duplicate network requests.
<code>import { cache } from 'react';
export const getUser = cache(async (id) => {
const user = await db.user.findUnique({ id });
return user;
});
// Called within same render pass: returns memoized result.
</code>Fetch calls are also automatically cached by default, reducing the number of network requests during a single render pass.
<code>export const fetchPost = async (id) => {
const res = await fetch(`https://.../posts/${id}`);
const data = await res.json();
return { post: data.post };
};
// Called within same render pass: returns memoized result.
</code>These caching mechanisms are especially useful with Server Components, which cannot access context APIs.
Summary
Concurrent React allows rendering to be paused, resumed, or abandoned, keeping the UI responsive even during large render tasks.
The Transitions API enables smoother data‑fetching or screen‑switching without blocking user input.
React Server Components combine server‑side performance with client‑side interactivity while avoiding hydration costs.
Enhanced Suspense lets parts of an app render before slower data‑fetching sections, improving load performance.
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.