Building WebApps That Light Up the Internet with QwikCity

Rate this content
Bookmark
Github

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.

170 min
12 Jun, 2023

Comments

Sign in or register to post your comment.

Video Summary and Transcription

Quick is a framework that focuses on performance optimization and reducing JavaScript overload in websites. It allows for lazy loading of components and efficient downloading and execution of code. Quick integrates with various tools and platforms, including Auth.js, Superbase, and headless CMS. It provides features like client-side navigation, route loaders, and the ability to fetch and update data. Quick's rendering paradigm makes every application instantly interactive, and React-Quickify allows for the use of existing React components in Quick applications.

Available in Español

1. Introduction to Quick and Performance Optimization

Short description:

Hello, my name is Misko and I'm the CTO of builder.io. I work on projects like Angular JS, Karma, and Quick. Builder.io is a headless visual CMS that allows for a drag and drop editor. We focus on open source projects like Party Town and Mitoses. The world needs a new framework because existing websites have poor performance due to the amount of JavaScript. We need a framework that doesn't overwhelm the browser with JavaScript. Service site rendering and reasonability are two approaches to improve performance by reducing duplicate information and downloading less JavaScript.

Hello, how's it going? Hi, my name is Misko. I'm the CTO of builder.io and I work on other projects, Angular JS and Karma and now I work on a project called Quick.

And so, before I tell you about Quick, just a little quick overview. So builder.io is a headless visual CMS. Basically what we allow you to do is to have a drag and drop editor for your application so that your marketing people can design the website without constantly bugging the engineering department about all the changes they have to do. And the cool thing about it is that it's running on your infrastructure rather than being hosted somewhere else. Because it's running on your infrastructure you can register your own components. Anyways, here is kind of their open source theme. So builder is about 50 people. Actually, we're probably pushing 55 at this point. And we love open source. And so we have a couple of projects besides Quick which is Party Town and Mitoses. Party Town is for running your third-party code like Google Analytics inside of web workers. And Mitoses allows you to make design systems, existing components that your company needs. And then it translates them into all major frameworks for you so you can have a native version of React, a native version of Angular components, and Quick web components, React Salt, whichever one you can imagine. So this is our team working at it. Adam Bradley used to work at Ionic. And Manuel also worked in Ionic and created Gin. And we have some Jabber who also worked at Mitoses. So that's kind of the open source we focus on.

And so the question then becomes, why does the world need a new framework? And the answer for that is, well, because if you look at the existing websites, the performance of these websites is not that great. So Google has spent a lot of time making sure that kind of trying to encourage the world to create faster websites. The way they do this is they create something called Core Web Vitals and a Lighthouse score, a PageSpeed score, and they're trying to basically show you how performant your website is. Core Web Vitals is actually instrumenting in Chrome. So it's a real world user experience. And it basically shows how interactive, you know, how well the user experience is. And as you can see, most websites don't have a very good Core Web Vitals numbers. And even the companies that try really, really hard, like Amazon, you know, they get much, much better, but they don't have the best numbers. And so the question is, like, why is this? You know, why are we having so much trouble? And the thing I'm gonna try to convince you is that it really comes down to the amount of JavaScript. And so the more JavaScript you have on your website, the slower the website becomes. And that's kind of obvious. You know, it's not really that difficult to imagine why that would be the case. And so the thing we're trying to figure out is how do you build, I mean, you need JavaScript, right? Because JavaScript is the thing that makes the webpages interactive. And so if you don't have JavaScript, your pages are not interactive. So the question becomes, like, how do you, oops, how do you design websites so that you can use JavaScript to build them, but at the same time don't overwhelm the end user with all of the code. Rather don't overwhelm the browser. And so this graph really shows that, like, over the years, we just keep shipping more and more JavaScript because we expect more out of our websites today than we had ten years ago. We expect a lot more interactivity, a lot better user experience. And all of that requires JavaScript, right? I'm here saying that we need less, but not in the form of like, you know, write less JavaScript and go back ten years. That's unrealistic. Instead I'm saying, hey, let's keep writing JavaScript, but like, how about we design a framework so they don't overwhelm the browser with all this JavaScript? So instead of dumping all the JavaScript all at once on the user, you know, could we have framework where the JavaScript kind of shows up on an as needed basis rather than overwhelming the whole system all at once? Okay. And so this is another chart. And it basically shows the same information. So it shows that on the left-hand side, you can see the scores and on the right-hand side, you can see the amount of JavaScript that is being shipped. And what you can see is that there is an inverse relationship, right? The more JavaScript we ship, the worse the performance is. And the less JavaScript we ship, the better the performance is. And so it all comes down to shipping less JavaScript. And if you look at the Core Web Vitals that the Google page speed gives you, it's the same kind of a story in the sense that the Google will oftentimes recommend that, hey, you know, can you ship less JavaScript. The hard part is how exactly do you do that? And so the thing I'm trying to convince you or rather kind of share with you is that if look at history of how we got here, you're going to realize that hydration is a bit of a workout. Here's what I mean by that. So first we sent HTML and then we originally had blank page. And the blank page had a script inside of it that downloaded JavaScript which then executed the application, which then rendered the application and this is where you had your app shown and you can interact with it. And so the problem is there's this white blank page. And people said, you know what, we really like the fact that it's super interactive, but this white page over here is problematic. So can we do something about it? And so what they created is a service app. So in the service app rendering, the HTML is much bigger, but as a result, instead of having a blank page, you actually have the page that you see. And so the site appears faster, but this site is not interactive. You can't click on it. So you still have to download the JavaScript. You still have to execute the application and you still have to render it, except this time we're going to call it reconciliation because we're trying to reuse the DOM elements. And then so at this point, you actually have a site that you can click on. And so service site rendering has this interesting contradiction where the site appears faster, but it actually is slower to get it to interact with. And so this is kind of the situation we have. And so instead oh, yeah, I want to point out that there's a lot of duplicated information. If you look at this visually built on your text stack, this string is found both inside of the HTML and inside of JavaScript. There's duplication going on. And so we'd like to have something some other way of doing it. And so I'm going to skip a couple of slides and I'm going to show you what reasonability is. So reasonability, basically, does something differently. So you start with the HTML. The HTML is big. And as a result, you have the page here. But the unique thing about reasonability is that there is a listener that knows how to interact with the page. So already you can interact with the page at this moment. Now you still need JavaScript. So the application appears faster. And so you eagerly start to download the JavaScript immediately as you show up. But here's the thing. Notice all this missing JavaScript. So the amount of JavaScript you download is a lot less. And that's because reasonability can actually remove duplicate information. Right here you have JavaScript for interactivity. But all of this was basically the static information that's already found in the HTML. And so this removing of duplication means that you don't have to re-execute the application. You don't have to re-render the application. And so you can just interact it way, way faster. Okay, I think this is kind of a good introduction. So I'm going to pause here. And then we're going to use this to build some application. But first, I want to see if people have questions.

2. Introduction and Workshop Setup

Short description:

We have a person from Slovakia. Any questions or should we jump in and try to build something with Quick and see how different? Here is the URL for the workshop. You can follow along and ask me questions. The link is also available in the chat room and the discord channel.

Oh, there's somebody in the chat. There we go. Is the darkness. OK, so we have a person from Slovakia. Awesome. Any questions or should we jump in and try to build something with Quick and see how different? All right. I'm going to take the silence. It has no questions. So here is the URL. the URL inside of the the I can paste it here instead of the chat window. But I also pasted it inside of the good nation. Discord channel so you can follow along. OK, so all of the steps for this workshop you can find this URL and we're going to go over it together. You're welcome to follow along and ask me questions, etc. Somebody says again, I'm not sure I understand. Which mean by again? Oh, the link again. Oh, I see. Oh, because I only sent it to a specific person. Oops, sorry. So here's the link in the chat room. And the link is also available if you go to the discord right here.

3. Building a Quick Application

Short description:

To get a quick application going, run 'npm create quick at latest' and choose an empty application. Open your favorite editor and run 'npm run dev'. The application will run on port 5174. Inside the 'routes' folder, create a new folder called 'demo' and a file called 'index.tsx'. Define a component and export it as the default. You can create a counter by defining a count variable and a button that increments the count value. In development mode, JavaScript is loaded when interacting with the application. In production mode, Quick eagerly downloads all necessary JavaScript into the cache. Interacting with the application then loads the code into the JavaScript VM.

OK, so let's build something. So the way you get a quick application going is, OK, I'm assuming the font size is good. If not, please in the chat, point out and complain, and I will do that. And again, as I said, I love questions. So make this more fun to me and ask me anything you might have as we go through this.

OK. So let's build a new quick application. So it's npm create quick at latest. So this is kind of what gets your application going. And it's asking where do we want to put it. So let's call it quick nation. And let's actually do a empty application that we can kind of build out from the beginning. So I'm going to choose empty application here. Yes, I would like to install on dependencies. And we should have this up and running in a second.

OK. Once we have the application, let's open up your favorite editor. And so in here, we should always type npm run dev. And so this opens up an application. And the application's running on 5174. Why is it running on 5174? That just means that there is an existing app that's already running. So let's open up the application. And actually, let's do it in a Intuitive form, and that font is way too big. If the font is not good, let me know. I think this is a usable font size. OK. OK. So we have built a very simple app. And in here, you have our source folder. You have a routes folder. And in here, we have our index.tsx. And this is where our text is. Hey, can't wait. The text that's basically in here is what's showing up in this location. And this is a starter that is an empty starter. So it's intentionally has very little inside of it. But that's how you get it going. So at this point, we have basically done the lesson 0. So lesson 0 basically shows how you get it up and going. And so before we jump into lesson 1, I want to give you a little bit of a tour. And so let's make a new folder here. Inside of the routes, let's make a new folder, let's say demo slash index.tsx. So this is our router. And so it's file-based routing. This is similar to other meta frameworks out there, so this shouldn't be really surprising. Let's create a component. And let's have a default export. So a default export is basically how the system knows to use it. So hello from Quix. So if you go to route demo, here we go, slash demo, then you can see that the code that has shown up is basically this code over here. And so if I go change this to git nation, let's say, and I hit Save, notice it automatically reloads. And when I go here, you can see that the application has reloaded and ready to go.

So this is our dev environment in which we can build our site, build our things. We have a basic hot module reloading going, and I'm kind of showing how you can use the URL for the purposes of the demo. And in here, I'm just going to show you a very simple example, and then we're going to get back to the workshop. So the example I want to show you is a counter. And what you do is you say you define count. Now, if you're familiar with React, you should feel right at home with Quick. And it's intentional that it's designed this way. So let's create a count, and let's do count. Value, and let's create a button. Click, and that value, plus plus. And why are you not happy? OK, move on. There we go. OK, so we now have a simple counter. And of course clicking on the counter increments the number, right? So it's a very simple demo. But the thing to show off here is, if I go to the Network tab, let's remove it, there is two files, two JavaScript files that get loaded. This is vit, vit for hot module reloading, et cetera. This is only in dev mode, and it's not in production. So I'm going to type in to remove the vit. And once I remove the vit, here is where we get the difference that how Quick actually works. So Quick, there is no JavaScript. Nothing has shown up. And it's when I interact with the application that JavaScript shows up. Now I know what you might be thinking, like, well, that's nice, but wouldn't that mean that every time I want to interact with it, I have to make a server side request, and as a result, it will be slow. So this is a dev mode. This is how it works in dev. In production mode, it works a little different. So let me show you how it's different inside production. So before I go anywhere, I want to go to the application folder, and I want to show you that the cache is empty and there is no service worker. And let's go to a website built with Quick. So let's go to our Quick homepage. And notice that as soon as I navigate to the home page, the cache got filled with the appropriate JavaScript. So what's unique about it, like the service worker has a service worker right here that knows how to go and fetch all this stuff. So what's unique is that in production, Quick will eagerly download all of the JavaScript that you could possibly need for this interaction. But it doesn't download it into the VM, into the virtual machine, into the JavaScript VM. It only gets that into the cache. And it's only when you interact with the application, only then does the code show up inside a JavaScript VM. So let me show you, let's go to the network tab, let's clear this. And when I go and interact with something, like for example here, notice JavaScript shows up.

4. Building GitHub Route and Data Fetching

Short description:

