Qwik - The No Hydration Approach to Performant Sites

Rate this content
Bookmark

Let's talk about hydration and the problems it creates. Resumability is an alternative to hydration which does not force eager execution of client side code resulting in instant on applications even as the application scales in coplexity.

20 min
15 Feb, 2024

Video Summary and Transcription

Builder.io is a headless visual CMS that allows drag and drop of UI components. Core Web Vitals are important for improving website performance. Hydration in frameworks affects performance and interaction with the app. Qwik offers a different approach to hydration, eliminating the need for downloading unnecessary code. Qwik's resumability feature improves performance by starting with HTML and avoiding the re-execution of code.

Available in Español

1. Introduction to Builder.io and Qwik

Short description:

Hi, my name is Marciko Heveri, CTO at Builder.io. We work on Qwik, a new kind of framework. Builder.io is a headless visual CMS that allows drag and drop of UI components. We also have Party Town and a team of amazing people working on our open source.

Hi, my name is Marciko Heveri, I am the CTO at Builder.io, and I've done these things called Angular and Karma, and now we're working on Qwik. So let's talk about Builder.io for a second. Builder.io is a headless visual CMS. It basically means that you get to drag and drop your components into your existing application that you can install using, you can install Builder.io using npm install.

Okay, let me restart. Okay, let's start from here again. Qwik, no hydration approach to perform on websites. Hello, my name is Mischko Heveri, I'm a CTO at Builder.io and I've done this thing called Angular and now I work on Qwik, which is a new kind of framework I want to talk to you about today. Builder.io does a headless visual CMS. Headless means that it is hosted on your existing website where you can install it using npm install and visual means that you get to take your own components and you can allow your marketing team to do drag and drop of the UI and build websites without having to have any sort of coding experience. Another cool thing we do is called Party Town, and these are some of the amazing people that work on our open source at Builder.io.

2. Improving Performance with Core Web Vitals

Short description:

Today, I want to talk about Core Web Vitals and the importance of reducing JavaScript to improve performance. Many websites struggle to pass Core Web Vitals, including Amazon. To illustrate, let's start with a simple counter example and gradually introduce more realistic components. Real-world applications consist of multiple components, wrappers, actions, and UI updates. These components often need to share data through wrappers. Despite its simplicity, even this basic application requires downloading several code files to the client.

So what I want to talk to you about today is speed, and specifically the Core Web Vitals and how to get it to Core Web Vitals as high as possible. Turns out the answer is relatively simple but hard to do, ship less JavaScript. And Google cares about it a lot, and as a result they created these Core Web Vitals metrics that they track and perform.

But it turns out that as our websites are getting more and more popular and more complicated, the amount of JavaScript we're shipping to the client is slowly increasing over the years, as you can see over here. So as the number of amount of JavaScript is increasing, it makes it harder and harder to actually pass Core Web Vitals. And you can see that most of the websites that are kind of out there actually have trouble passing Core Web Vitals. And even Amazon that cares very deeply about performance, and they are some of the most web performance websites out there, is having trouble passing these scores. And it really comes down to the amount of JavaScript that's being shipped to the client.

And so let's start with a very simple example. And let's build a counter example. Well, counter will probably look something like this. You know, we have a button that you hit plus one, and there's a count and it increments the value. And so this is kind of the simplest possible application that you can think of. But I don't think this is a really representative of the real world app, because in this particular application we have our mutation, which is your listener, our state, which is the current count, and the rendering, which is the binding of what the value is, all inside of a single component. And realistically, usually their mutation, the state and a rendering are separated into separate components.

And so let's look at that as a more realistic example. Or we break this thing up. And so an example of this would look something like this, where you have the counter component that is at the root, and it contains two smaller components, the action and the display, where the counter contains the state and the action has the listener, and the display shows just the current count. Now, even that is not actually quite realistic, because typically we have additional things like we have AppRoot, we have extra wrappers that you use for the purposes of the layout of the components and so on. And all of these extra things actually make it more realistic to what the actual application is. So here's what this would look like.

