AAbolfazl.dev
All writing
Jul 04, 2025
React

Revolutionize Loading UX with React 18: Suspense, Streaming & Selective Hydration

views12 min
Revolutionize Loading UX with React 18: Suspense, Streaming & Selective Hydration

The loading problem with traditional React

Not long ago, most React apps relied entirely on client-side rendering. You would show a spinner, wait for data, and finally render the UI. Later, SSR became mainstream through frameworks like Next.js, mostly for performance and SEO, but it still had a waterfall problem.

The old model forced three steps to happen in order: fetch everything before showing anything, load all JavaScript before hydrating anything, and hydrate everything before the user could interact with anything. That is a lot of waiting just to click a button in a sidebar.

React Suspense and streaming do not merely make that waterfall faster. They let us split the work by component so the page can show what is ready, stream what is slow, and hydrate what the user actually needs.

Traditional server-side rendering waterfall
Traditional SSR waterfall

How React Suspense transforms loading

React 18 unlocks two big changes through Suspense: streaming HTML on the server and selective hydration on the client. Instead of treating the whole page as one blocking unit, meaningful regions can become independent loading boundaries.

Imagine a page with a header, sidebar, projects section, and blogs section. With the traditional approach, all HTML and JavaScript had to be ready before the user experienced the whole thing as interactive. With Suspense, slower sections can show a fallback while the rest of the shell streams immediately.

tsx
<main>
<Header />
<section>
<Sidebar />
<Projects />
<Suspense fallback={<div>Loading blogs...</div>}>
<Blogs />
</Suspense>
</section>
</main>
Initial traditional server-rendered result before hydration
Initial SSR result
Hydrated traditional server-rendered result
Hydrated SSR result

Streaming SSR: breaking the bottleneck

Under the hood, React can use renderToPipeableStream to begin rendering HTML as a stream instead of waiting for the entire tree. The server can flush the shell early and postpone slower Suspense boundaries for later.

When a Suspense boundary is not ready, React sends the fallback in its place. Later, when the real content is ready, React streams the replacement HTML and a small script that swaps the placeholder with the finished content.

tsx
import { renderToPipeableStream } from "react-dom/server";
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
pipe(response);
},
});
Streaming SSR result with a Suspense fallback
Streaming with a fallback boundary

How streamed placeholders get replaced

During streaming, React inserts a fallback UI into the HTML where content is not ready yet. The fallback is paired with an internal placeholder ID. When the real content becomes available, React sends a segment and replaces that placeholder without a full page rerender.

You do not call these internal functions yourself, but understanding the shape helps connect the dots: the fallback you see is later matched with the streamed content that belongs in the same position.

html
<div hidden id="S:0">
<div>Comments loaded!</div>
</div>
<script>
$RC("B:0", "S:0");
</script>

Selective hydration: fast interaction where it counts

Even if the server streams HTML quickly, the page still needs JavaScript before components can become interactive. Code splitting helps by moving lower-priority widgets into smaller bundles that can load later.

React 18 can begin hydrating the parts that are already ready instead of waiting for every component. The navigation or footer can become interactive while a slower blog section is still loading its code.

tsx
import { lazy, Suspense } from "react";
const Blogs = lazy(() => import("./blogs"));
<Suspense fallback={<div>Loading blogs...</div>}>
<Blogs />
</Suspense>;
First loading state for a lazy Suspense section
First loading the section
Lazy blog section loaded after Suspense
Then the blog section loads
Other page sections are hydrated before the blog section
Other sections hydrate first

Smarter hydration for smoother interaction

Suspense boundaries also change how hydration work is prioritized. Hydration inside a boundary can happen in smaller chunks, giving the browser chances to handle user input instead of locking the page while React works.

If the user interacts with a not-yet-hydrated section, React can prioritize that section so the interaction works sooner. This is what makes selective hydration feel almost instant from the user's point of view.

Selective hydration completing interactive sections progressively
Selective hydration finishing progressively

Conclusion

React 18 fundamentally changes how we think about loading, rendering, and hydration. Instead of waiting for the entire app to load and hydrate, we can stream what is ready and hydrate what matters exactly when the user needs it.

If you have struggled with loading performance or interaction delays in React apps, Suspense boundaries, streaming, and selective hydration are worth understanding deeply.

Comments

()

Loading saved comments...

S
Sara K.2 days ago

The layout-accurate skeleton point finally made it click for me. Thanks!

D
dev_mehran5 days ago

Been streaming the shell for months but never tried selective hydration priority. Trying this today.