Skip to content
Dashboard

How React 18 Improves Application Performance

Staff Developer Advocate

Learn how concurrent features like Transitions, Suspense, and React Server Components improve application performance.

Link to headingMain thread and Long Tasks

The main thread is responsible for handling tasks one by oneThe main thread is responsible for handling tasks one by one
The main thread is responsible for handling tasks one by one
The TBT is 45ms, since we have two tasks that took longer than 50ms before TTI, which exceeded the 50ms threshold by 30ms and 15ms respectively. The total blocking time is the accumulation of these values: 30ms + 15ms = 45ms. The TBT is 45ms, since we have two tasks that took longer than 50ms before TTI, which exceeded the 50ms threshold by 30ms and 15ms respectively. The total blocking time is the accumulation of these values: 30ms + 15ms = 45ms.
The TBT is 45ms, since we have two tasks that took longer than 50ms before TTI, which exceeded the 50ms threshold by 30ms and 15ms respectively. The total blocking time is the accumulation of these values: 30ms + 15ms = 45ms.
The Interaction to Next Paint is 250ms, as it's the highest measured visual delay.The Interaction to Next Paint is 250ms, as it's the highest measured visual delay.
The Interaction to Next Paint is 250ms, as it's the highest measured visual delay.

Link to headingTraditional React Rendering

import React, { useState } from "react";
import CityList from "./CityList";

export default function SearchCities() {
  const [text, setText] = useState("Am");

   return (    
      <main>      
          <h1>Traditional Rendering</h1>      
          <input type="text" onChange={(e) => setText(e.target.value) }   />      
          <CityList searchQuery={text} />    
      </main>  
     );
};

If you’re on a high-end device like a Macbook, you might want to throttle your CPU 4x to simulate a lower-end device. You can see this setting in Devtools > Performance > ⚙️ > CPU.

Tasks marked with the red corner are considered “long tasks”. Note the total blocking time of 4425.40ms. Tasks marked with the red corner are considered “long tasks”. Note the total blocking time of 4425.40ms.
Tasks marked with the red corner are considered “long tasks”. Note the total blocking time of 4425.40ms.
When rendering the low-priority components (pink), React yields back to the main thread to check for more important tasksWhen rendering the low-priority components (pink), React yields back to the main thread to check for more important tasks
When rendering the low-priority components (pink), React yields back to the main thread to check for more important tasks
Instead of a single non-interruptible task for every render, the concurrent renderer yields control back to the main thread at intervals of 5ms during the (re)rendering of low-priority components. Instead of a single non-interruptible task for every render, the concurrent renderer yields control back to the main thread at intervals of 5ms during the (re)rendering of low-priority components.
Instead of a single non-interruptible task for every render, the concurrent renderer yields control back to the main thread at intervals of 5ms during the (re)rendering of low-priority components.
React pauses the current render based on a user interaction that forces it to prioritize rendering another updateReact pauses the current render based on a user interaction that forces it to prioritize rendering another update
React pauses the current render based on a user interaction that forces it to prioritize rendering another update

Link to headingTransitions

import { useTransition } from "react";
function Button() {
const [isPending, startTransition] = useTransition();
return (
<button
onClick={() => {
urgentUpdate();
startTransition(() => {
nonUrgentUpdate()
})
}}
>...</button>
)
}

import React, { useState, useTransition } from "react";
import CityList from "./CityList";

export default function SearchCities() {
  const [text, setText] = useState("Am");
  const [searchQuery, setSearchQuery] = useState(text);
  const [isPending, startTransition] = useTransition();

   return (    
      <main>      
          <h1><code>startTransition</code></h1>      
          <input  
              type="text" 
              value={text}
              onChange={(e) => {
                 setText(e.target.value)
                 startTransition(() => {
                    setSearchQuery(e.target.value)
                 })
             }}  />      
          <CityList searchQuery={searchQuery} />    
      </main>  
     );
};
The performance tab shows that the number of long tasks and the total blocking time have reduced significantly.
The performance tab shows that the number of long tasks and the total blocking time have reduced significantly.

Link to headingReact Server Components

// 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 />);

⚠️ This is an over-simplified (!) example of the CodeSandbox demo shown below.

Note: Framework implementations may differ. For example, Next.js will prerender Client Components to HTML on the server, similar to the traditional SSR approach. By default, however, Client Components are rendered similar to the CSR approach.Note: Framework implementations may differ. For example, Next.js will prerender Client Components to HTML on the server, similar to the traditional SSR approach. By default, however, Client Components are rendered similar to the CSR approach.
Note: Framework implementations may differ. For example, Next.js will prerender Client Components to HTML on the server, similar to the traditional SSR approach. By default, however, Client Components are rendered similar to the CSR approach.

Link to headingSuspense

async function BlogPosts() {
const posts = await db.posts.findAll();
return '...';
}
export default function Page() {
return (
<Suspense fallback={<Skeleton />}>
<BlogPosts />
</Suspense>
)
}

Using React Server Components works seamlessly with Suspense, which allows us to define a loading state while the component is still loading.

Link to headingData Fetching

import { cache } from 'react'
export const getUser = cache(async (id) => {
const user = await db.user.findUnique({ id })
return user;
})
getUser(1)
getUser(1) // Called within same render pass: returns memoized result.

export const fetchPost = (id) => {
const res = await fetch(`https://.../posts/${id}`);
const data = await res.json();
return { post: data.post }
}
fetchPost(1)
fetchPost(1) // Called within same render pass: returns memoized result.

async function fetchBlogPost(id) {
const res = await fetch(`/api/posts/${id}`);
return res.json();
}
async function BlogPostLayout() {
const post = await fetchBlogPost('123');
return '...'
}
async function BlogPostContent() {
const post = await fetchBlogPost('123'); // Returns memoized value
return '...'
}
export default function Page() {
return (
<BlogPostLayout>
<BlogPostContent />
</BlogPostLayout>
)
}

Link to headingConclusion