But notice it says service worker, it was the one that responded, not the actual code. With other frameworks, you would have to download the whole component, and not just this component, everything above. Quick knows how to break up your application to the pieces and then only send the relevant code to the client as necessary. Quick applications have this interesting property that they always essentially start with no execution, and then as you interact with them, then the relevant code gets loaded into the virtual machine and starts executing. Let's get into building applications. Let's build a route that can retrieve a list of GitHub URLs. We extract the user from our URL, and we want to do this on a server because we don't want to send a GitHub access token to the client because that's a private token that we don't want to share with anybody.

But notice it says service worker, it was the one that responded, not the actual code. And so as a result, it's always interactive, even if the network has gone down, et cetera.

Okay, let's get back over here. When I hit plus one, the network shows up. Now the unique thing here is that, then notice what code showed up. Notice that the only code that showed up is the code that it's inside of, right here. That's the only code that's in here. There's an extra thing in the front to recover the count state. Obviously the framework shows up, and this is kind of a build file that gets, tree shaken away in production, so you can ignore this one. So, but this is kind of interesting because this is now how other frameworks work. With other frameworks, you would have to download the whole component, and not just this component, everything above. So, you would really start with the root component and you would recursively download all the components that you need. And then the hydration requires that all the code executes. And so let me show you that the code does not execute here. So, if I say, render counter, and I hit save, notice on a server that function runs. Because the server needs to render this particular component. And so server definitely has to execute it. But if you go to the client, even if I interact with the page, you know, the application is working, notice what's missing here. That this function never re executes. Not just that it doesn't re execute, actually, it doesn't even download. Like it's not found in any of the code that is being downloaded. And so this is kind of what's unique about Quick is that Quick knows how to break up your application to the pieces and then only send the relevant code to the client as necessary. And again, I'm showing you the dev mode and the dev mode, this is like the extreme case. In production you actually get multiple symbols inside of a single file. By the way, this whatever is behind the dollar sign, we we call this thing a symbol. So right now, you have one symbol per file, in production mode you would have multiple symbols per file, so the behavior is slightly different in production, but you get the idea that the interesting bit is that JavaScript doesn't have to execute until you interrupt. And so while this is a trivial application, the fact that there's no JavaScript present also means no JavaScript is executed, right? So Quick applications have this interesting property that they always essentially start with no execution, and then as you interact with them, then the relevant code gets loaded into the virtual machine and starts executing. Okay, so that's kind of a quick overview, and then let's get into building applications. Any questions? As I said, I love questions. You're actually making this a lot more interesting for me and everybody else if you ask questions, so I'm highly highly encouraging you to ask away, interrupt me at any point. I think it's great. Okay, so let's get rid of the demo, and let's actually go into our first lesson. So the thing we would like to build is let's build a route that can retrieve a list of GitHub URLs. So let's create a new file, and so notice in this particular case, the directory is github slash user slash index, right, and so again, I'm going to have to create a component. It has to be default export, and now it says hello, quick, so now we can go to github slash mhebrey, right, and so what I put over here could be anything. It doesn't matter what I put here because this is a catch-all, right, so I'm extracting this parameter. As a matter of fact, we can actually get ahold of that and we can say const location, use location, and then we can say, we can say location, RAMs, now this says user, so I'm gonna say user here, right, so now, if I go visit, it says hello mhebrey. If I change this to builder.IO, it says whatever the particular thing is. And so the thing we would like to do is we would actually like to go and talk to a GitHub to get a list of repositories for this username. And so to make that like easier, I'm gonna grab a type information. So this is a project that has all of the type information for talking to GitHub, so I'm gonna install this. And I am going to cheat a little bit and just cut and paste this code. And I'm gonna explain this code in a second. So let's paste this code here. Okay, so what this code does, and you have to import your out loader. For some reason, TypeScript has fallen. TypeScript restart TS server. Give it a second. No, why doesn't the not auto import? Come on, TypeScript. Let me out. Okay, so what do we do here? So first of all, we installed the OctoKit open API. And so now we can import the types from the open API types. And I'm just gonna pull in a specific type called organization repos response. So this is gonna be the response we're gonna get from GitHub when we look for a specific organization or specific username. And so the next thing we need to do is we need to go and fetch the data from this URL. So this is the URL that GitHub provides and you can go talk to it. And to fetch the data, the GitHub wants a user agent, a version and a GitHub Access Token. So you can go get a GitHub access token by going to GitHub, going to your username. So if you go to GitHub, I go to every, you go here, settings, you go to, I believe to local settings. And so from here, you can get a personal access token. So I think it's this one. So I've already done this. So I am going to cheat a little bit. So if I go and copy here, Let's see if I can... So there is a environment file. And this environment file basically shows, This environment file basically shows all the tokens that you're going to need as you're going to build this project. So right now we need this GitHub access token. And so I've already done this, so I'm going to just copy it over. And, you know, for me, I've created a new file, environment local, and I'm not going to show it because it actually has my access tokens and I don't want to have to rotate them all the time. But this environment file, I don't want to have to rotate them all the time, but this environment local is basically the same exact file as this file, except the values here are the actual tokens. Oh, come on now. Sorry, I'm fighting with Zoom here for a second. Oh, geez. So in here we can get a hold of the token. So this function route loaders, well, what are we trying to achieve? What we're trying to achieve is we're trying to get a list of all the repos for this particular URL. And so we extract the user from our URL, right? And we want to do this on a server because we don't want to send a GitHub access token to the client because that's a private token that we don't want to share with anybody, right? And so this piece of code needs to execute on a server. And so for that, we have this function called route loader. And so route loader's job is to basically say, Hey, somebody navigates to the GitHub slash something URL, I need you to go and fetch this data from the GitHub and make this data available inside of user repositories. Right? And so because of that, we can get a hold of the repositories here. Repositories, close these repositories. To say Hey, somebody navigates to your GitHub slash something URL, I need you to go and fetch this data from the GitHub and make this data available inside of user repositories. Right? So this is basically making sure that we are not going into any service to manage whoever's data that's left on the surface. If we do this, it's actually going to run. We can do this is going to run if anyone goes and and we can do this is going to run if someone goes and and you can do this is going to run. and so that's a reward. And let's just return json. stringify. parentheses. Here we go. Okay, so now if I go to my page.

5. Using Quick and Serialization

Short description:

We created a route that fetches data from the GitHub repo using a private access token. The fetched data is used in the use repositories method. By filtering the dataset, the system recognizes the need for serialization when interacting with the data. The dollar sign in Quick ensures lazy loading of functions and the ability to serialize various types of data. However, there are cases where Quick cannot serialize certain types, resulting in an error.

Oh, sorry, you have to run a server and NPM. Whoops, NPM run. Now, if I run, you can see that I have a list of my repositories and I believe I can ask it to send me more. Okay, for page 100. So now I have 100 of my repositories that the server has returned.

Now, obviously that's not the way you wanna show the data to the user, but I wanna just kind of review again what we have done. So we created a route that has a particular key inside of it. We call this Prem and then we use this Prem to build up a URL that we talk on the server. So this piece of code never gets to the client. We use this piece of code to go and fetch a data set from the GitHub repo. GitHub repo requires a private access token. So we grabbed the private access token. We set it over here, and this private access token comes from the server environment. This environment is a server environment. And then what we get back is use repositories.

And again, if you're a React user, you know how to use the use method. And so you can use the use method to get a hold of this thing. If you are a React slash Next.js user, then what we have done here is essentially get static props. But this is a little better than get static props for a couple of reasons. First of all, you can have as many of these route loaders as you want. Show you how we're gonna create more in the future. Whereas you can only have one get static prop per file. And the other difference is that in get static props, you're not allowed to refer to the get static props because you want the framework to call it on a server, but you wanna make sure that that method, the function tree shakes on the client. And in order for the function to tree shake on a client, the get server props, you cannot refer to it. Otherwise you break the tree shake, right? So the way it works is that this is a well-known expert in Next.js, and because it's a well-known expert, and it has to be called get static props, Next.js knows how to call it. And so here, you're free to call it whatever you want. And the advantage of this is if you hover over it, you see that the type information correctly flows. So our repositories already have a correct type information that flows to the client. Let's see, do people have questions? I don't see anything in the chat, and is there the other chat? Check this one, okay, I don't see anything in this chat either.

So let's make this look prettier. So instead of just doing a JSON stringify on a repo, you'll probably wanna do a LI. The LI wants a key, so let's do key is repo.id, I think? There we go. And let's do repo.fullname. And so now, we get a list of all my repositories for me. And I can change this, I can go to builder.io, and I will get all the repositories for the other user now. Again, getting back to over here, notice if you go to the Network tab, there is no JavaScript that's being downloaded. And that's because there is no interactivity in here, right? Because there is no interactivity, you know, the system kind of looks at it and says, yeah, there's no need for JavaScript, but even if there was an interactivity, the system's pretty smart about downloading it.

Now, the other thing to kind of note is that there is Quick JSON. And this is kind of the state of the system. And this is kind of the covalent to the Next Data script tag that exists in Next.js, if you're familiar with that system. And in this particular case, the system looked at it and said, yeah, there's nothing to serialize. And that's because you can't re-render any of this code on the client, so even though all this data came from a server, it is not in here because, well, there is nothing really that you can do with it. So let's simplify this a little bit, or rather let's add some interactivity. So let's say, filter equals use, signal, so use signal is kind of like use state, so we say we have no signal at the beginning and we can do input types. Okay, it's pretty close, so let's do value, input dollar, filter, value equals, oops, it's gonna be a callback function, and so it's gonna have an event and a target, so it's target.value, right? So now we're saying that whenever we type into the input, we would like to update it, and to kind of show the point that it's updating, filter.value, we'll just print out this filter.value, and so now again we have our list of responses, we have our input and we can type over here, and as I type over here, we can see that the value updates. So we have binding. But what we'd like to do is use that information to filter the repositories. So one way to do that would be to say filter, and so filter takes a repo. So repo.fullname2 lowercase includes filter value to lower case. Okay, so now we're saying, hey, before you rendered the whole thing, only show the ones that have all this. So right now we have everything and then maybe I just type in click and somehow I only have click. And so it is interactive as I go and type the data updates. And I wanna point out a couple differences here, just by filtering on a dataset, all of a sudden the system recognized, hey, you know what? I need to serialize the data that came from the server because there's a possibility that I will client-side re-render this, right, in order to interact with it. Notice, if I just comment out the filtering, the system realizes, oh, there is no way to interact with this data. And so this data is no longer serialized. So I can type here, it is interactive, but no serialization is happening, right? Again, let's look at the network tab. The only code that downloads is the code for the first value and nothing else. And so it's always kind of nice to kind of point out that not only does the system and correctly tree shake, kind of remove the unnecessary JavaScript, the system also removes the unnecessary data. So the only data that's actually needed gets serialized. Okay. Any questions so far, or are we good? Maybe a thumbs up. Okay. So let's keep going. Is the chat, can you see my chat or is this hidden? Because I think it's, I'm just worried about blocking the demo that I'm showing. Hopefully the chat window is not something that you see on your side. What if the data can't be serialized to JSON? Ah, excellent question. So quick, let's talk about the dollar sign for a second. So notice that there's these dollar signs here, and here. And what these dollar signs do, is that they make sure that, whatever's behind it, typically a function, that function can be lazy loaded. So you can think of this as lazy load. Now, again, there's a prefetcher so that, you know, you don't have any network latency when you do that. And so the question that becomes like, okay, but if I lazy load a function, the function has a state that it closes over. So in this case, you're closing over the filter, right? Which means you're closing over the values. So the rule of the dollar sign, is that the dollar sign needs to, whatever it closes over, such as a filter value, it needs to be able to be serialized. And a signal is definitely not something that JSON knows how to serialize, but the Quick does. So you can actually serialize a lot of different types. You can certainly serialize filters, sorry, signals. You can serialize things like dates, set maps, errors, promises. There's a lot of different types that Quick just knows how to serialize out of the box without you having to even worry about. But you're correct, that there's always a type that Quick will not be able to serialize. And in such a case, you will get an error. So let me show you an example of that. So let's say we want to print, let's make a const in object equals, I'm just gonna create a class. Oops, ah! So I think I can just say new over here. Okay. Okay. Um, okay. So here's the object and let's try to, so let's update this. So that every time I, actually let's no, let's just make a new button to make it simpler. Let's make a new button.

6. Serialization in Quick

Short description:

Quik can serialize a lot of stuff, but not everything. If it's something that you cannot serialize, usually Quik tells you about it. Things like streams, classes, you have to recreate on a client because they are not serializable.

