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.
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 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.
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.
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.
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.
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
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.
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)