Advanced Remix

Defer & React Streaming

Kent C. Dodds

Kent C. Dodds

Professional Trainer
Advanced Remix

Check out a free preview of the full Advanced Remix course

The "Defer & React Streaming" Lesson is part of the full, Advanced Remix course featured in this preview video. Here's what you'd learn in this lesson:

Kent uses the network profiler in the browser developer tools to explain how the Remix Defer component works in tandem with React Streaming. A question about nesting suspense objects is also discussed in this segment.


Transcript from the "Defer & React Streaming" Lesson

>> Let's take a look at our network tab. And by the way also like as we're navigating between these we're actually still using the skeleton work that we did in the previous exercise. So this is not, this piece of like the skeleton UI that actually has nothing to do with the work we did with deferred.

Deferred will not, or the loader will not differ on transitions between the sibling routes like this, it only happens on the initial load of that route. That's important, little note, and so we're going to keep our existing work on pending UI. Okay, so let's take a look at the network tab when we load this, yeah.

So again the defer is only about the initial load of the route, that's it. And if you're transitioning from one completely different route to this route, then yes defer will apply. If you're loading the page, defer will apply, in other cases it does not, okay? So we load the page, we have this document requests and now the document request is really long, and you're like no my document request is long, but no, the document request is just open, and it's sending the data over time.

Unfortunately, I checked and the view source actually, here, let's see if we can do it this way. View source will sit around, and it doesn't show us the stuff that is being streamed in our weights. It buffers everything before it actually shows us all the stuff. But this is actually interesting to look at what is happening when it's all finished.

So if we look at the source of this thing, and let's make sure I'm on here, let's do a view source of that just to make sure we're on the same one. So, we have invoice 2000. So if we look for 2000 in this document, nope, that bad example.

Let's like get 10002. 10002, there we go. Now where it shows up in the HTML or in the DOM when we're all done, should be like somewhere in the middle between the body open body, and closing body. Well, let's look for the closing body, that's gonna be right here.

There's the close to HTML, if we look for that 10,000, that is after the closing HTML. That streaming folks, we sent everything, we sent everything between the opening and closing HTML. And then the server is still working, and the browser's like, I'm just gonna hang on to this request because you told me you're sending me more stuff.

So I'm just gonna hang on. And so, this is this piece is actually react streaming this is not a remix thing this is how react streaming works. Is they just you leave that request open or that that connection open and then when React is ready to go, yeah there we go.

When React is ready to go, it sends the rest of the resolved UI and data along to all along for the ride, and that part is the remix part that. Remix decides, okay, now that this is ready here's the UI that I need to send in it says here react here's the UI you need to send and react sends it off.

If we dive in a little bit deeper here, you'll see a div after the closing HTML that has a hidden prop. So UI doesn't see it, and then we see the table. That's the UI that was our result value. This is what we render when we're all done.

And this is gonna have all of the UI for this. There's our 10,002. And then after that, we have a script tag. And this script tag is responsible for taking the placeholder that we had before our loading state and swapping it with this content that it was streamed in.

It's magic, it is so cool, so cool how this works, so it's got this idea of s0, and if we look for s0, that's in the JavaScript. It says hey let me find it find that b0, whereas that up here, and we're going to swap that b0 with the rs0.

[LAUGH] Who comes up with this stuff, so cool, just think that is so rad. And so then in addition remix and remix is responsible for rendering those places where they're at, but React is the one that actually creates that script tag that does all of that. But remix also renders this script tag to put all of the data that we just streamed in into the React context that remix is managing for us.

So when you call user loader data again, it gets that result value and that goes in through the awaited and did everything. So that's why in here we see our invoice details and boom there's our total amount and thank you, JavaScript, for being such a jerk. [LAUGH] Don't build finance websites in JavaScript, I guess or something I don't know.

[LAUGH] Yeah, or we should do what is, what a big number, what is that called?
>> Big end again.
>> Yeah, big end. Yeah. [COUGH] Actually, I think that's how it's stored in the database, and I'm converting it to number, so it's my bad. [LAUGH] My goodness. That's hilarious.

So, anyway, that is how all that works. It just boggles my mind and on top of this, it's a little bit easier, or it's kind of interesting to see this if we're looking at the data requests. So when we're, when we're doing a full page load, this is all what's happening with the document request.

But if we transition from one completely separate route to this new route that has a deferred, then we can see the date of the request that happens and that one must be that one. And we preview and we have some info. It would be easier actually, if I just take, what the devil, will take this.

Copy that. Paste it there, watch. And that's streaming right there. So it streamed in my loader data that I was not deferring. So there's my customer info, I awaited that and that's why it was here right away. The customer details, whoever I gave it a promise. And so, it sent me just this identifier for the promise for the client side remix dinner.

Okay, so this customer details, I'm gonna make a promise for that, and I'm going to resolve when the rest of this gets streamed in. In fact, even the content type, for the response header, is a text remix deferred. So, this is not application JSON anymore, because we're streaming in, and you'll notice I've got a plugins, or an extension installed, that parses JSON for me, and it thought that this was supposed to be JSON.

