Making sense of React Server Components(joshwcomeau.com) |
Making sense of React Server Components(joshwcomeau.com) |
– A bunch of smart Facebook engineers with unlimited funding decided to mess around and see just how much they could upend all the existing web paradigms, before finally concluding that conventional wisdom and the traditional server-side rendering model exist for a reason and make sense.
– The entire software industry decided that every new experiment by this above group was worth ripping out and rewriting their entire codebase over, whether it made sense for their needs or not and whether it actually made their product better or not.
I have not been too deep into the weeds of it myself but have been interacting with FE teams at my company enough to see what a charade it all is.
"This React thing is cool, and everyone is talking about it. Let's move some of our existing JS components over to it and see how things turns out."
"We need to do a top-down rewrite of the entire app in React."
"We're going to need the infra team to set up a GraphQL API layer for us. It can't be that hard, right?"
"Hmm, initial page loads are too slow. Hey infra team, can you set up a render farm of Node.js servers so we can do SSR?"
"Whoa, functional components are cool! We need to do another top-down rewrite and get rid of all the silly classes."
"We have a small team dedicated to investigating server components, and if the prototype looks good then we'll switch over to Next.js and rewrite our FE + API stack."
I'm sure my company wasn't the only one that caught this virus. I shudder to think just how much money and productivity has been wasted industry-wide over the obsession with this framework.
The rest of the snark looks silly in context of the above two points.
I personally love vue and single page templates (I hate jsx)
SSR is stil a good option i think for complex apps
React was more of a replacement for jQuery/BackboneJS/MooTools/Angular/etc than a replacement for PHP/JSP/ASP/etc
Hooks are for stateful code.
If you're curious:
https://github.com/facebook/react/blob/main/packages/react-c...
https://github.com/facebook/react/tree/main/packages/react-s...
Also, links to packages with 5 line READMEs and no comments/documentation in the code might as well be called blackboxed. Yes, its open source but that's pretty much it. John or Jane Doe will have to deconstruct and/or assume the team's intention by combing through your links, https://github.com/reactwg/server-components/discussions/5 and the (light) documentation from Vercel on how all this stuff really works.
All I'm asking for is clarity and the ability to potentially create my own implementation rather than watch the "big guys" (read: frameworks) get the advantage by having access to information we don't have.
I am left with such confusion as to how React/Next arrived at this solution. `useEffect` didn't run on the server when doing SSR, great, easy enough to understand. Now several common hooks don't run on the server because they are considered interactive? And it isn't that they won't run, their initial pass in most cases doesn't do anything, it is that Next now yells at you for having a `useState` hook in a "Server Component".
I would like to write a component, not have to think about where it is running in most cases, and go on with my day. I mostly use React for large SPAs and have adopted Next for doing SSR on the few pages that I needed it for (SEO, etc).
This is not true/it’s misleading. What you need is to be able to fetch on the server, and hydrate on the client using data from the server. This is possible with SSR and injecting into the React stream, using isomorphic (client) components.
The article is great, but the reasons for RSC are more subtle/complicated than the flow of the article would imply.
The relevant isomorphic data fetching hook source code is here: https://github.com/xixixao/convex-hosting/blob/main/app/hook...
Also good for those moving away from React, since we understand React too. Probably won't change it, but may give us things to look for in our new frameworks. A former coworker who was really into React just switched to Flutter.
I'm not sure I'd use Flutter for the web yet but I definitely prefer it to React Native.
RSC locks you into their hosting solution and the way they suggest you to use it also locks you into NextJS way of building your backend data layer (ie PHP/JSP/ASP-like with our without MVC)
But their routing things locks you into NextJS as a framework. Just remember how much people complained about migrating from react-router v4 to v5 (or was it v5 to v6?). Migrating from a routing solution is a huge pain in the ass. I am still unconvinced that file-system based routing is better. It is at best a replacement for lack of guidelines in most routing libs on how to organise your components for routing.
I am also very weary of anything that takes over the bundler, bundling is a hard problem and just outsourcing it is asking for trouble. But I understand why a lot of people don't want to deal with it, it takes a huge codebase for bundling to become a big issue.
I am looking forward to Bun: https://bun.sh/
Which promises to reduce the tooling headaches to get a project going without using a bunch of frameworks. Their bundler though is still nowhere near ready so I wouldn't recommend it for frontend projects yet. Using Bun just to run nextjs/webpack/whatever on top of it also doesn't seem like a good solution to me.
Bun might support RSC in the future, but in my opinion RSC is a very specialised tool to be used only in a few critical places in your code and it is scary how Vercel is pushing for it so hard in their docs. You have to remember that the average new frontend developer now uses "create-next-app" not "create-react-app" and is introduced to RSC right away
I'm confused by this -- how is a framework supposed to offer routing in a way that doesn't "lock you in"? are you "locked into" React Router if you use that? what's the alternative?
> RSC locks you into [Vercel's] hosting solution
how? you can self-host it if you want.
Every dependency if large enough has lock-in. The problem with NextJS is how coupled everything is. In NextJS if you decide to stop using it you need to also rewrite your routing. React-router doesn't care about your bundler, or your deployment flows or your use of RSC, etc.
https://joshcollinsworth.com/blog/antiquated-react#server-si...
This blog was posted to HN a few weeks ago. The author does a really good job of summarizing React's relationship with the rest of the SPA ecosystem.
I don't understand how this is acceptable to the React user community, that only Vercel gets to be in this privileged position. Why did they not include other companies, or the users themselves, in the discussion and development of this feature.
> Since Server Components are a new React feature, third-party packages and providers in the ecosystem are just beginning to add the "use client" directive to components that use client-only features like useState, useEffect, and createContext.
> Today, many components from npm packages that use client-only features do not yet have the directive. These third-party components will work as expected within Client Components since they have the "use client" directive, but they won't work within Server Components.
The arrogance of expecting all third-party packages, React-related or not, to buy into the "use client" directive. A humbler approach would have been a "use server" directive to opt-in to this behavior, instead of expecting the entire rest of the ecosystem to adapt to it.
> But hm, how exactly would we do this?
Couldn't help but chuckle. What mysterious technology could allow us to send an entire fully-populated UI over the web?
All sarcasm aside, having composable components server-side is only a good thing. I welcome it.
WebAssembly? Just kidding. But I think a lot don't realize that you could. It's pretty well supported now and a small WebAssembly library can fit in about the same size as a full page photo.
complex: 2
confusing: 3
And then someone realized that browsers had long since reached standards parity, and javascript engines were getting really fast, and re-rendering the DOM was GPU-accelerated, and we just needed a simple little library. And Angular and React were born (and Ember but we don't speak of satan in this house).
React feels like it's following the exact same trajectory. It was created out of necessity, it was the best framework for a given point in time, but it's being stretched to handle the changing landscape, and it's feeling very kludgey. I expect that soon, very soon, someone will release the new paradigm. Probably just as soon as WASM is able to manipulate the DOM without JS hacks, which has been on the roadmap since before there was a road.
A useful feature that an insane majority of React devs have come to prefer is "downhill."
Kids these days.
I agree that useEffect is bad.
But there are other SPA frameworks that do things better. In fact, just about any other framework does things better.
I don't feel that React has reached that tipped point yet, but when it does I'm hoping we'll all move to Sveltekit or something like it.
SvelteKit is the first piece of web tech that I actually enjoy using. It’s the rare piece of tech that knows its lane, stays in it, and excels at it. It is marked by fewer features that work well together vs lots of features that sort of work well together.
You can read the docs in an hour or two and then start to get an understanding of what it’s doing.
Basically, it feels like it was made by someone who really understands the medium, thought deeply about how to distill that, and iterated to improve on it. (I get same vibes from Vue, but don’t have much experience there)
So far it seems really amazing even for the most complex apps. I’ve built 5 nontrivial apps with 2 being 2+ year projects
I haven’t found a “ceiling” yet
Really? What caveats?
The only maybe oddity that I can think of is that if the deps array is omitted it always runs.
What API would you prefer? Something more like signals?
useEffect breaks the sequential nature of your code and makes it feel like you're reading a script from a Quentin Tarantino movie.
I recalling using a lot of useRef to manually tune when to re-render, and in an ideal world I shouldn't be this granular about it.
You can certainly use one of the many React map wrapper libraries to lessen the pain, the pain is still very real, just dealt with by someone else.
> What API would you prefer? Something more like signals?
Yes. Or full tree redraws like Mithril.js did 8 years ago.
Google isn't even good at searching recent documentation, from my experience.
As much as the React team tries to push back on it, Vercel marketing has become synonymous with React's direction.
Most people do not need a single thing that Vercel/Next.js thought leaders are trying to shove down their throats.
Unless you're competing with Facebook for SEO rankings (good luck) the 1KB you saved by not shipping an un-interactive component down the wire is nothing.
They're actively pushing a set of best practices that align with their hosting model and building a moat around said hosting
—
Also despite the intentionally confusing wording, Server Side Rendering does work with Client Components!
There are literally *hundreds* of posts where people are unaware of that because the directive chosen by the React team was "use client"... which naturally leads people to believe "use client" will opt out of SSR.
—
To me that choice, and not allowing a project wide disabling of it, were the last straw that took it from innocent misalignment to intentional.
By having the thought leaders sell these heavily server involved features they've create a situation where technically you can move your Next.JS project anywhere... there's just a scary (for those who don't know better) laundry list of things that won't work unless a given provider goes out of their way to enable it.
They get to play both sides: Next.JS works anywhere (as long as you ignore a bunch if features that we sell as being very important to making a good site)
I've tried to explain our current terminology here: https://github.com/reactwg/server-components/discussions/4
you can keep doing this, just put "use client" in the file, Next will SSR them just like it used to. (SSR is an "emulated client" in this model. admittedly, the naming is a bit confusing)
but for some things, you want a guarantee that it only runs on the server, and then you can use server components :) i believe this is also why useState & friends are disallowed there -- they fundamentally don't make sense for stuff that'll only ever run server-side.
Child of a sibling comment has more details: https://news.ycombinator.com/item?id=37408918
Money is to be made on both sides which is why this relationship exists.
The directive marks where the transition from server to client happens. If you aren't already starting in a server component, then you don't need to do anything to use client features.
This is a deceptive reply since the only RSC implementation in existence starts from a server component.
That means that having React State, a basic building block of React that's leveraged massively across the ecosystem, is broken by default until the user adds a "use client" directive.
They're 100% right that "use server" should have been the directive, and more specifically it should have been something like "use non-interactive". Server vs Client is already widely understood to be tied to SSR in a completely orthogonal manner.
RSC has a much narrower value proposition that shouldn't have gotten muddied by all this.
AFAIK: the initial stages of the project involved Vercel and Shopify [edit: and Gatsby]. these companies maintain frameworks and hosting solutions for them, which makes them good partners to work on full-stack features like this. notably, the convention ("use client") came about based on early feedback from Shopify.
the reason to work with framework authors first is that RSC requires a high degree of coordination/wiring across client code, server code, and the bundler. React itself only implements a couple of low-level primitives that need to be wired together by something like a framework to actually do anything useful
> expecting all third-party packages, React-related or not, to buy into the "use client" directive
AFAIK: non-react code has no reason to care about this directive. and on the react side, couldn't you make the same complaint about most breaking changes?
This is...weird. How did Vercel manage that?
In short, it's because Vercel invested engineer-years into piloting RSCs as part of a production framework and making Next the guinea pig. Next wasn't actually the first framework to try RSCs - Hydrogen (Shopify) and Gatsby both tried early versions of it and provided feedback, but Next is the first to implement it to the point that it can be considered stable.
The long-term goal is that RSCs aren't a Next-only concept - for example the Remix team said that they wouldn't implement them until they became more stable (https://twitter.com/ryanflorence/status/1678416472010260480), but are working on it now (https://twitter.com/ryanflorence/status/1697253955376722092). But the React team has always historically favoured trying new features in production to iterate on the API (this happened a lot internally at Meta), and this was an opportunity to do that.
This is easy to miss, so you install an eslint plugin to warn you about missing dependencies.
This causes the opposite problem - adding dependencies unnecessarily to silence the warning. Then you need to ensure those dependencies are stable to avoid running the hook on every render. Or ignore the warning with a comment, and you’re back to square one.
It’s all too easy to write an effect that either doesn’t run when it should, or runs when it shouldn’t. This is especially true for inexperienced developers.
- Don't use effects for things that aren't genuinely effectful
- Put finnicky state management stuff in reusable utils instead of re-implementing it from primitives over and over again.
Neither is React-specific.
That's a bunch of examples where there are good alternatives.
GraphQL doesn’t do a damn thing to help you optimize your database calls. It also doesn’t do a thing to help with security or preventing DOS-inducing queries, which is the first question anyone who’s been around this block before (exposing remote query interfaces) has about it.
Some big frameworks that happen to speak graphql will automagic that stuff for you in simple cases, but we don’t claim e.g. the capabilities of the Rails framework with rails-api et c. as capabilities of REST or SQL or whatever.
This stuff is really annoying because you get half-technical leadership (or non-technical) thinking graphql is magic and that it’s good for all kinds of things it’s not and that it’ll save lots of time that it won’t, between the hype and the name.
Maybe it does those well, maybe not, but if you expect it do anything else, you'll certainly be disappointed.
And the comparison is valid because these optimizations do happen and do make a difference, and are pretty easy to get if you just for example choose Hasura which is a very popular library (and there are many many others too). GraphQL makes this possible in the first place, whereas RSC and REST basically don't.
I am a big fan of GraphQL, but it is no magic bullet. One of the biggest pluses for me is that it replaces Swagger with something that is sane. People often complain about GraphQL requiring a lot of tooling and libs to get working, but if you make a REST api with Swagger support you either have to maintain a swagger spec manually or have just as much (and depending on the language, more flaky) tooling. I think the only framework/language that made swagger reasonable to use is C#.
Also technically GraphQL is inherently more powerful than REST apis, so you can (technically) make a non-connected GraphQL api that mimics how a REST api works (no going from one model to another without making a new query). Losing a lot (but not all) of the advantages of graphql but keeping all the advantages of REST. Nobody does this of course because it is reaaaaallly nice to have that extra power.
Edit: I'm just happy to find something that I like to make web stuff in. Now I can make some apps and actually enjoy the process.
Easiest way I put it to my team was that in order to qualify as a hook, it must consume a hook.
const [sortedValues, setSortedValues] = useState([])
useEffect(() => {
setSortedValues(values.sort())
}, [values])
It is unfortunate that useEffect is too powerful of a escape hatch that people forget it IS a escape hatch and should be avoidedTo be honest I much prefer hooks than old class-based state manipulation, but it does require deeper understanding of the React, JS and state to get right.
Vue3/composition API -- probably the simplest if you're coming from React. Definitely feels like it was inspired by hooks.
Svelte/store -- svelte has a term called 'hook' but it's unrelated. The equivalent would be Svelte's store.
The other frameworks use something called 'Signals' (Preact, Qwik, Solid, Elm and even Angular/backbone though I don't personally have in depth knowledge of all of these).
IMO, I would prefer Svelte if you are adventurous, or Vue if you like React but just want something like 'better React'.
The data flows in one direction: from the server to the client. It's just physics — your server is where the data is. You're trying to move it to the client. So the code that needs to run first is the server code.
This is why the data flow starts at the server. You can't fix this by changing how opting in works, or some other API design workaround. What you're fighting with is the arrow of time.
As for the terminology, I agree it is confusing, but I have not seen a better suggestion yet. I shared our reasoning here (https://github.com/reactwg/server-components/discussions/4), hope it's helpful.
Except people not wanting to do that mental shift was largely why they chose Next.js over a framework like Remix.
Choosing a default that breaks things (forcing npm packages to label their components...) was way too heavy handed considering the tenuous nature of RSC as a whole with its single implementation at this point.
Making client components default (as they have been since the dawn of React) and saying something like "server only" would have done the trick.
But with the directive "use client" you hit the mental pathway that people have around SSR. It adds just enough friction to feel like you're missing out even though it's almost orthogonal to the concept of SSR.
Suppose that, as you say, client is default, and marking a component as server is opt-in. This means that in any given tree, a server component can appear in the middle and be rendered by a client component. That, in turn, means that any time that client component re-renders, the server component rendered by it need to refetch. But that server component may render another client component under it, which may render another server component (under the design you’re proposing). As a consequence, a simple state update in a client component somewhere can trigger an entire chain of repeated client/server waterfalls just to resolve the resulting tree. This is not a feasible design.
The way RSC solves this problem is by (1) starting the data flow from the server, and (2) ensuring that during rendering, the data always flows in one direction — from server to client. This restriction avoids the client/server network waterfalls I’ve described in the previous paragraph. But it does require that client is opt-in rather than server.
Hope this helps.
I don't really agree with this except to the extent that, sure, it would be ideal for all APIs and indeed the entire syntax of your programming language to be so well-designed that you don't need any lint rules to catch common mistakes. I'd love it if my static type checker could catch most mistakes like this (although the boundary between linting and static type checking is fuzzy).
But in practice, you're almost certainly going to either 1) have a linter or 2) have strong resolve that you will not make any easy mistakes that a linter could catch. And if you're in the second boat, it doesn't make sense to single out this particular easy mistake, given that omitting arguments is always an easy mistake to make in JavaScript.
Which linting rule?
> That being said, I'm not sure that an API is well-designed if the only way to use it effectively is by forcing you to even know there is an eslint plugin to begin with, and then once you do know that, that means now you're being forced to add eslint to your project whether you like it or not. And sometimes eslint just stops working for who knows what reason, I have a developer on my team who's constantly dealing with issues where his eslint/prettier setup isn't running correctly.
Yes, not to mention eslint is slow with large codebases. Fingers crossed for the Rust(?) rewrite.
In my opinion if people have the rule installed and still feel the need to disable it then it shows there may be an issue with the API itself. This is coming as someone who has written React for a living since before even ES6 classes were supported.
I see it relatively often, including in production codebases where people just disable the rule because they don't know how to write the sometimes non-intuitive way to get around it.
useEffect is for side-effects.
Either way, this is different than what I mean with Mithril.js (it does full tree re-renders on event handler calls or when a manual redraw function is called).
Hooks imo are great, as they are pretty explicit and low level.
Nowadays signals are the new kid on the block, though mobx and similar have existed since forever in react land, but they have their own crazy edge cases like reactive loops and implicit behavior.
Hm, could you give an example of this ideal performant state management world? Angular? Vue? Svelte? MobX?
As another commenter mentioned, yes it's less low level than managing your own dependencies, but then again, most React apps are already wrapped in layers and layers of abstractions/context providers/selectors and use Jest as their test runner (which injects the global namespace with functions) and does other black magic and weird metaprogramming, so I think most React devs are already OK with less lower level stuff.
Sequential, blocking modells like i.e. batch ETL jobs are just not the domain of the browser.
The DOM is almost entirely sync, all the operations on it are blocking operations (including things like replacing an entire document with innerHTML).
Thing about async/await.
const a = await doThing();
const b = await doOtherThing();
The order of the written code and the order of the executed codes are two different things. Being able to write code in proximate/logical order is a enormous relief, even if things don't actually happen like that.Same with hooks.
function SomeComponent({ someProp }) {
function doStuff() {
// do stuff
}
useEffect(doStuff, [someProp])
}
This trivial example is easy to read, but as more state and effects get added to a component, it quickly becomes fairly cumbersome to figure out the exact order in which your code runs.And I can see why the majority of HN readers would upvote the top level comment. Particularly since it's a popular re-hashed opinion.
Top comments aren't a good survey of the opinions of a specific community for that reason.
Of course React devs will "prefer" it; they are the idiomatic way to adding state/triggering effects in React.
Hooks were nice when they came out, but woefully under-documented. The fact that decent docs for hooks first development came out literally just this year when hooks have been out for years is kind of a shame.
As opposed to useEffect, most other hooks are fairly straightforward, but the fact it years of articles upon articles of mistakes and documentation to explain useEffect to both experienced and non-experienced devs alike shows that it really wasn't that great of an API for end users.
In fact the most common topic nowadays is how much you need to avoid useEffect, especially if you're not a library developer. None of this was documented/mentioned when it first came out.
Yes it does enable powerful workflows, but plenty of other frameworks/libraries handle the same thing in a nice way with much less drawbacks than react. (ex: automatic dependency tracking, etc)
The number of times I have to tell people to stop putting things in effects at all, or that the reason something isn't working is a stale callback due to missing dependencies in my opinion shows that its not really a good API for the average dev to be using.
Devs use hooks primarily because hooks are better.
No, but the React ecosystem has almost collectively migrated away from class-based components or deprecated support for them.
> Devs use hooks primarily because hooks are better.
You avoid much of the dependency array issues by using class components. But you have to deal with `this` and it's significantly more keystrokes. It's more of a tradeoff in my opinion.
The classes had closely related functionality spread out over several different methods making them hard to understand at a glance, function based components are very concise.
I think people who dislike hooks tend to overuse useEffect (it's rarely needed) and to do too much inside components rather than inside custom hooks.
I strongly suspect most HNers who are overly critical of React (and/or general web framework complexity) have never had to write and then maintain a large web application in jQuery.
I don’t think application state belongs inside the rendering machine.
Full of (ex?) React devs. There are way better things out there now if you are starting a project from zero.
The only downside is that sometimes, the rule can end up being overly aggressive and tell you to add things which you explicitly don't want in your dependency array, but it's helpful probably 95+% of the time and not hard to opt out of or work around as needed.
As you already mentioned, it's not a silver bullet.
What that compromise would have done however is caused significantly less adoption, because "out of sight, out of mind". Most people would not opt into shared/server since RSC is not providing enough value for them: They don't mind the waterfalls, they're stuck in their ways, and above all they highly value reactivity bring sprinkled everywhere since that's what they were all running away from in template based frameworks.
I think where we likely won't see eye to eye is if that was a fair decision to make. I think especially given the singular implementor, the React time should have left RSC as a default disabled and left the uphill battle to Vercel to convince developers to opt into RSC in their own bundler.
Forcing NPM packages to need to think about appending "use client" to be is the smoking gun that this was the wrong move.
That's why using any interactivity in a component default breaks until you add "use client". The alternative would have been to have state, a core concept of React, work correctly by default, and make server/shared components explicitly marked.
I agree with your overall sentiment though.
React does not have routes or caching. It has about as much management as jQuery.
>But I don't think the "pretend it isn't" makes any sense
Well, let me expand a bit more. React's pitch is that view is a function of state, and essentially designed around the idea of an immutable state which re-renders everything when changed. It then offered some APIs to allow fine-tuning of rendering via hooks which either ties with data changes or some rendering life-cycle. To me the API screams of stubborn refusal to let go of the "view as a function of state" mantra: When you encounter a scenario where it breaks the mantra, lets add another lever to handle it. This lever also has to be pulled by you the developer, and it is up to you to know when to do it.
>I actually like React more than Svelte for many reasons, but magic compilers and templates are two big ones.
I don't really have an issue with compilers. They are the accepted magic that bridges between language for people and language for machines. The ideal language might be something that is functional and immutable language that it is easy for us to read, but compiles to the optimized, imperative updates that machine can run well.
I'd probably like React better if it had a compile layer on top of its more functional parts?
As for templates, JSX is probably the one great thing that came out of React. It is no wonder that many other frameworks are embracing JSX, but none of them are really forking the idea of hooks.
Lifecycles existed since the first version, and they were always absolutely critical. Hooks just fixed many of their issues and made them more elegant, composable, and simple to write.
If it quacks like a duck..
Also, devs don't use hooks primarily because they are better. They use them because they are idiomatic React in 2023.
yes, exactly!
> They use them because they are idiomatic React
right, and do you think it'd be easy for a feature to become idiomatic if it wasn't an improvement over the previous patterns?
:hmm: