Hydration is pure overhead
builder.io
builder.io
Is the idea to take advantage of the server's horsepower to get something on the screen fast by sending pre-rendered HTML and then wait while the browser runs the code to basically re-create the page for interactivity?
(it's been a while since i've done traditional front-end web dev)
Servers are often weaker than many consumer computers anyways, so I don’t thin it’s because it can render faster than your own browser.
Isn't this the other way around? I mean a server is supposed to be fast enough to handle a lot of requests.
Same reason DDoS attacks are generally more powerful than DoS attacks.
Power in numbers.
Caching is not something individual distributed clients can do, which is why the server is the only way able to reasonably take on this role. You can also easily configure nginx to serve just-in-time caching.
I'm thinking about that Hacker News article, which had a laptop in the datacenter, because it was much faster on it.
For a decent fraction of applications a large chunk of the code that's written is to generate non-interactive parts of the app (data fetching, wrappers, component markup, CSS) and the actual code for event handling is relatively small. Hacker News, for example, is a pretty common framework demo since it's simple to write. You need a bit of JS on the comments page for the vote buttons and the comment collapse but if you write HN in a natural way in most component oriented frameworks the bundle coming down is considerably larger and includes all the code for rendering the comments (for example) even though the comments never change and most hydration approaches will put the comments in both the markup and embed a JSON/JS chunk in the page so hydration and the client side render can happen with consistent data. This doubles the size of the page with the overhead of the data half running through some subset of the JS code machinery.
In the case of Qwik, the idea is to do a server side render and then lazy load everything on the client as it's needed. At least that's my understanding, I haven't used the framework beyond a toy project. There are other approaches but to use the HN example, you'd never download the comment collapsing code if you didn't click a collapse button.
Rails has experimented with this sort of thing in the past; it requires some deep integration with the HTTP server so that clients can request updates to specific chunks of the UI rather than just URLs.
Marko: https://www.markojs.com Astro: https://astro.build
Astro, for example, is very conceptually close to PHP. Your code runs on the server, there's a code section where you grab content from the DB and a markup section where you template out the page, the lifetime of everything is one request, etc. What's changed is that you can put an attr on a component indicating you want that component sent to the client. No attr, it's old PHP, attr it's jQuery, and further the send can be triggered can be when the element enters the viewport or when it's clicked.
In the in-development Marko 6 runtime distinguishes between url/db derived "settled" data and client side/event handler state and only sends the latter to the client. If you have a paged list (for example) you can set up the prev/next page buttons to be links and you wouldn't have any JS sent down to the client despite the app being written with current SPA component ergonomics. If you'd rather have the data fetch and render client side you switch the data declaration from a const (dervied/settled) to a let (state) and all the rendering JS gets sent down.
I'm simplifying/omitting stuff but these are new and useful takes on old ideas. I think better takes on old ideas are some of the more effective advances in the state of the art.
But even then, it's definitely still a problem vs sites that don't have to be hydrated. Some people see this approach as a best-of-both-worlds, but in reality it's still a compromise that has costs
Just the same ordinary reasons to generate HTML on the server: it allows you to support HTTP caching (in a CDN or even browser caching), it (potentially) lets the browser start rendering content much sooner, and it will be viewable by user agents (bots, scrapers, search engines, etc., but also humans) that don't run JavaScript.
Not the DOM, but the virtual DOM. Instead of inserting elements (which is expensive), it can just walk the existing server-rendered DOM and attach listeners.
Where it gets technical has to do with how the JS gets delivered to the browser. It starts with a small bootstrapping that sets up event delegation, sort of like `document.documentElement.addEventListener('click', becomeAwareOfClicks)`. This happens literally on the first chunk of HTML being streamed in, meaning that the framework is now already aware of clicks happening anywhere in the app, even before HTML <body> is available in JS.
Eventually some HTML will stream in with an attribute that indicates how a click event on an element should be handled. The framework can then quickly determine whether it needs to "hydrate" that event handler: a) if the event was captured by `becomeAwareOfClicks` and b) the intersection observer deems that element is visible and available to DOM manipulation, then c) it can download the relevant handler, meaning it triggers business logic at latest as soon as the intersection observer downloads the handler.
Note that at this point, no other JS has downloaded yet. Eventually it does download it, like every other framework, but the key point is that it can respond to events with actual business logic before the rest of the JS comes down the pipe.
The reason for the double work is that the context here is SSR/SSG:
«The re-execution of code on the client that the server already executed as part of SSR/SSG is what makes hydration pure overhead: that is, a duplication of work by the client that the server already did.»
In client-side rendering (CSR), there isn’t double work in rendering the HTML, since the client only does it.
Yes, the idea is to send pre-rendered HTML (by using SSR/SSG). The client doesn’t need to re-create the page/HTML, simply hydrate it (attach event handlers). But it is duplicate work and turns out to be a bit expensive to always do it on the initial load.
Hope that helps to clarify.
The SSR/hydration approach:
- Benefits SEO
- Usually benefits initial content on the screen
- Has a bunch of performance penalties after that as JS is loaded, parsed, and (usually blocking) recreates the data structure representing its initial state
Qwik’s approach (at least conceptually) skips that third point for everything until an interaction needs to respond to some state change based on an interaction, and does that with only the information needed for that event.
In a tweet thread, I described my mental model of this as effectively developing like I’m building an app where all the server/client UI code is shared, but the UX is like I wrote plain HTML with some jQuery or whatever to make a few elements interactive. What Qwik does is determine which parts of the server code need to be the compiled jQueryish code, but only calls it when needed.
An equivalent React codebase would re-render the entire page (well mount point) even if most of it will have no meaningful effect.
At the end of the article, there's his solution: https://qwik.builder.io/guide/overview
> Qwik is a new kind of web framework that can deliver instant loading web applications at any size or complexity. Your sites and apps can boot with less than 1kb of JS
Welp, that's reason enough for me to ignore him entirely. Especially on the topic of "overhead."
Angular was an inexcusable atrocity.
He probably knows much more than those who didn't create a major front-end tool.
The state of the art has absolutely moved on but provided you were willing to understand its model (just like these days you need to understand React's model) it provided a power to performance ratio that nothing else in its class was capable of at the time.
It's a framework that hated the idioms of its host language, as if the problem with front-end development was that it didn't have the ceremony of Java and the attendant abstractions/"patterns" of a static manifestly typed language. The conceptual overhead alone was ridiculous (as famously described here: http://codeofrob.com/entries/you-have-ruined-javascript.html ), and the payoff in terms of performance was negative on mobile no matter what we did. The fact that I had to know what the digest cycle was is a testament to the leaky nature of the abstractions. The tooling, wow, as far as I could tell batarang was actively broken for a good chunk of 2014 and 2015 and nobody had better suggestions.
I've used a lot of libraries/frameworks/languages with their own baggage but I've never had an experience where the differential between what I was hearing and the actual experience was that large, to the point where it's one of the first things I think of when it comes to the hazards of social proof.
If I wanted something that heavy again circa 2014, I'd tell myself to just use Ember. Hell, I'd rather use jQuery than Angular.
So unless you're one of those people who just are consistently great, maybe the secret identity of Fabrice Bellard, you may want to consider this before making immature statements like this.
Besides, inexcusable atrocities being picked up by large parts of the industry (hi javascript), are pretty common and one could bet that they afford much more learning and real world value than perfect academic solutions never used by anyone
This doesn't seem like a great tradeoff to me. Sure, maybe you save time during component initialization, but while that is happening the user is digesting the information anyway. Then once they make their decision to act, there's no extra delay to produce the next state. However, with a "just in time" event binding, now the user has to wait (slightly) longer after they've already made their decision, which seems worse.
It may not be the best possible set of trade-offs for any particular application but it seems like a set of trade-offs worth exploring.
In addition, it sets up an intersection observer. Then depending on when an event happens, it might require downloading that one event handler piecemeal if the event occurred early enough during page load, or if the event is late enough, the action happens instantaneously because the intersection observer already downloaded the handler in anticipation that the user would interact with the element, it being visible and all.
The trade-off is that the download of every other JS thing effectively gets deferred due to fragmentation of how JS gets loaded in the page, but the cleverness of the trade-off is that in typical scenarios, most of that deferred code is not going to be activated by the user in the first place (or at least not in quick succession so as to overload network).
"Something i wanted to mention is both the React team and nextjs team are aware of this and are working on a solution to address needing to load Javascript on the client. Its called React Server Components
React Server Components Documentation https://reactjs.org/blog/2020/12/21/data-fetching-with-react...
Nextjs Blog on Server Components https://vercel.com/blog/everything-about-react-server-compon...
Nextjs Documentation on Server Components (Alpha) https://nextjs.org/docs/advanced-features/react-18/server-co...
We can try it out today on a platform that supports a node environment. This is from nextjs docs. I have a few thoughts on Svelte, but just wanted to point this out!"
With Server Components, there's zero client-side JavaScript needed, making page rendering faster.
really?
c’mon could we please ask the React folks to stop making simple things hard?
But if you can hydrate granularity, and prefetch smartly (based on visibility, analytics, etc) things speed up a lot.
If done right, there is no delay on interaction, and a lot less time and resources required to load a page, increasing lighthouse scores and TI specifically
Thats what we’ve seen in the field too, the FAQs in the article link to some real world examples. Tho I can’t say our prefetching is as smart yet in practice as we want, so sometimes there is a delay on very first interaction. There is a straightforward way to improve this tho that we are working on
It's actually quite subtle. Sometimes it works, sometimes it doesn't, depending on which page you are on, what you've already clicked, etc. All part of the fun of frontend web development, ain't it?
I mean it's fine to have choice about this trade-offs but you can do it right now just by splitting your application into parts and hydrating only the part the user interacts with. Which gives you additional flexibility of automatically hydrating the part the user is most likely to use and hydrating others in the background in the periods of user inactivity.
Also this article focuses very much on event handlers, but main part of hydration is creation of dynamic structures that allow the application to re-render dynamically and efficiently, sometimes swapping out large parts of page contents that are not delivered with initial pre-rendered HTML.
If you really wanted to improve the situation one could work on introducing partial hydration on demand into React and work on ways to serialize most of internal structures of React apps like virtual dom, so they can be passed along with the pre-rendered HTML to make the remaining requests lighter.
Creating new framework is way less impactful.
We've been doing SSR all along, folks: server side templates.
Yeah, HTML was pretty hamstrung as a hypermedia, which made for mediocre UX, but that's been fixed by libraries like unpoly, hotwire, or, my own, htmx.
Yes we've had SSR, noone is disputing that, nor is it relevant. These are separate solutions for separate problems.
I agree some people reach for the wrong tools sometimes, but that's a universal problem.
Traditional SSR involved a separation between templates which contain presentation logic, and controllers for mediating business logic, persistence etc.
If your backend & frontend are in same language, or you use template engines with implementations in mutliple language like handlebars/pug/soy etc. you could easily render the same templates using JS and your client side can have as much ui state, interactivity etc. as you want.
If we adopt incremental enhancement then the fetching of templates can be delayed - we primarily need the controllers which handle dom events to make the server-rendered ui interactive. This is easily achievable through libraries like stimulus where controllers can add complex interactivity to server rendered templates and re-render them if needed through templates which are fetched on demand. We can even preserve form element states by using libraries like morphdom for swapping content.
However, what really breaks down all of the above is the concept of components as popularized by React etc. When we start writing react-style components then our rendering logic and associated behavior are tightly coupled and we need to pull in all the rendering logic for enhancing the server rendered content. React devs like to preach that traditional separation of concerns is not useful in practice and it is better to have rendering code colocated with behavior - but solutions like this just demonstrate that this separation did actually have some merit albeit at the cost of some indirection.
What solutions like Qwik are attempting to do is enabling folks to keep writing component oriented code but now we need a fancy compiler tooling that deeply integrates with the stack. The approach does have its merits but it is just one path to address the problem.
I wonder if it’s a suitable approach at all for those, because when they’re offline they won’t be able to lazy-load the JS code they’re still missing.
there’s no other explanation
it’s a capitalist game and if you want to stay sane, you shouldn’t play it
Faster initial load times for PWAs?
No conspiracy theory or capitalism rant needed.
this is how Wikipedia does it for the last 20(?) years and they sure won’t be changing that any time soon
PWA is a whole different topic
No it's not, as hydration is used to improve the initial load times for PWAs. Afterwards PWAs will load offline first.
> faster load times is achieved by not using javascript, (pre-) rendering content on server and caching with CDN
Yes you can make sites not using JS and should in certain scenarios, that's not relevant here.
> this is how Wikipedia does it for the last 20(?) years and they sure won’t be changing that any time soon
Wikipedia is a web site, not a web app. It works really well as a site and thus uses technologies meant for web sites.
this very sentence sounds absurd
how many websites out there need to work offline?
> this very sentence sounds absurd
Which part? "hydration", "improve the initial load times", or "PWAs"?
Let me rephrase if you are confused. It renders a snapshot of the app on the server so when you first load the web app it's rendered already. Then the client picks it up from there. It's completely optional to do this.
> how many websites out there need to work offline?
Are you asking if it's useful to have access to information and entertainment offline? For me the answer is yes.
It depends what you are making, but yes I think you should strive to make things work offline if you can.
Also websites can work offline without CSR. That's not really what this is about.
Hydration is about improving initial load times of CSR. I really don't know how to simplify this further.
That said, I think you might want to consider looking more closely at how Qwik works. It produces markup metadata that’s not dissimilar to what I see in htmx. I don’t know if it’s a direct inspiration, but that similarity seems particularly odd to dismiss so bluntly.
The major philosophical difference between the two is the authoring experience: Qwik annotates the HTML with a compiler, in htmx it appears the expectation is you write the annotations directly. Qwik’s server side templates just happen to be authored as JSX components. Both are completely valid! Probably more a matter of preference than anything.
Personally, I prefer the Qwik approach. But I welcome yours as well and encourage people who would prefer it to choose it. Both are significantly better, in many cases, for users than the current outcomes from many other frameworks which appeal to the devs Qwik is targeting. Isn’t that also welcome given the state of web dev today?
i've spent a bit of time today looking into qwik and I appreciate what they are trying to do
not my bag, but i'm obviously a contrarian in the web world