And on click, and in here, every time I click on it, I will say console log object to string, right? So now I am trying to serialize the object. And so because object is a class is not serializable. And so notice that even the type system is already helping, right? And it's saying, Hey, it seems like you're referencing an object inside of a different scope in this case is on click. Okay. Let this is my way. Just written this when this happens quick needs to serialize the value, right? So this is one of the rules of the power side that needs to be serializable. However, in this instance, the class which is not serializable. So you know, it's kind of pointing you to the rules and kind of do some reading, but also when you run this, you can see that what you're trying to do is you're trying to serialize it. And this is something that's not serializable. There are ways around it. One of it is no serialized, basically you tell the system, like don't bother serializing, but then it's on you in your application code to kind of deal with it. You basically get undefined in the in the other side and then you have to kind of recreate it or deal with it, et cetera. So the short answer is Quik can serialize a lot of stuff, but not everything. And so if it's something that you cannot serialize, usually Quik tells you about it. And there are, you know, it's basically onto you to kind of deal with this particular problem, right. So things like streams, classes, you know, those you have to recreate on a client because you know, that's not really serializable. Hopefully that answers the question.

7. Serialization, Styling, and Integration

Short description:

I showed you how serialization works and how to style components in Quick. Lazy loading is a key feature that allows for efficient downloading and execution of code. The system automatically serializes relevant data and only downloads necessary components. Server-side rendering enables an interactive page without JavaScript. The Quick JSON format ensures efficient data serialization. I also mentioned the integration of Auth.js library.

Okay, let's see. Okay. So let's see, okay. So what I wanted to show you really is just how kind of the serialization works. So if you go to the network, oh sorry, if you go to the elements, you can see that in this case, what we're serializing is really just, just initial value here. But if we go and uncomment the filtering, then the framework realizes, oh now I have to serialize the full state that came from the server because it's possible that you will interact with this data. So by typing quick, I have to rerender the component and as a result, I need to have the full data from the server. And so what gets serialized is kind of automatically expands in here.

Okay, let's see. Okay, so let me show you styling. So let's create a style for this component because it's component doesn't look great. I am not an expert on styling. Hopefully you have somebody in your team who is, but let me show you how you do styling. So let's create index.css. Let's paste in some styles for this particular component, as we have card list and card item. And... And here are the classes... And... Now, this actually won't start anything yet because we created the style sheet, but we haven't loaded it. So what we need to do is we need to import it. And because this is beat environment, beat basically has the special thing called this question mark inline, which tells the beat that I want the content of the file rather than the file to be automatically inserted into the head of the application. And so, by getting the content, we can then load it by saying, use styled scoped. And this knows how to scope it so that if you have CSS in there that clashes with other components, it's correctly scoped so it doesn't clash. So now if I go up here, you can see that my repositories are cached, as I mean, they're properly styled. And if you go to one of these, you'll see that it inserted a special class that is unique and it's not found anywhere else. And the style was inserted in this location and you can see that the style also gets rewritten with the correct information. So this is basically how styling works. But I wanted to show you one more thing. Actually I skipped over it. So in this case, we are rerunning this code, which we're causing the filter to rerun. There is another way of doing this and that is something called computed. So we can say const filtered repositories. And so in React, you would just like put the coding here directly. But because we wanna do all this magical lazy loading, we have to do something called use computed. And use computed is the way you compute new values. So let's, let's do this here. So the thing we want is, let's see. Yes, I believe that is the correct code. So we're basically saying, hey, filter repositories are same as, same as, you know, regular repositories, but you have to run and run this code over them. So you have to filter them. And then in here, instead of talking about repositories, we're gonna say filter repositories, and we can delete this part right here. And so notice because the system uses signals, it knows to re-execute this piece of code whenever the filter changes. So whenever the filter changes, we re-execute this piece of code, and then as a result can then re-execute the template. So let me show you this again. So go to Network tab. If I do, I type here anything, the first thing that downloads is the listener for the input, right? So that's, where is the input? That's this piece of code right here. After that, we download the framework. And then the next thing we have to download is the computed. Because we have changed the filter value, we have to rerun the this function right here to update the filtered repositories. Right, so these, this you can see is the piece of code that is associated with that code to kind of update the repositories. The next piece, what do we download next? So the next piece is, we download the component itself. So now we have to rerun this piece of code right here to update the UI. And so you can see that that piece of code downloaded next and it re-executes and, let's see, what is this? Oh, well, this is the, oh, this is the, because the data came from a route loader, there is some logic that has to run to make sure that that the data hasn't changed on a server. Anyways, so now that we basically can kind of, and the other thing to point out, right, is that in production, instead of having a waterfall that's happening here, in production, the bits, the parts of that would be placed together. Now in this case, we only have one component in here, and so we kind of forced us to download all of the component, but in the real application, you would have a lot, lot more components, and most of them would be static. Most of them wouldn't be re-rendering, and so you actually get a lot of benefits by having a system kind of lazy load everything in parts like that. Let me show you one more thing in this particular case, and that is, this pattern right here of, like, data binding the value, and then just listening to the input, is so common that you can actually just say this. And that's absolutely equivalent to, what is, what? String or Undefined, why is it Undefined? Oh, it's possible for the input type to be Undefined. What? Okay, that's interesting. Something to fix. I'll just undo it so that we don't have a type error. Anyways, the two are essentially equivalent. Oh, oh, I know why it's an error, sorry. It's my fault. It's because you have to do filter. Here we go. Here we go, okay. Okay, everything works. Sign ID has returned. Everything works the way it's supposed to. Okay, so in this, this is basically the same exact thing than this part is, and so it's still, you can see that it's fully interacted, et cetera. Okay, let me know if you have any questions, but just to kind of review what I showed you is how do we execute code on a server. So this piece of code is guaranteed to always execute on a server, never on a client. And because of that, it is safe to access the environment variable and get the private token. As a matter of fact, this ENV only makes sense at a server. Like it makes no sense to have environment variable on a client at all. So we talk to the server, we fetch the data, and then we get the data and we can use it in a client component, but even though this is a client component, it's server side rendered. And so when the page first shows up when we first get the data, we see absolutely no JavaScript, right? So the page is interactive without any sort of JavaScript downloading or eagerly executing. And we can certainly interact with the page. And you can see that the behavior of the page is still there that I showed you how the system automatically serializes the relevant data inside of the quick JSON. And so only the data that's necessary is serialized over here. And I think that kind of covers it. You have questions, ask away. Otherwise let's go to the next part in the lesson. Okay. Okay. So let's try to now get an authentication code on the application. So there's a very popular library out there called Auth.js. So let's install it. So one of the things I want to show you is that Quic has a lot of integrations.

8. Quic Integrations

Short description:

Quic offers a wide range of integrations, including deployment to major platforms like Cloudflare, Azure, Netlify, and more. It also integrates with AddLess CMS, Cypress, Storybook, Auth.js, Playwright, PostCSS, Prisma, Style.DNA extract, Tailwind, VTest, and Partytown. Additionally, Quik allows the use of React components, although without the lazy-loading and other features provided by Quik.

So in this particular case, Quic is asking like, which integration do you want to add? And notice that we have a lot of integrations. We can essentially deploy to all major platforms, Cloudflare, Azure, Netlify, Bursell, Google Cloud, run Deno, Express, Fastly, even static HTML to like Apache, AWS, Lambda. There's a PR already out, and I believe it's even merged, but we haven't released, we just have to release the latest CLI. And we also have a lot of integrations with things like, AddLess CMS, Cypress for testing, Storybook for testing. Here's the Auth.js, which we're gonna look into. We have Playwright, PostCSS, Prisma for database ORM, Style.DNA extract, which is similar to Emotion, Tailwind, which is popular, VTest, and of course the Partytown for running your code instead of third-party. And finally, we actually have an integration with React in a sense that you can use React components within Quik. Of course, you won't get all this magic of lazy-loading utilization, and all this stuff that Quik shows, but it shows that React is something that you can use within Quik.

9. Installing Auth.js and Configuring Authentication

Short description:

We want to install Auth.js, which requires a workaround in the deep configs due to incorrect ESM file creation. A GitHub API and secret, as well as a private auth secret, need to be set up as environment variables. The plugin.auth file is used for configuration, including the use of GitHub and other providers. The use method allows for loading repositories and checking login status. The layout TSX file can be modified to include a sign-in button, which invokes the off sign-in action. The use auth session can be used to conditionally display user information. Quick recommends providing height and width for images to avoid layout shifts.

So, the thing we want is we wanna install Auth.js. So, let's just check it off. While it's downloading, we have to do one little trick. So, it turns out that Auth.js doesn't correctly create ESM files, and so there's a little workaround we have to put in in the deep configs so that it doesn't try to bundle this as Auth.js. Hopefully, the Auth.js bundle will get fixed in the future, and then you don't have to do this particular bit.

Okay, so, we're gonna have to create an environment variable. So, if you go to EMP again, we're going to have to set up a GitHub API and secret and the private auth secret, which is just a random value using for creating hashes, and you can create these at random value in this particular way. Again, I've already done this. So, now that we have this, we have vtauth. Oh, yeah, so, when we ran the command line, it created this special file called plugin.auth. And, so, plugin.auth is, it looks like this, and this is where you configure all the pieces. I think, if I remember correctly, the names are actually different. I believe I added the word private here, private here, and here as well. So, in this particular, I like to prefix with the word private so that it's clear that, you know, this is not meant to ever make it to the client. And so, making it the client would be a problem. And so this is a kind of configuration for the auth service. You can see that we have configured a GitHub, but we can configure other ones. And notice that we have a use method in the same way we had a route. I'll just go back over here. See how we have a route loader that knows how to load the repositories. The auth also has a loader that knows how to tell you whether or not you're currently logged in. And so, the nice thing about this is that you can declare this use method anywhere on a route, and then you can use it anywhere inside of your component that is on that particular route. So, again, the difference with getStaticProps is that the getStaticProps has to be declared in the same file as everything else. And you have to, you get the value of the getStaticProps inside of your component props. And so, if you need it somewhere deep down, you have to be proper, it's not just automatically available. And so, the nice thing about the use method is that, well, you can actually use it anywhere that you want. Okay, so here we configured it. And what we get is the onRequests, which is a middleware function that automatically sets up the cookies, et cetera, and it's already provided for you, so you don't have to worry about it. And we get these use methods, which show both a loader, which tells you whether or not you're currently logged in, and it tells you, it gives you two actions for signing in and signing out. And so, let's use these three pieces to show you how all of these things would work. So first, we will probably want to go to the layout TSX, and the layout TSX currently just says div. But it will probably say something like this. So the slot is where the content is projected. Why are you not happy, div? Oh, because I did like this. Okay, so here is where you would probably say header, and just say header here. Header. And then here you would say footer. Build with long, something like this. So it's semicolon, okay. And so this layout would then automatically turn into, notice, if I refresh, oh, the server's not running. So I'll refresh server, npm run-dev. Okay, so now this page should refresh, and notice that there's a header and there is a footer that is included. And because it's a layout, this particular file will get included everywhere. So the thing to do over here inside the header is to basically say, hey, I would like to have a sign-in button. So it's probably gonna be a button. Okay. Okay, there's a sign-in button that I would like. And so as you can see now, the top has a sign-in button over here. And so what we wanna do is we wanna invoke this action, the off sign-in action over here. So let's go back to the layout. And so let's say const sign-in-action, this thing, we get ahold of this, have to import it. And so the next thing we need to do is we need to get a hold of a form because it's a button and it's going to be a submit. So we get a hold of the form. Notice this is a capital form. And so in here, we can now go talk to the sign-in action. Like so, and we have to import this. Okay, so now we have a button that we can click on. So let's try this. If I go and click on a sign-in, it takes me to a page, the autogs provider page where GitHub is already configured. I can click on sign-in, let's see if I can remember username and password. You know. Okay, so now and we're now being redirected back to our page and we're logged in, but it's still saying sign-in. So we probably want to show a different button, right? We probably want to show this conditionally depending on what's going on. So let's remember that this auth also provided a use auth session. And so, this is the kind of link. Link. Okay, so let's get a hold of the auth session. So let's say const auth, auth user equals user like that. And so now we want to say here is like, well, if the user.value.name no, that's not correct. Oh, sorry, we need to import, that's why the type system is not working. Import this, okay. So user.user. Well, let's go with email, let's say. So if we have a user, right. Then we want to do a, well, we want to say show user. Otherwise we want to do this form or sign in, right. And so now it will say show user, but like we actually want to print the users. Let's grab this again. And so we want to probably do something like, show user. Let's do an email, a good name, actually. And so now it says my name. And we would like to show a, image for me. So let's do image. That correct now user, user image. So now it shows the image, but notice what happens. Quick is kind of complaining and saying like, hey, for performance reasons, you should always provide height and width because if you don't, you're going to have a cumulative layout shift over here. Right. So the page first load, there is no image. And so it, the Mishka have reason the first line, and then when the page loads, it kind of doesn't flash on stock content. And so it's basically telling you, hey, would you please provide height and width? It's 25, 25 and now this is happy.