As you can see, we still have a counter, but now our action is wrapped in an additional component and so is our display. And the reason I'm showing this is because you need to get data through these additional wrappers, and it will have effect in the way the application runs. And finally, let's introduce an action item and a display icon. And the idea behind those things is that there are essentially leaps. There are components that don't do anything, but oftentimes we have these components in our application. So a more realistic example of what an application really looks like is a mixture of these app components, the counter, the different kind of wrappers, finally a place where we actually perform some of the actions, and a location where we actually update the UI. You can think of the performing of the action as the buy button on a page, and you can see the count as the showing the current shopping cart on the UI. So really, what we have is we have the situation where the mutation and the display are actually far apart from each other, and they have to share a common root component, the least common denominator kind of root component, which encompasses both, which contains the state, and so the state is then passed down to both of these locations. Now, the thing is that you would think that for such a simple application, the only code that you have to download to the client is really just the mutation action, maybe the state and maybe the display.

3. Understanding Hydration in Frameworks

Short description:

Hydration in frameworks requires downloading the entire application and affects the ability to interact with the app. Frameworks need listeners, application state, and component bindings to function properly. The framework starts at the root component, renders it, and downloads the necessary components and their related information. From there, it descends into the child components.

But the thing I'm going to talk to you about is, actually, you kind of have to download the whole application, and it has to do with the way hydration works. So really, this is ideal world, right? Really I should only have to download these pieces of code. Everything else is really unnecessary, and it's static. But hydration comes into play, and so in order for the framework to be able to do things, it needs three pieces of information, right?

The first thing, the framework needs to know where the listeners are, because without listeners, you can't click and interact or do anything. The next thing you need is an application state. So again, think about the button, which has the plus one for the counter. Unless you know what the current count is, in our example it was one, two, three, you don't know what the next value is, and so you can't really interact with an application without its state. And finally, the application needs to know where the components are. More specifically, the framework needs to know, sorry not application, the framework needs to know where the bindings are, right? It needs to know that if the application state mutates, which components need to be re-rendered in order to show what's going on.

And so the question becomes is, how does the framework get ahold of this information? And so as again, we need the listeners, listeners need to mutify state, and the state is needed to update the components. So how do you get a hold of this? Well, you start with the root component, with app root, and the framework, this is kind of you can think of it as the main method, right? And you start rendering at this location, and then the framework then goes and downloads the counter, and executes the counter, and learns that the counter contains the state. And then it starts descending into the children.

4. The Impact of Hydration on Performance

Short description:

Hydration forces the execution of every component on the page, leading to the download of unnecessary code. Frameworks can only lazy load code that is not on the page. Hydration requires all code to be present, impacting performance.

So it goes to the action wrapper, there, nothing interesting, and then goes into the action component, and there it learns that the action component has a listener. And so it collects that information. Now it also sees action item. And unfortunately, the framework doesn't know whether there's a button in there or not, it really has no choice but to descend and visit that particular thing as well. So it goes and visits action item. And then finally comes back to the counter, and takes the second branch, and goes to the display wrapper, goes to the render for the display, and finally goes to the display icon.

And so hydration basically forces you to execute every single component that is currently on the page, right? There's no way to kind of short circuit this, or skip, or do any other behavior. And so even though in order for our application to run, we only need really the counter, the action, and the display, hydration basically caused us to download all of the code. Now some of you might say like, hey, but once the application is running, you know, the frameworks can be smart, and they know how to skip and prune these trees and don't descend into things that don't matter. And all of that is true, but that can only happen after the framework has fully executed everything that is on the page. The other thing actually to point out is that frameworks can really only lazy load code that is currently not on the page, right? So there's really no easy way to lazy load action wrapper, for example, because the framework during hydration needs to visit, needs to go through the action wrapper in order to get to the action. And so what I want to show you is that hydration really requires that all of this code is present on the client, and you can see us everything is being ticked off here. And again, like for a large application, of course, you know, nine components is not a big thing, but in the real world where you have hundreds and hundreds of components, it all adds up. And if every single component needs to be present and available, it has implications on performance.

5. A Different Approach to Hydration

Short description:

There are different hydration alternatives, but let's explore a completely different approach that skips hydration altogether. Hydration must start at the root component and visit every single component. All frameworks, including Quick, need information about listeners, application state, and components to be re-rendered.

