DEV Community

Discussion on: JavaScript vs JavaScript. Fight!

Collapse
 
redbar0n profile image
Magne

It is a decision you make depending on how important initial load performance(MPA) is versus future navigation experience(SPA). Either approach has tools to improve their weaknesses but each is fundamentally tailored to optimize for their primary usage.

Doesn't it suck that we have to make that tradeoff? I mean, both features are really really important for any app or site.

Why are SPA's naturally better at navigation experience? Because of animations, and relying more on replacing components on the page instead of forcing a full page change?

What is even a "page", in this case? When swapping out components, fluidly updating/morphing the UI bit by bit, when would/should a SPA decide to change the URL?

It's so complex. I'm starting to appreciate that there is a fundamental mismatch between the concepts of "documents/pages" and "application" which cannot simply be padded over by frameworks/libraries. The problem is exacerbated by the fact that to a consumer there isn't any material distinction between the two concepts (it's just a GUI), and sites often evolve into apps, as consulting clients want more and more features. Thus often invalidating any sound architectural trade-off one might have made in advance. In addition to the architectural complexities needed to be juggled by developers, who mostly really just want one single uniform way of working, which "just works".

Collapse
 
redbar0n profile image
Magne • Edited

Also, when reading about Resumability vs Replayability (re: Qwik), I wonder how approaches like Hotwire Turbo, which you mentioned, compare? Both Qwik and Hotwire seem to be DOM-centric.

Isn't Hotwire basically already "resumable"? Since Turbo Drive circumvents the need for rehydration, Turbo Streams reuses the server-rendered HTML, and Hotwire Stimulus also stores state within the HTML, and doesn't perform heavy bootstrapping or reconciliation? And how is Fine-Grained Lazy-Loading different from Turbo Frames?

Maybe @adamdbradley , @mhevery and/or @excid3 , @julianrubisch , @javan , @kirillplatonov could help clarify the similarities and differences between Qwik and Hotwire (specifically its Turbo and Stimulus parts).

Collapse
 
ryansolid profile image
Ryan Carniato

The difference is that Qwik isn't saying everything needs to be rendered on the server. You can mix Turbo with Stimulus and get elements of this. But Turbo is more just a means of HTML partials. You could use that with any of the partial hydration approaches to have benefit of not shipping js to render it. But Marko or Qwik(and really modern js frameworks) are designed so the shift between hydration and client render is seamless.

I will say this html centric is a bit overplayed. I agree from a transport perspective but that can be in the form of inlined script tags etc.. unless you can solve double data which these aren't claiming you can achieve the same without resorting to what can be serialized as an attribute.

In general for what it is doing you can compare these with Turbo and all, but when it comes to app mental model and DX these are worlds apart. One feels like layering a Javascript app on server rendered views, the other feels like authoring a single app experience like you'd find with any JS Framework, ie React etc.

Thread Thread
 
redbar0n profile image
Magne • Edited

Thanks for clarifying it a little bit.

The difference is that Qwik isn't saying everything needs to be rendered on the server.

Hm. But Qwik, being an MPA, has server-side routing, like you said. It's client also focuses on "resuming" HTML rendered on the server, and is apparently also completely stateless. So why wouldn't everything be rendered on the server, then? What would be beneficial to render on the client? Animations (like on entry/exit)? Being stateless, does Qwik ever render anything on the client? To me it seems (from their github description, and blog posts) that SSR is not only Qwik's focus, but its fundamental mode of operation. I can't see how replacing sections of the DOM with SSR'ed HTML is any different (or more client-side rendering) than Turbo?

unless you can solve double data

What do you mean by double data?

when it comes to app mental model and DX these are worlds apart

Yeah, the mental model seems quite different, and I appreciate that authoring components in React/SPA style is preferable DX to having HTML template partials which later has to be targeted from JS (Stimulus).

Thread Thread
 
ryansolid profile image
Ryan Carniato • Edited

They have client state they just put it in the HTML, keep on reading and reflecting it back. Again I think they are just overplaying the it's just HTML thing a tiny bit. It's really no different than anything else other than by keeping it in the HTML they can pause and resume the same page multiple times (like if stored in service worker app cache). Of course limitations here around serialization and after render performance (constantly reading from the DOM/reflecting to the DOM is less than great overhead). One could obtain resumable hydration without doing anything HTML specific. All you need to do is serialize the props to every component to get the same. Of course that is wasteful, so a better approach is to look at it like a reactive data graph. Qwik does that and so could anyone else not doing exactly what Qwik does.

Qwik's approach could apply to a SPA with some refinement but they aren't focusing there right away. And I imagine given Builder.io static site generation will be the starting point. But look at their TodoMVC example Misko tends to show off. That is a very client side application. Each new Todo is client rendered, each removal and edit as well. Qwik only loads the event handlers you need and in so has incredibly granular hydration. But you write your TodoMVC app like a TodoMVC app for the most part. Think of Qwik's state more like a global store with Dependency Injection.

Qwik isn't doing HTML partials. It doesn't replace things from the server. I'm not sure where you got that idea from. It just hydrates out of order. So you could put a client side router on the page and not load it until someone clicks a link, yet before that happens do something interactive on the initial page. This is very applicable to SPAs. Now you could use Qwik with something like Turbo.