10. Conditional Image Display and Styling with CSS

Short description:

We conditionally show the image and username, and style the layout with CSS. The slot component in Quix allows for the projection of children in a declarative manner. This avoids rerunning components and minimizes the amount of downloaded JavaScript. The RouteAction is an action performed on the client that executes code on the server, similar to the HTTP post-paradigm.

And then what is the problem here? Oh yeah. It's possible for it to not have an image. So I think we need to do is this say conditionally show the image. Oops. So if you have an image, then insert the image tag and render it, and then show me the username. And so now I, there is an image and the username, and we probably want to style this a little bit.

So let's create a layout down CSCSS. Okay And we're gonna have to load layout. Okay. And now let's go to CSS. So, now because it's scoped, I can just directly say header. I can say, well, I can say header. Oh, this is a style in here. And then I wanna say that from header, I want the image border radius 50%. And so that should turn me into a circle, and I probably want to take the header and make it align the other way. So header, display flex. Align items. Wrong. Nope. Okay, what am I doing wrong? Place flags. Oh yeah, that's not the right way to do it. I am not a CSS expert as you can see. Justify content flex and. Justify content flex and. Justify content flex. There we go. So now it's, I'm here, over here. But we probably also want a sign out button as well. So let's put a sign out button as well. So after the user, let's create a sign out. And so instead of sign in will have a sign out action. And so we can do sign out. Sign out. This has to be imported from the plugin path. And there you go. So now I can click sign out. And I'm no longer signed out, right? I lost the image. Now it says sign in. I can go ahead and sign in. It should redirect me to GitHub. I should go to authenticate and voila, we are back in here, pretty straightforward.

Let's see. Do we have any questions? What is the slot component? Yes, excellent question. So in the layout in here, we have a slot. And so the question is, what is the slot? Well, let's, let's comment it out. And notice if I commented out, I've lost the content. So what layout is, is, is a file that wraps the specific component of this. It's the index that shows the list of users. And so you need to know where exactly to project the children of the layout to. And so that's done through slot. So you say, well, wait a minute. That's the same thing as in React. We have children. Can't you just use children? And that's a, that's a good observation. And you could certainly, we could do it through children, but the children have a particular property that makes them not really compatible with the mental model of Quix. Remember Quix mental model is, look, I have, I have a layout component right here, right? I have written code inside of here and I have behavior for signing in and signing out. And then inside of this component, I have the thing that shows a list of repositories. And the Quix has, this property that if I interact with, with a child component, right? The, that it shows a list of repositories, then I download that specific component. What I don't download is the layout component, right? Because the layout doesn't have interactivity so there's no need to download it. The problem is that if the layout would have children here, then we would have to rerun the layout every time the children would change, right? So every time this, I would go and interact with this component that has a list of repositories, it would create a new set of children. And now the layout would have to re-execute to get a set of children. Because if you think about it, children is just props. And the problem is that this problem is recursive. What if the, the component would, what if the layout component would be in another parent component that also would use children? And so now you would have to re-run all the components inside the chain. And so, one of the things that QUIC tries really, really, really hard is to not rerun components, not because it's necessarily a performance issue in a sense of runtime performance, but because we don't wanna ever download the code because that makes the startup performance bad, right? So, in order to have the best possible startup performance, you wanna download the absolute minimum amount of JavaScript. And so, that means that you want to basically be able to render, or re-render the components independently of each other. And in order to be able to re-render the components independently of each other, the projection of the children has to be declarative. So that if the layout changes, we can re-render the layout without re-rendering the child component. So if the child component changes, we can re-render the child component without re-rendering the layout. And the children are kind of in a way because the component can do operations on the children. It can search them, it can restructure them, it can project them in different ways, et cetera. And so, you really have no idea what happens to the children without rerunning the component. And so, this is the thing that we wanna avoid. And so in Quake, the children are declarative, and so we called them slots. Hopefully that's not too long-winded of an answer. Sorry, my foot. One second.

So, let's see. So we went, okay. So we set up the auth, we changed the layout to get authentication in there, and we had a CSS for the layout, and so I think we covered everything, right? So let's just review quickly. So there's this RouteAction. We didn't actually write it. The plugin provided it for us. So these actions are similar to Routeloaders. So loaders give you data. Action is the opposite, right? So loader sends data from the server to the client, so the client can render it. The action is the opposite. You perform an action on the client, such as logging in and logging out, and you want code to execute on a server. So basically you're sending the data the opposite directions. And the actions are modeled on HTTP post-paradigm.

11. Functional Buttons and Navigation

Short description:

In this lesson, we learn how to make buttons functional even without JavaScript, navigate to different pages, and retrieve content details for a specific repository. We also explore the option of client-side navigation instead of server-side navigation. By changing the A tag to a link, we can achieve client-side navigation, reducing the need for round-trips to the server. This improves performance and allows for a more seamless user experience.

So if you go to layout, you can see that there's a form in here, right? So if I go and find the signout button, notice that the signout button is really a form that's pointing to a specific action. The advantage of this is that even if I disable JavaScript here, this button will continue working even without it. And notice if the JavaScript is disabled, if I refresh the page, you can see exactly what the SEO would see, right? So this is what the search engine would see if you had it navigated to your page because well, SEO has no JavaScript disabled. So let's enable it again, and I think we covered everything in this thing, I'm passing data between rounds, but it's not here. Okay, questions at this point? So we've been going for a little over an hour. So maybe we do another lessons, do people want a break afterwards or are you okay? Keep on going. It's strange having a silent audience. Okay, I assume thumbs up means keep on going. So let's keep on going. Small break after lesson three. Okay, sounds good. So let's do a small break after lesson three. So let's look at, let's look at lesson three. So in this lesson, what we want to do is now we want to show the details of the page. Again, you can go yourself over here, you can look at the div, you can look at the starting and ending position for this particular lesson. So you can see everything that's going on in here. And I recommend that you go and play with this. You can check out this whole repository and play with it on your own. You will have to create the keys, the private keys, et cetera, but the instructions are in the ENP file. Yes, this file right here contains the instructions on what keys you need to use to get. Okay, so let's go back in here. Okay, so let's make a new route. So the thing we would like to do is that when we are in this page right here, this should be clickable so that I can see it. And actually before we even do that, let's improve it slightly and in this kind of try. Let's see what's going in here. Okay, so we should have more text here. Here is the text. Maybe make this an H3. There we go, okay. Okay, so we would like to make these clickable so that when you click on it. So let's do that. Let's make an ahref. And so the URL that we'll go to is slash, oops. GitHub slash, right. So now, when you can see that it's clickable and if I hover over it, you can see the URL down here. So the URL is the URL that we're going to. So if I click on this thing, I will navigate to a new page, which is GitHub MHAveryquick. So in this particular case, the new route we just created contains the user and the repo name, as you can see over here. And so again, I'm going to give it a component. It needs to be a default export. I refresh now. It should say, why is it not saying anything? Source routes. No. Oh, I messed up, sorry. This goes here, no. Repos, oh, it's not paying attention. Sorry about the confusion. Okay, so in GitHub, there is a user with a parameter. Inside of it, there is repo, and inside of it is the index file, which currently just says HelloQuick. So if I navigate, here's my HelloQuick. Okay. So the thing we would like to do is to get content, details for this particular repo. And so again, we are going to cheat a little bit, and I am just gonna grab a route loader here. Okay. So this is the import of the type, so we know what we are returning. I'm sure one of my JavaScript is refusing to import the file. So here is our type response, and what we're gonna do is we're gonna grab out of the params the user and the repo, right? Those are the two parts. And we're going to go and fetch the data from the GitHub repos users.repo URL. And again, private token is to be included in there. And then the response we're gonna get back is Json, and we can do now a cast of the type into here. And so now our repository is what we're gonna return over here, and if our upper array, you can see that the loader has the correct type associated with it. So we can say const. His repository, and in here we can do. Let's do h1. Well, okay, good job. And then we do description. And so it prints the description for us in here. Okay, so the other thing we would probably like to do is to make this a backlink. So let's do a little complicated thing. So here, you do a href. No, I think that's correct. Right, so now it can link back to the previous. So now we can basically go link forward and link backwards. Now, the thing to understand about these links is that this is a multi-page application, right? So we're doing a full page refresh. So if I click on here, it's a full page refresh on the server to get over here. And if I go into a specific one like Quick, that's a full page refresh to get the client. Now, Quick is pretty good at server-side rendering. And because the startup performance is so cheap because there's no hydration, this might not be an issue. Let's say that you would like to do a client-side navigation instead of a server-side navigation. So all we have to do is, let's go to the bottom one first, all we have to do is change the A into a link like this. Import it. And now, when we click here, we haven't changed this. So it's still a full-page refresh. But now if we go forward, now this becomes a client-side navigation. So we didn't actually do a round-trip to the server. We just basically did a client-side navigation. Now, of course, when you do client-side navigation, it means that the router information has to be sent to the client. That means that all the rendering of these components happens on the client as well. And so we can do the reverse as well. And we can go back here and set this to a link.

12. Client-Side Navigation and Route Loader

Short description:

When navigating between pages, Quick performs client-side navigation, reducing round trips to the server. The route loader automatically re-executes when navigating to a previous page, fetching the necessary data. Quick currently only supports string-based route parameters, but future updates will include typing for route parameters and URLs. The route loader is a versatile tool that works for both client-side and server-side navigation. In the upcoming lesson, we will explore integrating a specific page with Superbase to enable favoriting functionality. Prefetching routes on link hover is possible in Quick by using a service worker to pre-fetch the necessary data. This optimization ensures that the code is available when the user clicks on a link. By collecting statistical information about required symbols, the bundler can optimize bundles for specific navigations.

And now, we have the behavior that we are on the client. We are navigating to a... This was a client-side navigation. We can go back over here, and it's a client-side navigation. So the navigation now isn't doing a round trip to back and forth, but it's all done on the client. The slight delay that you see, that's actually coming from the server talking to GitHub, right? Because when you navigate from the child page to the previous page, the server has to do a GitHub fetch of the data and return it to the client. Now, the interesting thing in here is that we were able to switch between a multi-pledge application and a single beta application basically by changing an href into a link, and all of a sudden, it became a client-side navigation. So it's pretty cool. But notice that we kept our mental model. When we are navigating to the previous page, so if we're going back to the owner, we go from this page to this page, notice that we had to go and re-run the route loader. And so the route loader automatically re-executes without us having to do anything. And so the nice advantage of this model is that, well, you can just, you know, the component just says it needs this. And so because this is a client-side navigation, we automatically cause a fetching of the correct data so that everything can happen. And I think that's pretty cool.

Can we type params? Yes, we can type params, not just request shrink. Yes, we can actually do that. Which params are we, oh, oh, oh, I see. You're talking about this param's here. So in the case of params, they will always be strings, right? Because the text in here is always going to be a string, because URLs are all string-based. But it would be nice if you would know that user and repo are allowed, like use a login name or something, it's not allowed. So it would be nice if this was information in here. It is not available in Quake yet. We do have a prototype that we looked into very deeply about this thing, an idea of having this thing typed. But the prototype has not yet turned into a full change. So this is a feature that will be coming in the future, so not only being able to type the route parameters, but also being able to type these URLs and basically get an error if you are trying to navigate to a page that doesn't actually exist. So both of these features are coming in the future. They're not available just yet.