Now, there are lots of hydration alternatives, everything from island architecture to partial hydrations to progressive hydrations. I don't have time to really go into it, and these are all different ways to kind of improve the situation and they do improve the situation to some degree. But what I really want to show you is a completely different way of doing it where we skip the hydration altogether.

Now, before I do that, I really want to point out that hydration is in order, meaning that a hydration by definition must start at the root component and recurse into all of the children, right? It is not possible to start hydration in the middle of your application. So even if you know that a particular component is static at the root and you don't need it, there's no way to skip it. You really have to start from the root and visit every single component. Okay, so let's go back to this slide, and I think this is an important slide. Now, I said that every single framework needs to really have these three pieces of information, right? Every framework needs to know where the listeners are, every framework needs to know what the application state is, and what are the components that need to be re-rendered if the application state changes. And so this is true for, as I said, all frameworks, including Quick, which is what we're going to talk about next.

6. Comparing Hydration and Resumability

Short description:

In the hydration world, we re-execute all the components to learn about listeners, application state, and components. Resumability sends information about bindings, state, and listeners in the HTML, allowing the application to be ready to go without re-executing code on the client. This eliminates the need to download and execute unnecessary code, providing savings and improving performance.

But the way we get this information actually makes a huge difference. And as I said, in the hydration world, the way you get this information is by re-executing everything that you see on the page, right? So you re-execute all the components, and as you're re-executing these components, you are learning about where the listeners are, what the application state is, and what components there are, and when the components need to be re-rendered.

So let's try to do something else. So hydration works, as I said, in this particular way, where we start with a service-side rendering or SSG, we start with executing the application code. And as we execute the application code, we learn about where the bindings are, what is the state of the application, and where the listeners are. We then serialize the HTML of the output. And then when we go to the browser, the browser shows us your application. But the framework, in order for it to be interactive, it needs to recover that information about the listeners. And so the framework goes ahead and starts executing the application again. And as it's executing the application, it learns about where the bindings are, where the state is, and where the listeners are. And when all of this re-executes, your application is ready to interact.

Now, resumability works slightly different. So just as previously with hydration on a server, there is no difference. You start by executing the code, as you're executing your code, you learn about where the bindings are, what the state is, and where the listeners are in the application. But here is where things get different. You obviously send over HTML. But additionally to the HTML, you actually send information about the bindings, the current state of the application, and where the listeners are. And by including that information in the HTML, it turns out when you go to the browser, when a browser wakes up and shows your application, the framework already knows where the listeners are, what the state is, and where the bindings are. And so as a result, it doesn't need to re-execute any code on the client on startup. The application is just ready to go. And this is kind of the big difference between hydration and resumability. And because we get to skip all of this code, you get benefits in multiple ways. First of all, you're not executing the code, so you have savings there. And second, because you're not executing the code, it turns out you probably don't even need to download most of the code. Some of the code probably needs to be downloaded, but not all of it. The whole trick of resumability is that we know how to transfer the state from the server where we had it to the client. Hydration rebuilds the state on the server, but then it kind of throws it away. It doesn't serialize it in HTML, which means it's lost in the process to go into the client. The whole point of resumability is that not only do we include the HTML, the rendered application, but we also include information about where the links are, what the state of the system is, and where the bindings are. As a result, we completely get to skip this part of it here, which is the re-execution.

7. Comparing Hydration and Qwik

Short description:

Hydration requires downloading and executing all JavaScript code, followed by reconciliation. Resumability, on the other hand, starts with HTML and doesn't require additional steps. Qwik works this way, knowing the listeners' location and system state, allowing for performance benefits. With Qwik, only necessary code is downloaded, resulting in significantly less code on the client compared to hydration.

The way it looks like on a timeline chart is that when you navigate to a website, the first chunk here is the HTML. This is what you need to download before you see the app. At this point, you see the application, but you can't interact with it. Hydration requires you now to download all of the code associated with JavaScript, then you need to execute all of the code associated with the JavaScript, and as a final step, there is a reconciliation. This is where you go back to the DOM and verify that it has all the listeners and all the pieces. Typically the listeners is the part that you have to reattach in this particular phase.