The "double data" problem is that technically the data gets serialized twice with hydration. Once in the HTML (ie your manifested view) and once as the data you bootstrap the framework with. I've seen experiments to extract the data back from the manifested view but that has big limitations, and it isn't what Qwik is doing. Their data in the HTML is just the data you may have serialized a different way and is still independent of the manifested DOM.

Thread Thread
 
mhevery profile image
Miško Hevery

I was going to reply but, Ryan you are doing a great job, thank you.

Thread Thread
 
redbar0n profile image
Magne • Edited

Thanks for a good and candid response, as always, Ryan.

To complicate things a little bit further..

Compared to Turbo, you said:

The difference is that Qwik isn't saying everything needs to be rendered on the server.

In general for what it is doing you can compare these with Turbo and all, but when it comes to app mental model and DX these are worlds apart. One feels like layering a Javascript app on server rendered views, the other feels like authoring a single app experience like you'd find with any JS Framework, ie React etc.

I recently discovered Inertia.js. Which is like HOTwire Turbo (aka. Turbolinks) but sending JSON over the wire containing props data and the the name of the client side JS-component to inject it into. So that you could develop an SPA (using React/Vue/Svelte), with the same app mental model and DX, though also avoiding having an API. You get this by letting Inertia.js hot-swap components and data (props), and let the server take care of the routing. So it occupies this weird spot between an SPA and an MPA, since it, like Turbo, will "serve an MPA off a single page", as you said.

So Inertia uses client-side rendering (CSR/SPA) by default, and like Qwik is also saying "not everything needs to be rendered on the server". (Even though Inertia can even do SSR now.)

So how is Qwik different from Inertia.js?

To try to answer my own question:

I guess the main difference is that Qwik is able to Resume hydration of the initial HTML the server sends. While Inertia relies on React/Vue/Svelte to hydrate it by Replaying the application state on bootstrap / first page load.
So Qwik would have a faster initial Time-To-Interactive (TTI).

As you said:

But Marko or Qwik (and really modern js frameworks) are designed so the shift between hydration and client render is seamless.

So: partial page hydration.

But for subsequent interactions, then I guess it wouldn't be much of a difference. As Inertia doesn't do what NextJS SSR does. From what I understand, NextJS SSR serves a new page and then completely re-hydrates (replays) that page on the client, on every route change. (Unless using next-super-performance, which is a proof-of-concept that apparently does partial page hydration). Inertia, on the other hand, wouldn't need to re-hydrate (replay) components it swaps out, because it is responsible for rendering them in the first place.

Now you could use Qwik with something like Turbo.

It would be interesting to see an app that used Qwik with Inertia.js. It would, if my analysis is correct, speed up the initial page hydration, by allowing it to be SSR'ed and then simply Resumed on the client, allowing faster initial TTI. Inertia would then take over for subsequent page changes.

Feel free to correct me if I'm wrong anywhere.

Thread Thread
 
redbar0n profile image
Magne

On another note, @ryansolid:

It would also be really interesting if there existed a Inertia.js adapter for SolidJS.

Thread Thread
 
ryansolid profile image
Ryan Carniato

The thing with Next and most SPAs is that they are fast after the fact because they render in the client. THe server only renders initially for the first load. The main awkward part of SPAs is that initial payload/hydration. You could make it work in MPA mode where it went back to the server but those frameworks don't optimize for that in general.

Now like we are seeing with React Server Components or Turbo there are cases where rendering partials on the server after the fact is beneficial, and in so those partials can also be partially hydrated. Which is interesting. But as soon as you have the ability to move this to the client the performance is better with client rendering. But there are other considerations here. I feel that thinking of this in a more microfrontend scope is where things like Turbo have more value. Or the fact that server rendering can save you from implementing an API.

But raw performance, client side rendering is probably going to win once you have the necessary JS in the browser. This is important. An impossible task on first load, but not so much after. You can of course do stupid things and cause waterfalls in the browser but just generally I see Turbo as a way to keep the MPA mentality going for client side routing. But other than the reasons I mentioned above SPA taking over is probably fine at that point. It's just harder to break things up in that manner.

So I'm still unclear where this goes. But there are a few things being overestimated right now. Lazy hydration just defers expense. You need to do it carefully. Like if a SPA did this and you clicked deep in the tree it would need to then load the whole page and hydrate, which would be terrible for the end user. Qwik avoids this by isolating the components hierarchically, whereas Marko today and Astro solve it by breaking off Islands. But naively implemented if you were to use Qwik in a SPA and you navigated since it suddenly finds itself needing to render everything in the browser it would be downloading a ton of small granular chunks that would have been better served as a single split bundle.

So if attacking this holistically I'd look at something in the middle where we can make the bundles large enough to not die by a 1000 slices but also defer work needed for hydration as needed. This is what we are doing with Marko and why we have a different focus. Because honestly while Turbo is probably good for the big stuff(page navigation) it sucks for the little stuff(small interactions). And might not even be needed for the big stuff if we do this properly except in those cases I was talking about.

We are also working on a streaming microfrontend setup with Marko that I guess could be Turbo-like but it's still something that you go to when you need it. But for now these are separate things and we are working on making Marko produce the smallest possible bundles and are in the process of implementing resumable hydration. Qwik is working towards it's similar goals. As is even Astro. I think we all acknowledge Turbo is a path but it's a bit longer term because there are so many gains to get already before going there and adding that complexity. Inertia.js is fine and all but I feel it is working from the assumption client frameworks have to be like Vue or React.. they don't.