Excellent question. I didn't understand the difference between a route loader with multipage versus an SPA. So if you have a SPA, a single page app, really, what you have is you have a router in the client because when you click on a link, you don't want to do a full page reload. Instead, you want to just compute the difference to get to the client. And so now that you're navigating from this page to this page, this page needs data. It specifically needs repositories. So let's go back over here. So let's say we are inside of this page. Let me do a full page refresh. So it's clear when I did a full page refresh that the browser has no idea about a list of my repositories. But now when I click on my name and navigate to this page, it had to go and fetch the data set. So if you look at the Network tab, and if we look at Fetch XHR, we see that the browser had to do a fetch for this particular thing. And this fetch, I believe, is can I get a URL? I'll get a URL. Here we go. It did a fetch on that particular route with a special target queue data. And so it returned the data associated with this particular component. And because it returned that data, it could then render the UI. So my point is that we were using, essentially, the idea of route loader, if you're familiar with Next.js, is that this is kind of like get static props. But if you do client-side navigation, which I don't believe that's possible in Next.js, how exactly do you get get static props? There has to be some way of fetching it. And so my point is that you can have a single mental model of how you fetch data, which is the route loader. And this mental model works both in a client side as well as server-side navigation. Hopefully that makes it more clear. OK, great. I apologize if I convinced you. But yeah, please, ask questions. I love it. It makes it a lot more interesting for me. OK, so let's see. What else is in lesson three? So we create a route. OK, so the next lesson, we're going to take a little break. But the next lesson is going to be what we're going to do is now hook this up, hook a particular page with a super base. So that in a super base, we can favorite this particular page right. So I want to show you how to access an integration for a super base. So we can use that information with data. OK, that's not what I want. Sorry, OK. Can you prefetch a route on link hover? Yeah, actually, that's exactly what happens. So if we go to a round. So if you go to this route right here, this is a, right. So you basically see that this still look like a regular a href. But notice there's a prevent default. If the JavaScript is enabled, this navigation is prevented. So you're preventing the URL. And then there is a one of these scripts. I don't remember which one. I think it's this one. There is a script that does the catching of the click listener for this particular thing so that it can do a client-side navigation. And that can then send a message to the service worker to go and pre-fetch all of the data so that the navigation is instant. So let me show you this maybe in the production. So in here, the way this is done is that one of these script text. Here we go. All you have to do is send a dispatch event to the service worker. The QPREFETCH is listened to by a service worker. And basically, you just tell the service worker what are all of these symbols or chunks that you need. And then the service worker will go ahead and pre-fetch them. And so in the same way, the LINQ-HREF can notify the service worker that hey, this is a possible thing that the user might click. And because the user might click this, the service worker should go and pre-fetch that information. And so that is the way this gets optimized is that when you actually have a running application, then in the running application, you can collect statistical information about what symbols are needed, when. And then you tell the bundler to optimize your bundles for those navigations. Sorry, that's a little more advanced topic. But yes, the short answer is that yes, you can absolutely do that. So you can pre-fetch that as well. So if the pre-fetcher is working correctly, then you should never get into the situation that user clicks on something, and the pre-fetcher doesn't have the code for you.

13. Creating Endpoints and Middleware with Qwik

Short description:

Qwik can create endpoints easily by declaring an on-get method on a specific URL. Middleware functions can also be created in the layout file to wrap other components and prevent defaults. Superbase can be installed and used to set up a project with a database. It is not possible to have more than one onGET function in a file, but onGET and onRequest can coexist. The difference between a route loader and an on-get function is that the latter is a lower-level primitive that provides full access to the HTTP request and response, while the former is a higher-level concept that returns data and allows for JSON responses.

So every single interaction should be met with a cache hit in the client. What we did is we did client-side navigation. I showed you how we can do both server-side and client-side navigation. Actually, I want to show you other things. So Qwik can also do middleware and endpoints. So let's say we wanted to create a endpoint for some of our data. Let's make a new file. Let's do a API slash pollet just data. It's not 6. OK, so you can do on get, export cons, get, request handler. Yes, OK, I want to show you is I'm not going to go too much in depth. I just want to show you that you can easily do endpoints by doing 200. So just by declaring a on get method on a particular URL, you can very easily create an endpoint. This is inside of API slash data. So I'm going to point out if I go there's local host 3 slash API slash data. You can see that ETH returns a data set. So it was super, super easy to create an endpoint in our application. Now just like you can create an endpoint, you can also have middleware. So let's go to the top level layout TSX. And in here, we can create a on request. So export. So while on get only responded to get request, the on request responds to everything with handler. And it's equal to. Thanks. Were you complaining about? Oh, wait, what? Why is the on request here? No, that's not supposed to be here. All right, it's supposed to be. There you go. So now what we can do is we can say console.log. And we can do request.url.restrict. So now we have a middleware, and you can see that any URL I navigate to, like, for example, this one, you can see that it prints the navigation in here. So if I refresh this page, you can see that this URL has been. So it's really easy and quick to not just create endpoints, but also to create middleware functions. So basically, endpoints go into index.tsx, and middleware essentially goes in the layout so that you can wrap other things and prevent defaults. So for example, if you wanted to do authentication, you could easily look at the cookie and say, if you don't have the right cookie, then redirect somewhere else instead of executing the normal layout. So it's a little bit of detour. But let's see, where are we? Oh, yes. So we wanted to set up a Superbase. So let's install Superbase. We're just going to get rid of this middleware so we don't have all those console logs. Install Superbase. And go ahead and create the project. I believe I've already created a project. So let me just show you. So I have a project called Workshop. And inside of this project, can you have more than one onGET in one file? So you cannot have more than one onGET because ESM, you can only export one function of a given name. So you cannot have more than one onGET. But you can certainly have an onGET and onRequest. So you can have two onGETs, one onGET and one onREquest. So you can have two different name functions. And you can certainly have onGET in all of the layout files in between. I'm not sure if there is a use case for having more than one onGET, as in like you're saying that both of them should run. And given that it's the same file, just put them in the same function. Hopefully, it answers the question. But you can certainly have onRequest and onGET onPost in the same file. And you can certainly compose them by putting them inside of the layout files in the request that I'll view. OK, so I've already went through and I created a database called Favorite. And if I go and look into it, the tables, how do I go into the database? All it shows in the database. I want to see the data. OK, why did I forgot how to do this? There's a table, yeah? There's seven rows. How do I see the seven rows? You, you, you, you, you, you. I have forgotten how to use this tool. Database. That's the table editor. Database. Here's my table. Why can't I click on the table? Division. I have forgotten how to use this tool. BGMI. Database. Oh, here we go. All right. So here's our existing records that we have. And so we will try to basically have a favorite button that tells you whether or not a particular project is favorited based on your username, and repo, and user. But before we jump into it, let me ask a question. What is the difference between a route loader and an on-get function?

14. Using Route Loader and SuperBase Integration

Short description:

The on-get function provides low-level access to HTTP request and response, allowing for full control. In contrast, the route loader is a higher-level concept that abstracts away low-level access and allows for easier data retrieval. Multiple route loaders can be composed into a single request for a route. The route loader is a high-level way of fetching data, while the endpoint provides more control but requires more responsibility. Let's hook up to SuperBase and fetch data. We create a SuperBase client and query the connection. The shared map allows route loaders to pass data to each other. The session, obtained from the shared map, contains information about the currently logged-in user.

So the difference is how you use it. So this on-get is meant to be a lower level thing. So you can think of the on-get as a low level primitive that gives you a full access to the HTTP request and response. So from here, you can set the response, set the cookies, basically do anything you would want to do with an HTTP request and response. The route loader is built on top of on-get and it's a higher level concept. And so instead of getting a endpoint, which is a specific JSON shape that you control, the route loader doesn't give you the low level access, it basically just says, what is the data? And here, you can return whatever you want. Now, typically, we return JSON data because we're talking to another JSON service. But here, you could return other things. You can certainly return objects that have cycles. You can return functions, closures, all kinds of other things. And because of that, the JSON format is not meant to be something that you consume. Instead, it's like an implementation detail that happens underneath it. Also, the way you consume the route loader is you consume it by simply getting ahold of it. So as far as the developer is concerned, you're just kind of calling a getter function that gives you this thing. The fact that underneath in the server environment, it just talks directly to the function. But then in the client environment where we do a client-side navigation, we do some kind of a custom HTTP request to kind of fetch the data that's hidden from you. The other thing to kind of understand is that when I go and I do client-side navigation, not only am I executing this route loader, but I'm also executing this route loader here because this is part of the kind of the request path. And so I can get route loaders kind of additive. Meaning that you can have many of them inside of layouts. Do I have one here? Sorry. T put sex, I don't think we have one here. We can put multiple of them in a lot of different places and they all get composed together into a single request for the route. So in this particular case, this is just one of many different route loaders that got activated in order to bring the data in. So you can think of route loaders that are kind of like high level way of fetching data that where you don't think about, like, how do you set the query parameters? How do you serialize the data? All this stuff is abstracted away. So it's a very high level kind of a concept. And you just worry about like, hey, I just want to get the value. And the value you get here is a signal, which means that if the route loader reruns and updates, this signal will then cause maybe a re-rendering of the dataset, right? So the route loader is much higher level than a primitive like an endpoint. Endpoint only really allows you to kind of control everything, but you're responsible for everything. I hope that makes sense.

Okay, so let's get back to hooking this up to SuperBase. So we have installed the SuperBase, and let's run the server again. So now that SuperBase is installed and let's navigate to our, let's go to Quick here. So if we go to our database, we see that, that, is this favorite, is it not? No, it is, and we have read Quick, right? So this repo is already favorited in the database because this particular row, this particular in the row in a database already exists. Make this thing smaller, no. Okay, so we would like to fetch this. And the way you do the fetching is, so let's do this. So let's do our repo, so we are in here. We are in... Sorry, this view. So we would like to fetch the data of use is favorite. And so for now, I'm just going to return a random number and basically says, sometimes I'm gonna say it is favorite and sometimes I'm gonna say it's not favorite just to kind of make a point. And notice, now we have two separate routers, so route loaders to go to the previous point, right? And these two route loaders are fetching different data, but they get composed into a single request if it's a client-side navigation. And if it's a server-side navigation, then there's not even a request, it just, the server just does all the parts. So here we're gonna get it's favorite and right here, we would like to print a button and we would like to show that it's favorite.value, okay. So we would like to show whether it's favorite or not. So if it's a favorite, I'm gonna insert an emoji, star. So here we go. Oops. Okay, what happened to my emoji thingy? Okay, so now we have a solid star and an outline of the star. So now if I go here, now you can see that randomly, it will just flip over, right? Sometimes it's shown and sometimes it is not shown. So now we have a wrap loader that loads the data and then we pass the data in its favorite and based on that we render different states. So instead of doing this, let's actually talk to superbase. Let's do the request. And so we will create a superbase client. Again, you will need to get the URL and the keys and this is a request event, so like this. And now we have a superbase. So this gives us a connection and so now we would like to query the connection. First, before we even do that, we will const I forgot the syntax. Is it user? Charmar repo? How do you do it? I forget how to do this. Sorry. There's a syntax we can do. Let's take a shot. Let's do it in separate shots. Wait. Did it do it? It's just complaining that it's unused. Oh, I think it did get it correct. Oh, I just mistyped it. OK, so I did get the syntax correct. So we have a user. And then we have a repo. Oh, we also need to get a currently logged users. Let's get a session share. So all these routes loaders can pass data to each other. So that's the shared map. So here, let's do this. And so what we want to do is we're going to say, session is shared map get session. Let's see if I remember this correctly. Console.log. We want to print share session, user. OK. Oh, I got it right. OK. So there is a session inside of the shared app. So just to be clear, the reason there is a session inside of the shared map is because the authentication plugin does auth js, place the session. So there's this unrequest, right? So it's a top level middleware that executes for all requests. And because it's a top level middleware, it looks at the cookie and based on that, it tells you what the current logged in user is and so that logged in user you can retrieve as a session. So this plugin, this unrequest, which is a top level middleware, goes and inserts the session into the shared map. And so now anybody can get a hold of it in this particular way. And so you can see that we can get an email. So what can we do is we can say const user equals session.user.email.

15. Toggling Favorite Status and Database Interaction

Short description:

We can get the user's email from the session and use it to fetch data from the super-based database. By setting up an action, we can toggle the favorite status of a repository. The form object knows the property 'favorite' and its boolean value. We can use Zod to provide strong types for the form. Clicking the button executes the action, which interacts with the database. Refactoring the code and implementing best practices is recommended. The database client allows us to perform useful operations, such as favoriting or unfavoriting a repository. The code for favoriting is asynchronous.

So what can we do is we can say const user equals session.user.email. I believe that this is really a session. But I think it might be null or undefined if it's not locked in. And we need to input this. And so this wants to be a question mark. It's not against the question mark, but it's a question mark. OK, so now instead of session, we just put user. So now we will see. Oh, email is not user. OK, so now you can see that you have a request, that the session is showing me my current email and that I am currently fetching the [? emyhevry,?] So emhyvry quick. And [? emyhevry?] is logged in, so it's showing my name over here. So you can get all of the parts in here.

