---
title: Introducing RSC Explorer
date: '2025-12-19'
spoiler: My new hobby project.
bluesky: https://bsky.app/profile/danabra.mov/post/3mabn2f236s2f
---
In the past few weeks, since the disclosure of the [critical security vulnerability in React Server Components (RSC)](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components), there's been a lot of interest in the RSC protocol.
The RSC protocol is the format in which React trees (and a [superset of JSON](https://github.com/facebook/react/issues/25687)) get serialized and deserialized by React. React provides both a writer and a reader for the RSC protocol, which are versioned and evolved in lockstep with each other.
Because the RSC protocol is an *implementation detail* of React, it is not explicitly documented outside the source code. The benefit of this approach is that React has a lot of leeway to improve the format and add new features and optimizations to it.
However, the downside is that even people who actively build apps with React Server Components often don't have an intuition for how it works under the hood.
A few months ago, I wrote [Progressive JSON](/progressive-json/) to explain some of the ideas used by the RSC protocol. While you don't "need" to know them to use RSC, I think it's one of the cases where looking under the hood is actually quite fun and instructive.
I wish the circumstances around the increased interest now were different, but in any case, **that interest inspired me to make a new little tool** to show how it works.
I'm calling it **RSC Explorer**, and you can find it at [`https://rscexplorer.dev/`](https://rscexplorer.dev/).
Obviously, it's [open](https://tangled.org/danabra.mov/rscexplorer) [source](https://github.com/gaearon/rscexplorer).
---
"Show, don't tell", as they say. Well, there it is as an embed.
Let's start with the Hello World:
Notice there's a yellow highlighted line that says something cryptic. If you look closely, it's `
Hello
` represented as a piece of JSON. This line is a part of the RSC stream from the server. **That's how React talks to itself over the network.**
**Now press the big yellow "step" button!**
Notice how `
Hello
` now appears on the right. This is the JSX that the *client* reconstructs after reading this line. We've just seen a simple piece of JSX--the `
Hello
` tag--cross the network and get revived on the other side.
Well, not *really* "cross the network".
One cool thing about RSC Explorer is that it's a single-page app, i.e. **it runs entirely in your browser** (more precisely, the Server part runs in a worker). This is why, if you check the Network tab, you'll see no requests. So in a sense it's a simulation.
Nevertheless, RSC Explorer is built using exactly the same packages that React provides to read and write the RSC protocol, so every line of the output is real.
---
## Async Component
Let's try something slightly more interesting to see *streaming* in action.
Take this example and press the big yellow "step" button **exactly two times:**
(If you miscounted, press "restart" on the left, and then "step" two times again.)
Have a look at the upper right pane. You can see three chunks in the RSC protocol format (which, again, you don't technically *need* to read--and which changes between versions). On the right, you see what Client React reconstructed *so far*.
**Notice a "hole" in the middle of the streamed tree, visualized as a "Pending" pill.**
By default, React would not show an inconsistent UI with "holes". However, since you've declared a loading state with ``, a partially completed UI now can be displayed (notice how the `
` is already visible but `` shows the fallback content because `` has not streamed in yet).
Press the "step" button once again, and the "hole" will be filled.
---
## Counter
So far, we've only sent *data* to the client; now let's also send some *code*.
Let's use a counter as the classic example.
Press the big yellow "step" button twice:
That's just a good old counter, nothing too interesting here.
Or is there?
Have a look at the protocol payload. It's a bit tricky to read, but notice that we're not sending the string `"Count: 0"` or the `