Now, resumability also starts with HTML, as shown here, but there's no additional steps. You're just ready at this point, because the framework knows about where the listeners are. It knows what the state of the system is and all the additional information. You can just go on where you left off. This is where you get the performance benefits from. This is how Qwik works, and this is what is fundamentally different about Qwik. Let's look at Qwik, how this would work. On a server, you execute all these components just as before, and then you download the HTML associated to the client. This HTML really contains now the red boxes. The HTML contains information about the state, it contains information that here is where the mutation happens when you click on it, and here is the rendering that has to update the UI.

Specifically in here, we don't actually know what the mutation is. We just know that some code has to be executed if you go and interact. The code is loaded when necessary. As a result, when you go and click on a button in Qwik, the only code that downloads is the Qwik listener. That one is required. You have to download that code and execute this code. The second code that may or may not be downloaded is display. Depending on whether the display has structural changes or not, Qwik might get away with not even downloading the display. If it has structural changes, it might be forced to download the display as well. In each case, you can see that the amount of code that is needed to be present on the client is significantly less as hydration. More specifically, the thing I wanted to show you is that with hydration, there was a check box everywhere here. Every single one of those columns needed to be executed before you could do anything. Whereas Qwik, there's nothing that has to be done eagerly, and only a few things happen lazily. That's where the performance gains come from.

8. Understanding Lazy Loading and Memoization

Short description:

We load less code and avoid duplicating work through memoization. Frameworks can only lazy load and do memoization after downloading and executing all components on a page. Qwik Insights observes how the application runs and collects anonymous statistics to determine the probability of executing certain functions.

The way this works is that we load less code, we do less work, and we don't duplicate the work, which is memoization. You're going to say, yes, but my framework knows how to lazy load code, and my framework knows how to do memoization, and all of these things. I'm going to say, absolutely, that is true. However, your framework can only do it after it has downloaded all of the components that are currently on a page and it has executed all the components on a page. Memoization, for example, to not descend into certain subtrees only works after you've done hydration, which means after you have at least once successfully descended into everything, then on subsequent executions, you don't have to descend into those trees.

An important thing to know here is prefetching. I know what you're thinking. You're thinking, if Qwik is lazy and if you go and interact with a particular button and all of a sudden you don't have that piece of code, the user will have to wait for the code to download over the network. I want to show you the important part that Qwik does. It's called Qwik Insights. What Qwik Insights does is it observes how the application is actually running. First of all, Qwik Insights is a service. You don't have to use it. It's optional. But if you do it, it collects all this information. This information is completely anonymous and what it basically collects is statistics that says, hey, if you execute this function, which is shown as a particular SHA over here, then you have a certain probability that you're also going to meet this other function. In this particular case, there's a 19% chance that if you execute this particular function, this other function is going to be needed. We're showing it in what we call a correlation matrix.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Summit 2023React Summit 2023
32 min
Speeding Up Your React App With Less JavaScript
Too much JavaScript is getting you down? New frameworks promising no JavaScript look interesting, but you have an existing React application to maintain. What if Qwik React is your answer for faster applications startup and better user experience? Qwik React allows you to easily turn your React application into a collection of islands, which can be SSRed and delayed hydrated, and in some instances, hydration skipped altogether. And all of this in an incremental way without a rewrite.

Workshops on related topic

JSNation 2023JSNation 2023
170 min
Building WebApps That Light Up the Internet with QwikCity
Featured WorkshopFree
Building instant-on web applications at scale have been elusive. Real-world sites need tracking, analytics, and complex user interfaces and interactions. We always start with the best intentions but end up with a less-than-ideal site.
QwikCity is a new meta-framework that allows you to build large-scale applications with constant startup-up performance. We will look at how to build a QwikCity application and what makes it unique. The workshop will show you how to set up a QwikCitp project. How routing works with layout. The demo application will fetch data and present it to the user in an editable form. And finally, how one can use authentication. All of the basic parts for any large-scale applications.
Along the way, we will also look at what makes Qwik unique, and how resumability enables constant startup performance no matter the application complexity.