It's not, it just kind of resembles that JSON. This actually sort of resembles server sent events, the way that that works. So, we have this new line, and then data colon, and then, the data for the customer details. So, those two things are the same. So you can defer a bunch of things, you'll get deferred promise, and I should probably mention that I'm showing you some of the implementation details.

These are absolutely possible to change. You should not rely on the implementation details, but I think it helps you understand how to use this if you understand how it works. So that's why I'm explaining all this. So Remix will grab this piece, parse it as JSON, and hand to those customer details, resolve the promise that our client is waiting on with this value.

And it's freaking magic, because it means now, watch this, I can prefetch this. I hover over that, and I'm It's being deferred, and boom. Tried to do that with your client side data fetching. [SOUND] Yeah, good luck. You have to wire that manually. It's possible, totally possible, but you have to wire it all up manually.

I'm controlling all of this with just this defer thing. And watch this, what if I decide you know what? Sometimes or like, maybe it's actually better to make the user wait, I don't know. How about we do an AB bucket test? So I've got this little will make a little function is or yeah, should defer, okay?

And then, we'll use some environment variable or whatever, you all do the feature flags and bucket testing like AB testing and stuff? So we'll just say, for the purposes of what we're doing, we're in the bucket that says, yeah, you should defer that, or let's say we're in the bucket that says no, you should not defer this.

So false. [LAUGH] So now, I say here should defer. If we should defer, then we'll defer that. Otherwise, we'll await that customer details promise. I just did AB testing of whether I should fetch it at like show that. So what we're doing here we've got a lever content layout shift, time to first byte, which do I care about, do I not want any content layout shifts.

No content layout shift, and I'm not going to defer, I'm gonna wait for this thing. So I don't get content layout shift. Do I want fast time to first byte? Okay, that's fine. I will defer, and I'll pay with content layout shift, and that's it. And you can split this up however you want.

You're like, well, one part of the data is slow, the rest of it's fast. That's exactly what I did here. I said the customer info that's fast, customer details, that's slow. So I'm gonna defer that but not, and it works like it totally does. So we're in the bucket that says no, we're not we're gonna wait for everything and so yeah, it's gonna take a while but there's no content layout shift, that's it.

Literally, we basically took the await keyword And we moved it into our component. That's all the code changes necessary for this. That's pretty rad. What if you happen to know that one customer getting their invoice details is really fast. So let's say Santa Monica is superfast, so we can say if customer is Santa Monica.

Here will say should or defer it, should defer, or the customer info is not our N. Let's say N, and it's not sent to Monaco, so, if we want to defer,. If we should defer because we're in a bucket, or we also know that it's not Santa Monica.

So like we can decide based on other data whether or not we should defer this. I happen to know that that customer's data is like really fast, so I won't even bother no flash a loading state let's just wait no content layout shift at runtime people. I can't.

I've never seen anything quite like this, only possible because of what React has done with streaming and what Remix is doing with that feature. It took you like all of five to 10 minutes to never have heard of this before and implement it. What? [LAUGH] Yeah. This is one of those things like, yeah, I could do that in all the way I used to do things, I could work around those problems whatever.

This is something no, you could not do this before this is an enabling technology that you could not do before that's I get excited about that stuff. Okay, [SOUND] so that is it. I think that's everything I have to share. Any questions? Yeah.
>> Can you nest suspense and awaits?

>> Yes, you can. And the cool thing is that, so we talked about React server components and the work that the React team is doing and suspense in general, and it's really exciting stuff. The challenge with that is that all they do our handle the fallback when something is wait, we're waiting for something to happen.

In that world, you can very easily end up with stairsteps, if you're triggering the fetch when the component is rendered. So you have one suspense boundary, and then here's the component that like, okay, we're gonna trigger and throw. And now, we're waiting for that one, and then that's done.

And now we're gonna go down to this one. That's gonna trigger another one. So now, it's just a stair step waterfall, not a good user experience. It's really bad. So what you have to do is you have to know all of the things that are gonna suspend, and you trigger them all at the same time that's really difficult to do, unless you're using remix.

Because remix does it for you because it's all in your loader, it's like here's all the data that this routing needs. So, yes, you totally can. I can't think of any use case on the top of my head where I'd want to do but no. Yeah, I could see situations like, let's say that, you've got a couple of elements in here where you want to defer a couple of them and so you could render the parents with the suspense boundary here.

I'd probably more likely see these as sibling things. But then there's also the suspense list component that can, like, help you make sure that they load in the right order, even if they load out of order, that they're rendered in the right order. But yeah, so you can have a couple of these things that are deferred and have them all nested.

And what's cool about that is they're all kicked off ahead of time. So you're not gonna be running into a stairstep, they're all running concurrently. So does that answer your question? Okay, cool. Any other questions?
>> What happens if you have an effect that changes something in the deferred context?

>> What happens if you have an effect that changes the different context. I'm not sure I understand, I'm guessing by effects they mean a use effect, use effect callback.
>> Yeah.
>> Okay. So when would it use effects change, the different context? You don't really have control over that.

So I can't think of a reason why you'd want to, so I'm not sure I understand the question.
>> If you can provide additional context, I don't think that should be possible.
>> I do feel like I have to mention that in chat there's a lot of like I'm in awe like crap, wow.

>> That's, Brad, great use case, etc.
>> Yeah. That's great. I am not excited by myself, cool.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now