So now that we have a super-based client, we would like to determine, instead of randomly deciding whether it's favored or not, we would like to get a hold of it. So if you super-base, we've got the syntax. I apologize if you see. So the syntax is get super-based client, from the table favorite. Remember, I created the table beforehand. Select all the columns, but only if the email is email user and repo are the values that I've extracted. Show an error otherwise, otherwise you say favorite.linked. Okay, so now we're saying like, hey, if we returned any data then it's probably favorite. Well it's not, it's probably favorite. And if we don't return any data, then it's not. So now that I navigate to here, you can see that it's showing it as being favorited. That's no longer random, right, it's always showing it. And the reason why it's showing that is because if you go to the super based database, you can see that mishko, repo, quick, and Hevery is being favorited, right? And so it's here. Now the thing I would like to do is if I click on this, currently doesn't do anything, it should go and change the value inside of the database. So let's do that next. So what we would like to do is we would like to set up an action. So let's kind of dive into the actions. So export const, use toggle favorite, perfect, love AI. Okay. So now we do this and, actually, it's called data. I'm still in that the node around action. Data, oh sorry, not about, little bit around with action. It is form, that's what it says, okay. So let's just print this particular thing out so that we can see what's going on. So what we're going to do is we're going to pick this button and we're going to wrap it in a form. And this form wants an action, so I'm going to say const toggleFavorite equals useToggleFavorite action, this should be action, okay. Very simple. Then we can place the form into an action here. We need to import this. Okay, so we just created a button inside the form. So if you look at what's being rendered here, right, you can see that there is a button and this button is inside of a form and this form is pointing to a particular action, right? And so the session, where's this coming from? Here it is. Okay, so now if I click here, I'm gonna submit the form. And what you can see is that it says use toggle favorite. Right? The form object here. Here, this form object is empty. It has nothing interesting. And so a typical way to do this with forms is you say input type equals hidden name is favorite. And the value we wanna show here is, well, if it's currently favorite, if it currently is favorite, then we don't want it to be favorite. Otherwise, if it's not favorite, then we want it to make it true, right? So now, right, because it's favorite, if I click on it, then it prints favorite is equal to nothing Right? So this object literal has essentially this input value from the form. But if you hover over it, we see that it's just the generic type called JSON object. It's not specific enough. And so what we would like to do is we would like to use Zod to give it a strong types. So Zod and what we're going to do here is we're going to say that we have a favorite and it is going to be z.obj. Okay. Okay. So now when we clicked it, we noticed if I hover over a form, it's no longer a generic JSON value object. Now the form knows that there's a property called favorite and it's Boolean, right? So let's give it a try. So if I hit this button, notice it says that, hey, it was favorited. And there was a false in here. And so if I go and I, for example, forget to have the input field, if I comment this out, and if I click this button, now it, oh, because it does a corgi. Ha ha. Okay, so emptiness is the same thing as false. And so in this particular case, it corsed it to Boolean. So it's assuming that it's false, okay. If there would be no corgi but it would be like a string, then the string would give you an error saying like, hey, I didn't find this particular value. Okay. So this clicking this button executes this console.log. And so we would like to actually talk to the database and do something useful here. So I'm gonna copy this piece of code that's talking to the database. And we're actually gonna need all of this information. Okay, so let's copy all of this code now. Obviously you can refactor this to common methods and best practices, et cetera. But I just want to keep it simple and take this and get it as a secondary. So now we have a simple database client that we can use to talk to. So we can now say, if, instead of a form, just gonna say favorite here. So if favorite, right? Now we can execute code here. If you wanna favorite it and if you wanna not favorite. So favoriting something, the code for that is, I'm gonna cheat here and look at my solution. Okay. And this has to be asynchronous. What is the asynchronous? So, actually it's a little more complicated.

16. Favorite and Unfavorite Actions

Short description:

We can now execute code to favorite or unfavorite something. The code for favoriting involves an upsert operation, which inserts a new value if it doesn't exist or updates the existing value. Conversely, to unfavorite, we delete the data. This entire process happens on the client side, even if JavaScript is disabled. The actions work by programmatically submitting a form and executing the toggle favorite action. After the action executes, the query data is updated, causing a rerendering of the form and associated information. This ensures that even if JavaScript is disabled, the actions still function correctly.

So we can now say, if, instead of a form, just gonna say favorite here. So if favorite, right? Now we can execute code here. If you wanna favorite it and if you wanna not favorite. So favoriting something, the code for that is, I'm gonna cheat here and look at my solution. Okay. And this has to be asynchronous. What is the asynchronous? So, actually it's a little more complicated. So, if we are logged in, right? Because if we're not logged in, then we can't do this. So if we're logged in then look at the favorite and do some stuff. So if we're logged in and we are favored, then what do we wanna do is we wanna insert into the database email, user and a repo. Here, notice it's not, doesn't say insert. It says upsert. Two, okay. So, upsert is a insert that will either do an update or an insert. That's what's called an upsert. And so, it basically says, if it already exists, then just update the value. And if it doesn't exist, then insert a new value. Right, so that's just kind of what the upsert does. So we're gonna do that in case we have it. And in case we don't wanna be favored, well, we just have to delete it, right? The code for deleting is right here. Okay, so if you wanna be favored, then we do an upsert. If you don't wanna be favored, then we go and we delete what's already in the database. Right, and so again, the Zot does the data typing for us. And so, now, we have a nice action. So now, if we go to our application, when I go and I click this, you notice it says, used toggle false, right, so we don't want it. But notice that the button, actually, removed the data. So what happened was we clicked this button, this button causes the form to submit. Now, even though we are submitting a form, this is actually all done on the client, because this form has prevent default. So we're not actually submitting, we're pretending, we're pretending to do a roundtrip to the server, but actually that's not what's happening. We're just directly doing a post. So if you look at the network tab, let's refresh this again, and if you click here, notice that we programmatically did a Q data post with favorite equals true. We didn't actually do a roundtrip. So this is not a roundtrip. This is a programmatic submission of the form. So we go in the form so this then executes the toggle favorite action. That's this code over here. From here, we figure out whether or not we're asking to be favorited. We extract username repo from the past, we extract email address from the shared map that was placed in there with the authentication plugin. So now we have an email and now we say, hey, if we're logged in which means we have an email either do an absurd or do a delete of the data but because it's an action, actions execute before route loaders. And so after this executes, we re execute use is favorite. And in this particular case, we re query the data, we just update it. And now the query data says we are no longer favorites. And so that updates this signal, to say like, oh, so now this signal and now you're no longer favorited. And because the signal updates, it causes a rerendering of the form but also primarily of this information over here. So all of this is basically happening in the client. It's not around trip to the server. So the mental model from the developer point of view is like you're pretending to just doing a post into the application and then getting a full size re-render but actually on the client side what's happening is that you are just... You're doing everything in JavaScript. But an interesting side effect of that is that you can actually disable JavaScript. So even if the JavaScript is disabled completely, you can still favorite and unfavorite things because in this case, the JavaScript is disabled and therefore prevent default has no meaning. And so we do a normal post. So if you go to all, you can see that we're doing a normal post and then the response in this particular case isn't just an update to our data but a full on HTML response that renders on a server. So the nice property of the actions is that they work even if a JavaScript is disabled.

17. Super Base Setup and Route Loader

Short description:

We covered setting up a super base, using the route loader and route action to fetch and update data, and using the form for data submission. Currently, the route loader always reruns, but there is an upcoming API that will allow for cache rules to optimize performance by determining which route loaders need to be rerun. This feature is not yet available.

Okay, I think it's a good place to pause and sees if people have questions. So go ahead and interrupt me if you have anything on your mind. Let's have a look. So we set up a super base. We use the route loader to fetch data from the super base. We use route action to update data from the super base and then we use the form to do the data submission. So I think we covered everything in here. Okay. So I think we covered, can we prevent route loader from being rerun if the action doesn't influence the result to optimize performance? So good question. Right now the answer is no, the route loader just reruns all the time, but we are working on an API, which I think is currently in the works. So hopefully it'll be out in the next couple of weeks or a week or two. And this API allows you to set basically cache rules on the action of cache headers kind of a thing, you can think of it as cache headers, not actually a cache header. And this basically will be using kind of the caching strategies you can then tell the system that which part of the system which route loaders have been invalidated by your actions and which have not. And so therefore, as a result, you can get proper updates or you can kind of save yourself the trouble of talking to the database, if you know that particular router doesn't have to be rerun. So that feature is coming, it's not available just yet.

18. Advanced Concepts and User Tracking

Short description:

Let's talk about advanced concepts and create a new route to display a list of favorite repos. We can use the code provided and modify it to return the favorite repos. We can also implement a search box with a debounce feature to search for specific usernames. The use task function is similar to use effect in React but with some differences. It allows us to track changes in the user value and delay the execution of a function. The use task function is useful for creating side effects that happen outside of the application. It can be asynchronous and requires explicit tracking. On the other hand, use computed is used for derived values that can be synchronously obtained from the existing state. Use computed does not require explicit tracking and is automatically updated. Another option is use resource, which allows us to fetch values from external state.

Okay, so let's talk about advanced concepts. So what I mean by advanced concepts is that we can do really complicated things. So let's make a new route, which gonna display a set of users. So inside of here, we're gonna say new file, index.tsx. And as always, we have to have a default component. And so you're here, I delete everything. I will see just, hello quick, right? And so what I would like to do is show a list of, let's show a list of all your favorite repos, right? So I'm gonna just cheat a little bit and copy the code here. Oh, I see this is a different code, okay. So we will use this favorite, okay. Let me copy this code here. To here. Here. And here. Okay, and so instead of returning the length, I'm gonna return a favorite, and that's gonna be, I don't have a type information set up for the superbase, but you could definitely do that, there's a stuff you could do. So we're just gonna say that we're gonna have email string user, oh, perfect, okay. I don't know how to compile a new list, but there's correct information. Okay, so now let's go and get it. So that's not is favorite, use favorites. Let's connect that to the whole, so that Experian becomes like a second method. There we go. Saving this. I think we already got the demo going, so save it exactly as we did the demo, and then we can forget about it. And then everything we done so far, and we can go feed our watch, which is gonna be the first round of media creation. And then the lab, which is gonna be the last one we use for media creation. I'm gonna show you two different ways of doing it in a minute. I'm gonna create a user, that's feeds this index, let's year, yeah, just, Okay, let's see, all because we create gray, we want to query use a specific repo, and I'm just going to put all this, there we go, so here's our list of favorites. So there's two, okay, so we have a list of favorites here and I can click on any one of them and opened up in a separate file and come over there. So that should be pretty straightforward, so let's do the next thing, which is, I would like to execute, basically, I would like to have a search box here, which allows me to search for any specific username in it, so let's see, I think what I have in mind is here, it's username input.text user. User needs use signal. So, you know, I just have username and had to type in here, and I would like to display all the users that are in that document, in there. So, but because I want to talk to the server, I don't want to overwhelm the server, and so what I really want is I want to have a debounce server debounce, debounce user, which is, well, what is this? Which is a, it's same thing as a user, but delayed, right? So by print debounce user dot value, I would like to see it delayed. So for that is something called a use task. So used to ask is similar to use effect for people who are familiar with React, but there's enough differences that we thought that naming it the same exact thing would cause confusion. And so while you can think of it kind of like user effect, there is enough differences between user effect, how user effect works and how it works in Quick that it's worth kind of going into it. So what I'm gonna do is I'm gonna say track and we're gonna say that. So we basically saying, hey, whenever a user dot value changes, we wanna rerun this function. So, so far it doesn't really do anything useful. So what I'm gonna do is we're gonna say, set timeout. And we gonna say that debounce user dot value is equal to value after 500 milliseconds. And then we also have to do a cleanup. So if you, if this function reruns before the, you know, before 5 milliseconds is up, then we should do a cleanup or we clear the timer, right? And so now notice there's a username here and I can type in Hevery, and as long as I'm typing, nothing's happening, but the moment I stopped typing half a second later, M Hevery shows up in this page. Sorry, I was just rereading the question, I already answered this. Okay, so as you can see that there is a delay right? Is a half a second delay, but notice as long as I keep typing, the half a second delay doesn't apply. It's only when I stop typing, right? So we're basically setting up a task and the task says, hey, whenever the user value changes, because there's a track thing in here, go set up a timeout and this timeout, this timeout sets the debounce user, a value to the current value of whatever the track gives you but it's a 500 seconds late and of course there's a cleanup function that is, if this function reruns before the other side then it's not right. Now you might ask yourself like, well, why isn't it a use task? Well, the first big difference, why isn't it just called use effect? The first big difference is that in React, use effect does not run on a server and in our case, if I do, we started console, a log, user let's say the user value. Notice that the user actually, user debugs. It was fatal debugs, okay. Notice that this actually runs on a server and the reason why this runs on a server is because on a server we set up tracking of this particular value, right, so the server learns about the fact that whenever a user changes, this function has to be rerun. And so because we learned this on a server, the fact that this is attached over here, on the client if you look at the console, no JavaScript has to execute, right, but when I start typing, it is at that point that the user debounce task re-executes on the client, but it knew to re-execute on a client because on a server it's set up a subscription to this signal constant, right? If we didn't execute this on a server, then we would not know, we would either have to eagerly execute this task on the client to set up a subscription or this would simply not rerun because, well, the subscription doesn't work. And so you might say, like, yeah, but like in the React, you just put the subscriptions in here on the end. Sure, but the same problem kind of arises, which is that the only reason it works in React is because React re-executes the component all the time, right? Whereas if I do a console log here, if I do a render, I'm gonna see a render on a server, but I don't see a render on the client. Even if I type, the render still is not happening on the client. What is the difference between use task and solid create effect? Why do you need to track? Can't it automatically know that there is a sign, signal, and rerun? Okay, so all of these are good questions. So the use task is, in many ways, very, very similar to create effect, but there are a couple of differences. So the first difference is that this task can be async. So we could rewrite this function like so. So let's say we have const delay equals, delay equals, yes, okay. Oh, right, but this will not give us a cleanup. Okay, that's okay, we can do it differently. We can treat this and clean up is a function that takes a B, which is a number I think when we return. And then we have ID. Is this gonna work? Yo, yo, yo, you don't like it, sorry. Get term if I fall, so. Okay, so what we can do is we can rewrite this code as await delay 500 cleanup. Oh, I'm sorry, sorry, I missed out here. The cleanup is a callback, which is a function which takes, which produces. I'm making this unnecessarily complicated, sorry about that. So in this particular case, this function is now async. And so we just need to do this part. All right. Okay, yeah, it works. Okay, so this is an alternate implementation. And the thing to understand here is that we were now allowed to do an await statement. So the first difference is that the use task is asynchronous. It can be a synchronous and therefore it's meant for side effect the things. And therefore it's meant not to automatically track. The problem is that if you put user value in here you could automatically track it. But if you put it behind the evade statement you couldn't automatically track it. And so you now you have a choice. Like you can have an API that says, okay we can do automatic tracking but you can't have async functions. Or you can have an API that says you can have async tracking, but you can only have automatic traffic in front of the await statement but not after the await statement. Or you can have an API that's a lot more consistent and say, hey, you just either, when it's asynchronous you just can't do automatic tracking. And so because we mean for this thing to really have side effects outside of the application we chose not to do automatic tracking in this case but notice that in the other example, which was right here we were using use computed, right? And use computed doesn't need tracking. And that's because use computing must be synchronous. And so the mental model is that if you are doing a derived value that can be synchronously gotten from an existing state then you should use, use computed and there is no need to do explicit tracking and it's all automatic. On the other hand, if you are not really producing a value but instead you wanna create a side effect that it happens outside of the system then it's probably gonna be asynchronous. And if you do, then you have to do tracking. And there is a third kind of way of looking at it which we haven't covered yet, but we'll cover soon is that maybe I wanna get a value but the value is not derived from the current state but really I have to go external state. And so that's a use resource.

