React2Shell Made Me Rethink React Server Components
The React2Shell vulnerability made headlines recently, and while the security implications are serious, it got me thinking about some deeper frustrations I’ve had with React Server Components. Not the vulnerability itself, but the broader lack of compile-time safety that’s been bothering me since I started using RSC.
The TypeScript Problem
My biggest gripe with React Server Components from a DX perspective is that TypeScript currently can’t check for boundary violations at compile time. You can add a "use client" directive somewhere and completely break your app, and TypeScript will happily say everything is fine. You have to actually navigate to the affected page in your browser.
This is a massive step backward from the type safety we’ve come to expect from TypeScript and React. We’ve gotten used to catching bugs at compile time, but RSC brings us back to the world of runtime errors.
I’ve lost count of how many times I’ve refactored a component, moved something around, and then discovered hours later that I broke a page I didn’t think to check. The lack of compile-time safety means you either need comprehensive tests for every page or you’re relying on manual testing to catch these errors.
Example Problems
A big source of errors is when a component is rendered as a Server Component and as a Client Component.
Adding Server-only Code
Let’s say you add a database call to this component and you only check the route that renders it as a Server Component. You won’t notice the issue in development unless you visit the other route. You’re lucky if running a full build catches it, otherwise it’s up to any end to end tests you have, or worse: your users.
Adding Client-only Code
The opposite also happens regularly, where you modify a component to use client-only APIs and only test the Client Component route. You might not notice the issue until a user tries to access the Server Component route and gets an error.
Adding "use client" at the top of every file that uses client-only APIs is not always a great option. Now you can have eslint warn when you are passing in non-serializable props.
The whole thing feels like you’re walking on eggshells. One wrong step and it will crack!
What I Want to See
I don’t think RSC is fundamentally broken, but the developer experience needs work:
Compile-time Boundary Checking
TypeScript should error if you try to import server-only code into a client component, or vice versa.
Imagine if adding "use client" to a file that imports server-only code gave you a red squiggly line immediately. That alone would prevent so many bugs.
Explicit Execution Context
Make it clear where code runs. The current “it depends on who imports it” model is too implicit. Being able to sprinkle in "use client" anywhere is powerful, but sometimes being more restrictive ends up being more powerful.
Better Build-time Validation
The build should fail loudly if you’ve created an invalid server/client boundary, not just warn or silently generate broken code. I’d rather have my build fail than discover a broken page in production.
Final Thoughts
React Server Components promise a lot: better performance, less JavaScript shipped to the client, simpler data fetching. And in theory, they deliver on some of those promises. But the lack of compile-time safety is a serious flaw in the developer experience.
We’ve spent years building better type systems and static analysis tools to catch bugs before they ship. RSC feels like a step backward in that regard. Until TypeScript can actually verify that our server/client boundaries are correct, we’re all just one misplaced "use client" away from breaking production.