How React 18 improves application performance with Lydia Hallie === [00:00:00] Hello, and welcome to PodRocket, a web development podcast brought to you by LogRocket. LogRocket helps software teams improve user experience with session replay, error tracking, and product analytics. You can try it free at logrocket. com. I'm Noel, and today we're welcoming back Lydia Hawley, software developer advocate at Purcell, here to talk about how React 18 improves application performance. Welcome back, Lydia. Yeah, thank you so much for having me back. I'm excited to talk about React today. Yeah. Yeah. It's always good though. The well is deep in the react world. So there's always plenty to chat about. Yeah, before we jump in, can you refresh listeners or catch people up if they haven't heard you on a previous episode about who you are and what you're working on currently. Yeah, for sure. So currently I'm working as a staff developer advocate at Vercel. Mainly I specialize in JavaScript, React, web performance, all the good stuff, all the good stuff. Uh, In my free time I also just like to create resources that make it a bit easier for people to understand the nitty gritty [00:01:00] details of everything that goes into web performance and JavaScript. Yeah. Lydia has lots of really good resources on GitHub and stuff. If you're looking to go take a peek, we'll have links in the show notes. Yeah, super active online and I'm sure those resources are. Very helpful to lots of people. I think based on the, I don't know, interaction and numbers you get on some of that stuff, they probably are. I hope so, yeah. Yeah. So always good to see. But yeah, today we're talking about React 18. There was recently a blog post I think you made on the Vercel blog, is that right? That was yeah. Talking about the main thread and long tasks in the JavaScript browser. I think most of us probably have some grasp of that, especially if they're in the web, they know what the main thread and maybe not a long task, but they at least know what the main thread is, but I think it's it's probably not as quite a good of understanding on like how that plays with react and like how that you know, that relationship exactly works. So maybe just to start us off, can you explain what the main thread is, [00:02:00] especially in the context of react and what reactive should be thinking about there. Yeah, so I think first is the very basic, so not covering React. So the main thread, just get everything on the same level. We're referring to the primary thread that browsers use to, execute JavaScript, but also other tasks like layout computations, painting elements to the screen, user interactions, pretty much everything that happens in a browser. Issue, or not necessarily issue, but what could be an issue is that the main thread handles all the tasks sequentially, so one after the other. So if one task takes a long time to finish, which isn't too uncommon, all the other tasks have to wait. And as I just mentioned, that also includes user interaction. So it could happen that our code, our JavaScript code, or anything else isn't optimized and this task Takes, I don't know, like 500 milliseconds or even a couple of seconds to complete. During that time, the user interaction task won't be [00:03:00] able to get handled by the browser. And I'm sure that, you've, everyone has noticed this on websites, even on fast devices, but sometimes, if you're like, I'm on a MacBook and sometimes it's hard to recreate that bad user interaction or user experience. Sorry. But yeah, if you ever are bored and you just want to see how websites act and you can throttle the CPU and like dev tools and you'll see like, oh, websites are not that optimized to handle or for like for an optimal main thread, if that makes sense, or main thread tasks. So yeah long tasks as you mentioned before, is any task that is longer than 50 milliseconds. you think like, 50 milliseconds is not that long, that's really short. But when it comes to having a smooth visual experience, having that 50 milliseconds is very important. Yeah. So for developers it's very important to optimize your application that it can execute a task in under 50 milliseconds or a lighthouse will punish you and tell you that you have too many long tasks. Yeah. [00:04:00] Google search rankings, possibly all that stuff get Exactly. Yeah. yeah, then to close that loop, what again if you're a front end developer, what are things to look out for, and then particularly in the react space, like, how do you go about thinking about this? Where do you start investigating if you have some react app that you're trying to minimize your number of long tasks? The thing with React, with a traditional React render, which is also, by the way, still the case in React 18, we'll talk more about that later, is that a render is an all or nothing approach. When state changes, when props changes React will go ahead and render the entire component tree as a single... Uninterruptible task on the main thread but as you can imagine, sometimes we have components that are pretty complex and this can take a while to complete during that time users aren't able to interact with your application as it's still waiting for that component update to finish or like the task to finish and that can definitely [00:05:00] cause a bad user experience if this takes a couple, or even if this takes longer than like the long task Amount of time or even just a couple seconds, which happens pretty often. Your application seems frozen so for react developers it's important to ensure that your application doesn't re render too often and if it does then optimize your kind of components to Like only render or re render the components that actually need to be re rendered. So there's just a lot of thought in like application architecture that developers had to think about, even though it's not directly related to their application. It's not like directly related to your product, but it's still important for the user experience. And this is where some of the React 18 features help out to make sure that developers don't really have to think about that too much anymore. React handles that for you. Gotcha. Yeah, to make our examples a little bit more concrete here, what might be something one could do even like prior to React 18 to minimize the amounts of rendering that the component tree needs to do?[00:06:00] So there are like. Third party tools like debounce or throttle that we often used in react applications for example with input fields You know, we don't want to re render our application on every keystroke which By default could definitely happen if you didn't optimize for that. So in that case, you always had to rely on third party libraries or use, react up memo use, use callback to optimize your application for that. Still, again, all this takes like manual work, like a developer actually has to think about, application performance in that case. But those were memo and callback were built in methods Think about performance that way, but otherwise it was really only third party tools. There was no like built in solution to handle main thread work Like rekt18 does. Gotcha. Gotcha. So yeah, then let's talk about React 18 though. What does React 18 add? And is there, is it manual for devs to have to like, update with new features or is a lot of this kind of automatic with the [00:07:00] version upgrade? Well, So rekt18 includes a new renderer called like the concurrent renderer This is not the default but this new kind of rendering architecture is enabled by using suspense or transitions, which I guess we'll talk a bit more later, but essentially what it does is if our react 18 allows developers to mark certain component updates as non urgent. And in that case, if that components state changes or props changes, anything that would rerender, it will still. render that component in the background, right? Like in like the react client, it will, you know, reconcile that entire component tree, but while it's doing that, it's yielding back to the main thread every five milliseconds to ensure that there are no more, not more important tasks waiting to be handled instead, like user input, or maybe even another component that the user more recently interacted with and is more important To render so this is different than what we saw before, where [00:08:00] React had this all or nothing approach where nothing could interrupt that render, because right now it yields back every five milliseconds to see like, all right I'm re rendering this component tree, but is this still really the most important thing I should be doing right now, or are there more important thing that would create a better user experience pretty much so yeah, this is a new kind of rendering And I don't want to say mode because it's not concurrent mode, it's like concurrent features that enable this, but again, it's not the default. So by default, of course, React 18 still has this all or nothing approach. We have to optimize our application to actually enable this kind of new rendering method. Gotcha. Gotcha. So do you think in the future most apps will utilize this rendering method more often than not? Will this kind of be the, functional default at some point? Yes I think, what we already see happen now is that React expects frameworks to implement this instead to [00:09:00] have it under the hood. So with like users that really had to think about it, it's not using React, it's not like with create react app where we used react, bear react because it takes. A certain setup with the bundler and everything else to actually make this work smoothly and performantly. So it's better to just rely on frameworks like currently Next. js, but I'm sure in the future there'll be other frameworks as well. That people can use to To benefit from all these features without really having to think about it themselves. So there will be performance benefits, but even less work for the developer. So even though it might sound like, Oh, I'm going to start building an application with react today. And I have to think about all these features. No, you don't, it'll all just be abstracted away from you. Yeah, Yeah. I guess just naively thinking about this, it seems like deferring to the main thread every, five milliseconds or whatever. It feels like that. Would be useful in, or, would improve the user experience in almost all cases that I can think of. Okay. There's probably a few [00:10:00] edge cases where it's okay, I want the rerender here to have priority over, deferring back, see if anything else was going on. But I guess, are there non obvious reasons why we wouldn't want to be deferring back to the main thread? Like as often as we can let me think I mean, of course like an important one is just you have to make sure that what you're Suspending or like what you're non urgently rendering is actually non urgent. You don't want Your first initial render to be non urgent because you don't want just this blank page because react optimized something else instead So we want to make sure that kind of that initial render is still the most important thing But just make sure that initial render doesn't include too many components that it might still take too long to render that initial UI Yeah, I guess the one that sprang to mind to me was like something that's like going to trigger a big navigation or something. Like If you're submitting a form and like, you don't expect any further interaction after that. But like you said, I'm sure there's other. Yeah, well the thing of navigation is that for example with next. js [00:11:00] these navigate client side navigations are transitions anyway So they could be canceled. So You know, it won't really matter, but I'm sure that there are like edge cases that I don't think about right now. Yeah. Yeah. . So there's probably quite a bit we could talk about there. But like you said, the frameworks hopefully are handling most of this kind of transition for devs day to day. Is there anything, even if a dev is using a framework that they might have to change in how they think about? I don't know designing their applications or architecture to kinda handle suspense more elegantly Uh, You mean the users of frameworks or framework authors? the users of the frameworks. Yeah. Like the developers that are uh, wheezing them. you know, I think it's important to understand where you should even add a suspense boundary and what suspense is even used for, because I think at the moment a lot of people confuse suspense with just data fetching, and that's not necessarily the main Purpose of suspense, even though, of course, it shines with data fetching. Like the way I see suspense is really more of a way to mark certain [00:12:00] components as non urgent. And I think non urgent is also the wrong word for it. But it's just like a more streamlined way to handle like asynchronous operations with suspense. And still what we see also with Next. js and also with React server components. Even though I usually refer to them as React build server components, but we can talk more about it later. That will still be rendered at build time, almost in a synchronous way. So in a lot of cases you won't even see that suspense boundary. But yeah, I think suspense and its integration with concurrent mode is like where it shines because of course like anything that's wrapped in that suspense boundary will be rendered in that yielding way. And for example, with Next. js, if you're using a loading. js file in your directory, then you have to understand that, okay, now I'm creating this implicit suspense boundary, so everything in here is seen as non urgent. In certain rendering modes, if that makes sense. So there's still some like, basic understanding that users need to have, of okay, [00:13:00] this, these are the implications of using concurrent features in my applications, even if frameworks Abstract a lot of it away. Yeah, I was trying, that's the word, I was like, why not sub, subtract what? That makes sense. That makes sense. So you mentioned in that last thing you said how people think about like suspense and React server components and all this kind of together in this. I don't know. Upgrade when having this discussion about the, about upgrading and you noted that you like to refer to them as react build server components. Why do you draw that distinction? I think a lot of people confuse, or they don't confuse it, but they think that you need a running server to use React server components, and that's not the case. You could even just host your React server component payload on just some kind of CDN and it'll still work. For example, what happens with Next. js is that at build time we generate the RSC payload, which is still that React server component format, which I'm sure you've talked about before in another podcast. But only in certain conditions. So with, for example, with [00:14:00] Next. js, when you're using like headers or cookies, then it'll be generated at request time. Cause you're using request based data, but otherwise we're just generating that payload at build time. Cause it's still pretty much like a static payload. So it's called React server components. And I think the confusion starts because we used to, also have server site rendering, which was always happening. At request time and then we had you know, like a static generation which happened to build time but now we have you know, react server components Of course, they still render components on the server, but that doesn't mean like a running at request time server it's also the build server and I think we need to make that more clear. It's like, all right, you can still use react server components even If you don't have a running server, which sounds a bit confusing. So that's why I just say any server built React build server components is usually the default for most applications. Yeah. So I guess devs that haven't, kind of seen this distinction or thought about it what are the advantages of having prebuilt components [00:15:00] if you don't have a running server from the browser's perspective? So how it used to be, of course, is that at BuildTime we generated the static HTML and then we generated the JavaScript bundle that included the React componentry pretty much to hydrate that static HTML client side. So client side what happened for the React renderer is that, of course, it couldn't do anything yet with that static HTML. We needed to ship React to the client, but then also the entire JavaScript bundle just to hydrate the page. But in most cases, only a few components actually needed that JavaScript, needed that interactivity to actually work. And in some cases, we didn't need it at all. Like the entire page was static, but we still needed to ship the JavaScript bundle to the client for the React renderer to create the virtual DOM. So in this case, we can just generate the RSC payload or the React server components payload, which is this streamable format that is essentially already a representation of the virtual DOM. So the client side renderer doesn't, like , we can ship even just initially [00:16:00] static HTML, but we don't need to ship that JavaScript to the client to hydrate it if it's not, interactive, if we don't have any event handlers, stuff like that. And it can use the RSC payload to still create that virtual DOM to know what the actual DOM looks like. And then with concurrent mode we only for example, with Next. js, any navigation, any client side navigation, we're just fetching. The RSC payload. We're not fetching any HTML. We're not fetching any JavaScript if it's not necessary. Just the react server components payload and then react client psych and kind of efficiently. So like, all right, now I only need to update these React components, which then, map to these actual on screen components. So you skip that entire JavaScript bundle part. But of course, we still need to have some runtime. So we still need the React client side renderer JavaScript code to be shipped. So it's not the Astro Island architecture where you're actually not shipping any JavaScript. No, we still have to include [00:17:00] that. But it, yeah, so it just works very well with the concurrent renderer to ensure that we don't have to refetch HTML and we don't have to ship additional JavaScript for a page that doesn't include interactivity, stuff like that. Yeah. So this, I think so segue nicely, one of the next areas I want to talk about, which is like the router. So either like using a router, particularly with the like next app router, how does this interact with this relationship where. React is just requesting from the server, which components is going to need to render the next page. How does that handoff work? Yeah, so with Next. js, any client side navigation is a transition. So that's the first thing. And as it's fetching that new page, it's just the RC payload, which returns the entire react tree. But then react client side is we'll only update the components that have actually changed during that navigation. And this is different from what it used to be because on every client set navigation react had to reconstruct that [00:18:00] entire. Tree. So you'd lose by default, you'd like loose state, stuff like that. But now we can just, we only update the component or the page dot JS essentially, and actually has that updated. But also because it's a concur or because it's a transition users, if they accidentally click on a navigation, but they actually, they changed their minds, they want to cancel that navigation. It can do so because it would prioritize that user interaction. As we talked about previously, so it's just a lot more. Responsive and you don't end up in this like annoying waiting time where it's oh, now I click this and I don't even want it. But I still have to wait for that task to finish. And you also have the benefit of not shipping that additional JavaScript by default. So it's just you ship less and it's faster and it's a better user experience due to the constantly yielding back to the main thread. Yeah. So are those, two benefits kind of the main two of using next router? Other than the developer experience, are those the two main big lifts there? Yeah, I think of course, like the fact that. [00:19:00] With the new kind of renderer, we're not able to also have this kind of persistent UI with user transitions. We, it's just easier to create applications in kind of a more intuitive way, because often, we have this persistent UI. And of course with the pages router, you also get it with a get layout method that you can export from a page. But this new kind of way of thinking about React It just, it fits really well with, or of course like the apparatus built around it, but the way that we now have to think about the directory structure of our application works very well with the new renderer. And also the fact that we can now. Kind of implicitly use these new features or that the app router leverages these features implicitly, like with the loading. js having this error boundary with error. js. We don't have to think about transitions cause it's all abstracted away from us with the link components. , there's just a lot of benefits to using app router because, but only because of the updates [00:20:00] made or introduced in React 18 pretty much. Gotcha. I guess if someone isn't using next for whatever reason, what's the easiest way for them to leverage some of this stuff? I think a lot of our conversation so far has been about how, the frameworks have made this easier for devs to use, but if someone has some app that they're not going to be able to port, but it'd be easier for them to do the version upgrade, , is there somewhere you'd. Recommend them to start looking. Like they were create React app, applications at some point, and they've been upgraded slowly over time, but they haven't switched to next or something like that. right. It's totally doable to migrate your application to use all these React 18 features, even though some of them are, like, considered stable in Next. js, but are still experimental in React. So that's the only thing to look out for, but otherwise, you just have to update your bundler code and there are a lot of examples also in code sandbox I also added an example in my blog post. I think den evermouth also has a lot of Really good resources on how you can implement [00:21:00] all these new features in your applications but again, it just requires a bit more dev work to Implement this in your applications. And also React didn't really design them to be implemented by kind of individual developers, even though it's totally possible. It's just not the most optimal way to use them. Yeah. Yeah. It's a lot of kind of plumbing work and work that might be, it might be tricky to get done. Do you I guess just like on a personal note, do you ever feel like it's tricky to get in and I don't know, understand how these things are working under the hood when the default seems to be like abstract them away into a framework. Do you think that makes it harder to learn and figure out these things? Or do you think it's still easy to get a grasp of what's going on but leverage the framework to make your life as a developer? Easier. Yeah. I think it can be pretty difficult to understand exactly what's going on also, cause it seems to still change pretty frequently. Yeah. Yeah. and I think. There's still some documentation lacking on certain sides. So it's just difficult [00:22:00] for the community right now to really understand, okay, what are these features? What can we use them for and what do we need to do to implement them? I just, I think personally, I just want to raise awareness of like why these new features were added into react in the first place and how they can improve user experience, but also developer experience, because I think, that's where. React is headed anyways, it's just making it easier for developers to optimize the application without thinking about it too much themselves React adds more of that, those performance benefits, or it's built in, so I guess, yes. Is there anything else you cover in the blog that or just even in react 18 that you want to talk about that you think devs should be aware of that we haven't covered yet. Yeah, I think there's also a common, not necessarily misconception, but people don't seem to like the fact that we cannot use certain kind of client side for example, React Context, they're like, oh, now I cannot use React Context anymore, , how am I going to, you know, upgrade, because I'm [00:23:00] using it everywhere in my code, and how can I then even data fetch, how can I share it across my application? And I think that is one of the biggest, not necessarily disadvantages, but that's probably Is what's going to take developers the longest is you have to rethink your architecture quite a bit to optimize for this new kind of rendering model, but also mental model. Because right now, React 18 includes the new cache and fetch or it, it adds like functionality to the fetch function to ensure that it memoizes the results in a single render. React developers have to rethink where data fetching happens and where the actual rendering happens, because now they can. Both live within the same file, within the same component. Whereas that used to be two separate kind of layers, you know, you had like your context, it's your provider, and then you had to pass it down to everything. Right now you can just implement it right into your component. And if you're fetching from the same API endpoint, [00:24:00] react will automatically, memoize that result. So you're actually only fetching it once, despite maybe using it throughout your application multiple times. And that is just something that's very important to remember is that, yeah, you, you don't. You can't use React context anymore in React server components. You can of course still use it in client components, but in many cases you don't have to. Cause right now there's this new, more like optimized way of fetching data. And also cause of course, like if you're handling data in a context, you also need this separate API layer and you don't need that anymore in most cases cause it's directly in your components. So there's just this, yeah, again, new way of thinking about data fetching and. UI rendering, all that kind of stuff that, we just need to create more kind of like educational resources on how people have to think about, yeah this new model, this new way of architecting React applications in general. And then also, of course, how, how client components and server components integrate because of [00:25:00] course you can pass you know, or when you should, import a component tree. In a file versus passing it as component props, just so it doesn't get included in the client bundle. There's more, or developers have to be aware of how React works or how the bundler works. With. These kind of things to ensure that they're still optimizing their client bundle because that may not always be very obvious. With that in mind what do you think the future of kind of performance in react is going to look like? What direction do you see the framework and the people working on it taking to make this easier for devs going forward? Yeah, I think you know We're all just focused on providing a better balance between having a good user experience or a good developer experience. And of course we'll see a lot of, frameworks adopting React 18 features and leveraging just under the hood, so developers don't really need to, add any manual optimizations anymore. Also, the ongoing [00:26:00] work with React for Git is also, I think, a very... I don't know, thing to think about in the future, which of course isn't necessarily part of the React 18 features, but I think there's too much focus right now on just React 18 and not the other developments, which are more about ensuring that components don't unnecessarily re render anyways, which we've just talked about, if they re render, then it'll be in a, un, or in an interruptible way. But how about components actually don't re render if they don't have to? Like that would be even better, right? And that's what React for Get is about. So yeah, there's just a lot of, main thread optimizations happening in React re render optimizations in a way that will benefit both users and also developers by abstracting it away in most cases. Nice. Yeah. Very cool. Thank you so much for coming on and chatting with me today, Lydia. It's been a pleasure. Of course. Yeah, thank you so much for having me. It was fun. Of course. Take it easy.