19. Use Resource and Fetching Data

Short description:

The useResource function is used for asynchronous operations and requires explicit tracking. It allows for side effects outside of the system and can update signals. The useSyncExternalStore function is not familiar and requires further research. Signals cannot work with asynchronous operations, so explicit tracking is necessary. The useResource function returns a promise with data. Endpoints in Quick can be created using the useResource function. The useResource function is used to fetch data from an external system and return it. The get list of users function can execute on a server or a client. The useResource function is used to fetch data from the GitHub API and return a list of users. The user resource can be rendered and updated with the useResource function.

And so that's a use resource. And so in the use resource case, which we'll cover next, because it's asynchronous, you also have to use tracking. So basically the tracking thing is kind of a compromise between the fact that this function can be asynchronous and also because this function is not meant to, it's meant for side effects outside of the system rather than for the system itself, even though you can do these kinds of tricks where you can go and update the signal again and recause the thing to happen. So that's kind of a complicated answer.

Is something like useSyncExternalStore? Hold on, why is it different? Okay, so I answered this question. Is it something like useSyncExternalStore? I don't know what useSyncExternalStore is. Wow, it's backwards because I'm in Israel. Okay, hold on. Ah, sorry. Okay. I think this is closer to a resource which we're gonna cover next. Okay. I actually need to read up on this. I'm not familiar with this API, so it's a good learning for me.

Okay, so the track functions, basically when you have asynchronous, because of how signals work, signals cannot work with asynchronous, and so you need explicit track function. Or rather, it would be super confusing if I had to explain the fact that, oh, if you have async, then the signal will only work in front of the await, but not after the await. And the problem is it would be very tempting for somebody else to insert await somewhere, and they could basically break reactivity. And so for this purposes, with async we require you to track, and getting back to the used computer, this is, when you do async, you're actually returning a promise with things.

Oh, there should be an error, I need to fix this. This is an error, should be an error, because this should not allow you to deal with async for that specific reason. Okay. Okay, so we got it here. So here we have a memory, we do an update. And so now the... Oh, right, so what's the next thing I wanna do? So, after some debounce time, we would actually go and like to go and query the server. And so a route loader is used on initial navigation. And when we go and type in here, we don't wanna cause a re refresh of the whole page, right? We just wanna talk to the server. So in that sense we wanna do like an RPC call or an endpoint and there's easy way to do end points in quick. So first of all, you can do an endpoint as I showed you in this example, right? Which you can do get, but there's actually a simpler way to do it. So let's create a resource. So what we're gonna say is I'm gonna say I wanna get users and the users is gonna be use resource. So use resource is like the use task, but it returns a value, returns a signal right here. And so we're gonna have to track this. And so we're gonna say const user is equal to track the bounds user route. Perfect. So we basically wanna say that anytime you debounce value changes, we would like to return and analysis return, empty array. And so we will update the, well actually it could be like this console log users users. And yeah, okay. So now I actually can cheat and just keep returning a user for now. So now what we have is again, no JavaScript executes on the initial page load. Sorry, I want JavaScript only. No JavaScript. Then when I type my username, a half a second later, this updates and I want the resource to, all right, the users.value. Okay, sorry. So am I great? And then half a second later, you see that it says users, the promise is fulfilled and contains an array of the data, right? So it just contains my name for now in here. But what really would like to do here is talk to an external system to go fetch some data for us and then return it like so. So let's create a function for that. So let's say we have a function called export get list of users. And for now, let's say we just say users here. Sorry, user rather, it's gonna be query. And for now, we just gonna say get list of users query. And so here, and then we're going to return just let's, I'm just going to cheat and return the query for now. And so instead of doing all of this, we can just say return get list of users. Oops. And why are you not happy? Okay. So let's go to, So the difference between usedTask and useResource is usedResource can return data. Yes, that is incorrect. Well, that's one of the differences. There's also a different difference. You can only have one useTask running at a time. So, usedTasks are guaranteed to execute synchronously, whereas useResource is meant for pitching data externally, and as a result, you can have multiple useResources running concurrently. So, there's also a concurrency difference between them. But yes, that is a correct observation. So, fetching data from the server. So, I'm going to get type. And that's... And so, into our server data, we can now, this could be in a sync, we're going to query, we're going to talk to the GitHub, pass it its access token. Notice, again, because we're running on a server now, we have access to the environment and we can pass an access token. And we get a list of users and we can simply return it. And because we can return this, now notice that the user resource has a correct type. And so now, our user resource is now correct. And so the next bit to do is to render it out. So we simply do a resource. So just, yes. Import. values is users.

20. Fetching Data from the Server and Rendering

Short description:

Fetching data from the server and rendering it out. The resource is a set of users obtained from the server. The function executes on the server and returns the set of users. The resource is a promise that displays loading while fetching data and the set of users once resolved. The function is pinned to the server and takes a query as an argument. It naturally flows with type information and handles null values. The example showcases concepts like use tasks for debouncing, use resource for fetching server data, rendering with loading indicators, and pinning functions to the server.

But yes, that is a correct observation. So, fetching data from the server. So, I'm going to get type. And that's... And so, into our server data, we can now, this could be in a sync, we're going to query, we're going to talk to the GitHub, pass it its access token. Notice, again, because we're running on a server now, we have access to the environment and we can pass an access token. And we get a list of users and we can simply return it. And because we can return this, now notice that the user resource has a correct type. And so now, our user resource is now correct.

And so the next bit to do is to render it out. So we simply do a resource. So just, yes. Import. values is users. And I say on pending. Say loading. And resolved. into... Okay, so now for the users, we do users, not math. Hello. The commandline. Okay. So in the onordered list, redo. Users item. There's items. Oh, items. No, it's an array. So items is an array. So why are you complaining? Oh, because, ok sorry because this guy could be an empty array, but it's an empty array. So it's, okay. Oh. Users is an object that has items, so why are you complaining about it. Oh, oh. Oh, right, right, right, right. Oh, okay. So this gives me users, which is this users object. Right. So this gives us users. So this users end. Here we go. Okay. All right. Okay. So now we can type in query. And we get a list so notice what's happening right. We get we type in there is a delay that takes five milliseconds to update. Once this updates the resource reruns because the resource requires a private key. We are actually executing the component on the server. And so on a service are not the components the get list of users we're actually executing on the server. This fetches our data returns the set that we need and then we get back resource out of the users. And then the resource is essentially a promise. And so when the promise is pending state we print loading otherwise we print a set of users. So if I type in you will see that it will show loading for a second and it will show could set of users that are associated with the list. Now this thing should be clickable so let's fix that. Now I can click on it and just navigate to the next page. You can see that I can see a list of things.

Okay. So this is kind of an advanced concept because, you know, I was able to show you how, we can, well, let's go over it, right? So we have a component, the component, this was just showing the render that it doesn't render or re-render on the client eagerly, does so lazily. We get a list of favorites, which we get from a super base. We then get a user that is bound to the input. We then create a de-bounds chooser, which has a delay. This is the kind of the more complicated way of writing it, I think there was a simpler way before, before we refactored it. Then we say, great, we now need a resource. So the resource is a set of users. And so we look at the query. If we have a query, then we go talk to the server, otherwise we're in null. We know we're talking to the server because this function is, where is it? Right here, it has a server dollar sign around it. So the server dollar sign will ensure that this function always executes on a server. So in essence, we have created an endpoint that we can talk to and essentially an RPC call without having to worry about how to serialize things. Now this is useful for many things, but I just want to point out is there's still a time and place for something like a TRPC where you want to have full control over the response, you want to make sure the versions are stable or other things. So this is not just a total replacement, but in many cases this is good enough. This is a simple and easy way of doing it. So this basically says, make sure this function executes on a server. Because it's a function we can take a query parameter, right? On the server, we have access to environment variables. So we can get the environment variable here and just make a fetch request to the server. We use our currently logged in. Do we have to use? No, we just basically get a set of items and we return the set of users, right? So now the repository returns the set of users here. This causes the resource to delete the loading indicator and replace it with a UL. And this UL now has a list of LIs that come from the server, and we simply map it over and create a list. And so this is a showing a lot of interesting concepts such as use tasks for debouncing, it's showing use resource for fetching data from the server. It's showing a way of rendering things, showing a loading indicator while the data is being fetched from the server. And we're showing how to pin a function to the server. And notice that this function returns a particular type. And so the function returns a particular type. Also, it shows that it takes a query as an argument, right? Wherever this function is used, if I forget to call it with a query, notice that it complains about it to make sure we have. But also the type information correctly flows over here and so if I hover over user, I can see that this is a object of the correct type or null in case there's no query. So that all the type information just naturally flows without you having to set anything up. So it's a very, very natural way of doing it. Okay, so somebody's asking, can we use Lodash to debounce a get list of users? I think so. Let's see.

21. Using the Debounce Function

Short description:

Yes, you could definitely use the debounce function from lodash. Just make sure to execute the necessary code for tracking before calling debounce.

It's a good question. So lodoash devounce? devounce function 8 options are for this used. Yeah, so it runs this function. It runs this function. Yes. Oh, but you have to pass a different, no, that'd be fine too. Yes, you could definitely use the debounce. The only thing you would have to do is make sure that your task would somewhere call this piece of code right here. Right? So this piece of code would have to be executed so that the tracking happens, but once the tracking is happening, then you can certainly call them debounce, and this API should work with the lodash.

22. Integrating Headless CMS and Custom Components

Short description:

In this final part, we integrate our headless visual CMS and demonstrate its features. We show how to add images, customize components, and allow the marketing department to modify content. We also mention the availability of lessons on Cloudflare deployment. If there are any questions, feel free to ask.

Okay. So we have about 10 minutes left. I'm going to show you one more thing and we can call it a day. Obviously, keep asking questions. If you have other questions, let me know. I would love to answer them.

Okay. So I'm going to show you how you can integrate with our headless visual CMS. So I'm going to do npm. Run quick. And I'm going to say I want to integrate it with VictorIO. So restart the server. There is a default route here, which is this index. And I'm going to leave this default route. And that's because when we ran the builder IO integration, it created a catch all route, which is right here. Basically, how this works is that if something matches one of the existing routes, then we use an existing route. If no existing route is caught, then we go to index to this one, this route is the one that will catch. Now this requires the builder public key. I've already set it up in the environment variable, just like the other public keys. And so if we did this right. Let's see. Let's check it out. So there is a hello world from CMS. Let's go to this, this, because I'm already logged in here. And so what I want to show you is that this is an integration. Oops, here. This, this section right here, notice that it says I need to sign in. So I notice that this section here not on signing, right? So this header clearly came from the layout. Okay. This is going, okay. This came from the layout, but if I click on our editor, this is going to take us to our editor. Now, obviously I've already set up our editor with the content. So we already had it, but the thing I want to show you is that, you know, the, the, the, the layout got activated, right? So the sign out is here and the footer also is showing up and difficult here and in here, that's using the wrong URL. So let's go and change this to our local host URL. And so now you see that we have our page. We have our page. OK, we have our CMS that's already integrated, and this CMS, I can go and, for example, say, hey, I would like to get an image in here. So now I imagine that you have integrated something like this with your marketing department, and then the marketing department is free to modify things. So let's say they want image. I'm just going to pick a stock photo. This one looks nice. And I'm going to pose a new sheet in the center, but that thing's too big. So let's go to Styles. And max width, let's make it 100 pixels. Right? So I can make this. I can hit publish. And it takes up to a minute or two to propagate through our, it takes a minute or two to propagate through our CDN. But in a few seconds, we should basically see our image has showed up. There you go. There it is. And so the nice thing is that the depth, the, your marketing department can go and modify anything you want that's in here. But I want to show you something, one more cool thing, and I think we are out of time and that is, we have custom components. And so what we can do here is we can declare a new custom component. Oops. Gonna call it search. And the search component. Right. Okay. Hold on. Okay. So I'm gonna take this search component right here. I'm gonna move it into a separate component called search. And the search component basically needs all of this. So root favorite needs a signal. Eval three source, and it doesn't need this, okay. Okay. So we just refactored the search component into a separate thing, right. Oops, okay. The search component is now defined. So... So, search imported, from the route, yes. And let's call it search, let's not worry about inputs. And so, now that we have registered it with here... Okay, so what the marketing department can do is, they can go and say, hey, notice that, there's our search component, and I can drag the search component, let's put it below the image. And published. And now, sorry that's not the... So, let's wait for the CDN to propagate. And so, now the marketing was able to use a existing component that they could drag and drop into the output, and you can see that the output shows up. So, we can say who is interested in QUIC, and you can see different users that have QUIC in their name. And so, now the marketing department is free to edit any, well, not any part, right, they cannot edit the sign in button that's not available to them. And they certainly cannot edit the footer that's not available to them. But this component right here is actually interact, can we type, I guess they cannot type. But they were able to drag and drop these custom components. So, you can create your own custom components for the editor. Okay, I'm gonna stop here, because I think we covered a lot, and see if there's any questions. I do wanna point out that everything we have done is available for you inside of these lessons right here. There is another lesson that we haven't had a chance to do, but it just basically shows how to attach Cloudflare so that you can easily deploy your application to the Cloudflare for your UI, et cetera. That is it, I don't think we have anything else on it. So, let me know if you have other questions. Otherwise, we are gonna call it a day.

23. Quick Rendering Paradigm and Quickify

Short description:

Quick provides a unique rendering paradigm that makes every application instantly interactive. The developer experience is nice, with simple ways of achieving a lot. React-Quickify allows turning React components into Quick components. Existing components can be used, although they don't have laser loading. Quickify is available for React, solid, Vue, SWAD, and Angular.

Hopefully, you've enjoyed it. Thanks for the nice words, I appreciate it. I hope that when you start playing with Quick, you will discover that not only does Quick provide a very unique paradigm in rendering, which makes every application essentially instantly interactive. But you also gonna discover that actually, the developer experience is very nice and you can achieve a lot of things with just, simple keystrokes, et cetera, simple ways of expressing yourself.

Is Salajay's on the list of supported frameworks? Yeah, so I did mention that we have something called the React-Quickify, which I did not mention here and I should have. React-Quickify allows you to take React components and turn them into a Quick component. Obviously they cannot do all this fancy laser loading, they still require hydration. But the benefit of it is that you can use existing components. We don't have a Quickify for solid, but we do have community members that have done it. So there is a community who has done a Quickify for solid, for Vue, for SWAD and for Angular. The core team of QUIC is explicitly supporting the Quickify for React.

Watch more workshops on topic

React Summit 2023React Summit 2023
170 min
React Performance Debugging Masterclass
Top Content
Featured WorkshopFree
Ivan’s first attempts at performance debugging were chaotic. He would see a slow interaction, try a random optimization, see that it didn't help, and keep trying other optimizations until he found the right one (or gave up).
Back then, Ivan didn’t know how to use performance devtools well. He would do a recording in Chrome DevTools or React Profiler, poke around it, try clicking random things, and then close it in frustration a few minutes later. Now, Ivan knows exactly where and what to look for. And in this workshop, Ivan will teach you that too.
Here’s how this is going to work. We’ll take a slow app → debug it (using tools like Chrome DevTools, React Profiler, and why-did-you-render) → pinpoint the bottleneck → and then repeat, several times more. We won’t talk about the solutions (in 90% of the cases, it’s just the ol’ regular useMemo() or memo()). But we’ll talk about everything that comes before – and learn how to analyze any React performance problem, step by step.
(Note: This workshop is best suited for engineers who are already familiar with how useMemo() and memo() work – but want to get better at using the performance tools around React. Also, we’ll be covering interaction performance, not load speed, so you won’t hear a word about Lighthouse 🤐)
React Summit 2023React Summit 2023
106 min
Back to the Roots With Remix
Featured Workshop
The modern web would be different without rich client-side applications supported by powerful frameworks: React, Angular, Vue, Lit, and many others. These frameworks rely on client-side JavaScript, which is their core. However, there are other approaches to rendering. One of them (quite old, by the way) is server-side rendering entirely without JavaScript. Let's find out if this is a good idea and how Remix can help us with it?
Prerequisites- Good understanding of JavaScript or TypeScript- It would help to have experience with React, Redux, Node.js and writing FrontEnd and BackEnd applications- Preinstall Node.js, npm- We prefer to use VSCode, but also cloud IDEs such as codesandbox (other IDEs are also ok)
React Day Berlin 2022React Day Berlin 2022
53 min
Next.js 13: Data Fetching Strategies
Top Content
WorkshopFree
- Introduction- Prerequisites for the workshop- Fetching strategies: fundamentals- Fetching strategies – hands-on: fetch API, cache (static VS dynamic), revalidate, suspense (parallel data fetching)- Test your build and serve it on Vercel- Future: Server components VS Client components- Workshop easter egg (unrelated to the topic, calling out accessibility)- Wrapping up
React Advanced Conference 2023React Advanced Conference 2023
148 min
React Performance Debugging
Workshop
Ivan’s first attempts at performance debugging were chaotic. He would see a slow interaction, try a random optimization, see that it didn't help, and keep trying other optimizations until he found the right one (or gave up).
Back then, Ivan didn’t know how to use performance devtools well. He would do a recording in Chrome DevTools or React Profiler, poke around it, try clicking random things, and then close it in frustration a few minutes later. Now, Ivan knows exactly where and what to look for. And in this workshop, Ivan will teach you that too.
Here’s how this is going to work. We’ll take a slow app → debug it (using tools like Chrome DevTools, React Profiler, and why-did-you-render) → pinpoint the bottleneck → and then repeat, several times more. We won’t talk about the solutions (in 90% of the cases, it’s just the ol’ regular useMemo() or memo()). But we’ll talk about everything that comes before – and learn how to analyze any React performance problem, step by step.
(Note: This workshop is best suited for engineers who are already familiar with how useMemo() and memo() work – but want to get better at using the performance tools around React. Also, we’ll be covering interaction performance, not load speed, so you won’t hear a word about Lighthouse 🤐)
Vue.js London 2023Vue.js London 2023
49 min
Maximize App Performance by Optimizing Web Fonts
WorkshopFree
You've just landed on a web page and you try to click a certain element, but just before you do, an ad loads on top of it and you end up clicking that thing instead.
That…that’s a layout shift. Everyone, developers and users alike, know that layout shifts are bad. And the later they happen, the more disruptive they are to users. In this workshop we're going to look into how web fonts cause layout shifts and explore a few strategies of loading web fonts without causing big layout shifts.
Table of Contents:What’s CLS and how it’s calculated?How fonts can cause CLS?Font loading strategies for minimizing CLSRecap and conclusion
React Summit 2022React Summit 2022
50 min
High-performance Next.js
Workshop
Next.js is a compelling framework that makes many tasks effortless by providing many out-of-the-box solutions. But as soon as our app needs to scale, it is essential to maintain high performance without compromising maintenance and server costs. In this workshop, we will see how to analyze Next.js performances, resources usage, how to scale it, and how to make the right decisions while writing the application architecture.

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 Advanced Conference 2022React Advanced Conference 2022
25 min
A Guide to React Rendering Behavior
Top Content
React is a library for "rendering" UI from components, but many users find themselves confused about how React rendering actually works. What do terms like "rendering", "reconciliation", "Fibers", and "committing" actually mean? When do renders happen? How does Context affect rendering, and how do libraries like Redux cause updates? In this talk, we'll clear up the confusion and provide a solid foundation for understanding when, why, and how React renders. We'll look at: - What "rendering" actually is - How React queues renders and the standard rendering behavior - How keys and component types are used in rendering - Techniques for optimizing render performance - How context usage affects rendering behavior| - How external libraries tie into React rendering
React Summit Remote Edition 2021React Summit Remote Edition 2021
33 min
Building Better Websites with Remix
Top Content
Remix is a new web framework from the creators of React Router that helps you build better, faster websites through a solid understanding of web fundamentals. Remix takes care of the heavy lifting like server rendering, code splitting, prefetching, and navigation and leaves you with the fun part: building something awesome!
React Summit 2023React Summit 2023
32 min
Speeding Up Your React App With Less JavaScript
Top Content
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.
JSNation 2022JSNation 2022
28 min
Full Stack Documentation
Top Content
Interactive web-based tutorials have become a staple of front end frameworks, and it's easy to see why — developers love being able to try out new tools without the hassle of installing packages or cloning repos.But in the age of full stack meta-frameworks like Next, Remix and SvelteKit, these tutorials only go so far. In this talk, we'll look at how we on the Svelte team are using cutting edge web technology to rethink how we teach each other the tools of our trade.
React Summit 2023React Summit 2023
23 min
React Concurrency, Explained
Top Content
React 18! Concurrent features! You might’ve already tried the new APIs like useTransition, or you might’ve just heard of them. But do you know how React 18 achieves the performance wins it brings with itself? In this talk, let’s peek under the hood of React 18’s performance features: - How React 18 lowers the time your page stays frozen (aka TBT) - What exactly happens in the main thread when you run useTransition() - What’s the catch with the improvements (there’s no free cake!), and why Vue.js and Preact straight refused to ship anything similar
JSNation 2022JSNation 2022
21 min
The Future of Performance Tooling
Top Content
Our understanding of performance & user-experience has heavily evolved over the years. Web Developer Tooling needs to similarly evolve to make sure it is user-centric, actionable and contextual where modern experiences are concerned. In this talk, Addy will walk you through Chrome and others have been thinking about this problem and what updates they've been making to performance tools to lower the friction for building great experiences on the web.