How To Fix NextJS 13's N+1 Problem
Vložit
- čas přidán 2. 08. 2024
- Code: github.com/jherr/njs13-n-plus...
Survey: docs.google.com/forms/d/e/1FA...
Oha: docs.rs/crate/oha/0.4.5
👉 I'm a host on the React Round-Up podcast: devchat.tv/podcasts/react-rou...
👉 Don't forget to subscribe to this channel for more updates: bit.ly/2E7drfJ
👉 Discord server signup: / discord
👉 VS Code theme and font? Night Wolf [black] and Operator Mono
👉 Terminal Theme and font? oh-my-posh with atomic and SpaceMono NF
0:00 Introduction
0:44 Project Introduction
2:05 Building The NextJS 13 Application
7:26 Testing The N+1 Performance
11:21 Trying React's New Cache
13:34 Creating The Backend For Frontend
18:36 Analyzing The Returned Page
23:59 Doing Client Components Wrong
27:03 Outroduction - Věda a technologie
This is my hidden gem channel.
Criminally underrated
The idea, the preparation, the explanation and the editing...is so much work. I can't thank you enough for making these videos.
This is the first time i see someone talking about eager loading in js frameworks
Keep going!
Hi Jack, I really loved this video
I was really impressed by the way you presented the benchmark and explained the problem with such clarity and precision. Your expertise and attention to detail were evident and made a big impact on me. I would love to see more of this type of content from you in the future.
Maybe I am missing something a core concept but.. when you make a backend for your front end, you combine the “get all ideas” and “get iPhones with ids” into a singular request that your React (front end) can can request. Yes it turns 51 requests into 1 request but 51 requests are still being made in your application to the backend server to get the data. You say it only happens once when the route is accessed. So basically it’s the same as the cached version regular n-plus-one version? When the data is cached in n-plus-one-cached you no longer make those requests to the backend after going to that page the first time. You just return the cached promises correct? So is the performance boost here that in n-plus-one-cached you still return 51 cached promises on each page load after the first one, as opposed to “backend for frontend” where it’s just a singular cached promise that is returned on each page load after the first one?
Love all your videos!
Very nice, Jack. Thank you! I had the same problem using graphql one time and I've solved using a npm lib called dataloader to batch the operations. So we would have a list of IDs to be fetched instead of a list of iphone requests.
25:55 blew my mind. Really appreciate this video. Keep it up with such examples. Love the "Don't do" section. I think we need more of these
An awesome video that was well worth the watch. Thank you for exploring this tech early and explaining to the rest of us!
Amazing breakdown! Great content as always!
Very informative video! Thanks Jack, this is really helpful!
big props Jack for all the explanations !
15:24 could have used Promise.allSettled/Promise.all instead here to fetch concurrently. Using await in a loop will block execution until the Promise has resolved before continuing the loop. Since there's no dependencies between each async call, there's no need to wait around.
Additionally, you should run benchmarks against a production build rather than development because dev environments skew the results on top of the already rather flawed benchmarking.
Tested the above with Promise.all and it seems to be negligent with the amount of data used in the example. Would love to do a more realistic example of hitting a remote server instead.
underrated channel, never a trivial tutorial!!
Hey Jack,
I'm having a hard time understanding what the benefits of server components are when solutions like React Query exist, and how/why fetching data on the server is so much more performant than just fetching on the client and showing a loading spinner
If you could make a video about either or both of these topics I'd appreciate it a ton. Filling out the survey now, thank you for the great content as always
"More performant" is the wrong takeaway, either can be performant in their own way. There's tradeoffs between the two based on the kind of UX and requirements you have. While spinners aren't the worst UI to display users (there are plenty of worse things, like flashing styles), in some cases it's not desirable, at least for initial rendering.
When able to, you should provide the UX that makes sense for your use-case. For simple listings of products, spinners are just noise as they aren't triggered by user actions and it makes things seem slower.
It also depends on how you consume said API. Some things may not be necessary, or takes additional time to fetch upfront, so will be required to load lazily in order to not stop the entire page from rendering with the most important content.
If your most important content happen to be slow to fetch, then that's where you should focus on optimizations.
one more reason is that you cant control client speed and location, while your server speed and location is a constant.
what I meant by this is if you fetch on a slow client, it will be much slower than if you just fetch html once (all data fetched from your server which should be fast)
Smaller client side bundle. All the code lives on the server. Client gets hydrated with essentially just markup.
@@dynatle5450 That depends what you're sending. If the HTML sent is larger than the what the payload would be for individual data fragments, that you could fetch lazily then it would cost more time for both server and client.
This is especially true for things like pagination (infinite scrolling, et al.) where there's no need to fetch entire HTML for each request, only the necessary fragments of data required to render a component.
Also, server is close to Database, so if you need to fetch a lot different thing, better do it on server and then send to the client whole page
Thank you, Jack! That's why I never miss any of your videos.
Now I should really take the survey 😊, you guys are doing such a good job.
🙏🏾
this is by far very useful content. you're making us a great developer just like you Jack! you're awesome!
That shell is very pretty 🤩 I’d love to see a video on your terminal/shell configs
Excellent chapter of "the more you find out on your client components, the more you F around your server payload"
I think its important to discuss why n+1 problem araised in the first place, the performance boost having page load way faster was because components only fetched data after they got rendered in server/ client. So moving the data to a point where the component (parent) is redered first will give you fastest fetch of data possible which is also required to finish the redering of the individual components.
Hello Jack! I want to express my sincere appreciation for your hard work and dedication in creating each video. Your generosity in sharing them for free and with such exceptional quality is deeply appreciated. Thank you so much.
Awesome video 🎉thank you
Waiting for more
wow that's very informative, i haven't heard of that cache in react thing before !
That is really good🎉🎉
This channel has helped me a ton
Extremely interesting!!! Thanks for the video!
Great tutorial, thank you.
thank you sir, you produce very good content
it doesnt matter if phone or pokemon.
just make sure there's a stringMander.
Thanks for so much Jack. Excellent video. Perfect for improving preformance. When you can make some video about render types for Nextjs, please!
Nice! You know what, video worth it just to learn about oha, thanks!
You might simply pass options to the useMutation wrappers and use onSuccess in order to remove useEffect 😊
Great work
Thank you!
Great video as usual! I do feel some of this might be a little deceptive, by generating all the iPhones up front in the backend for frontend you essentially turned the API requests into something like what a static site generator would do. This works nice for this static API but if the API needs to be fetched with user data that approach wouldn't work well
Great video - thank you. Btw, what font is that?
That's nice to know that the boundary point of going from SC to CC should be thought carefully.
in my project I left the mapping on the server component and made a client component only on the leaf, passing down the object as a prop, here instead in aggregated-client you cut at Phones [20:50], I found this interesting and I would be curios to check the benchmarks for this case, essentially leaving the use client only on single phones
nice one!
you deserve a million subs
the last bad example reminds me of a beautifully stupid faux pas i had a couple of months ago where i had a getCategory() function that took an id and gave back information and then i used it to get the metadata from category ids in a navigation, but i forgot that i built that getCategory function for actual category pages, so every item in the navigation included the data for the first page of products
Awesome video as always, what terminal theme is that?
It's OK, just read it above
Survey done! ❤
Thank you!
Survey done
What would be the optimal way to share that same data in another page that also needs it?
If it was only client and react, we could have 2 useQueries, one for each page, and whichever got visited first would download it and the other one when it got visited would use it from the cache.
Vera good!!!! Thanks alot
With the introduction of Next 13 to use FETCH to get data, if I have a mysql database which I will have my utility functions using 'mysql2' to query the data, which of the following method is best to get to the data for the ServerComponent page or the ClientComponent page?
1. Create an api route, and then use Fetch
2. ServerCompoent page await the import of the utility function
Thanks!
Thank you!!!
By the way Jack, couldn't we use the aggregate pattern in the original "n-plus-one" where instead of doing n+1 calls you could do the aggregate call in the main top level component and then pass data down the line? Personally I believe that it wouldn't be that much of a difference from the "api aggregate" you demonstrate.
As an aside, how would an RSC-with-aggregate-fetch compare in terms of caching and memory performance versus an RSC-with-internal-API-call? Isn't the second case a bit of an overkill in terms of system resources use when scaling it up, if you have to both call an api route AND do a server render?
You're right, I should have. This adds a network call that isn't required. I think I just got carried away on how cool the API stuff is.
thx!
If you make a full video about production ready app and e2e testing in nextjs 13, it will help me in interveiws
Very well done video. Tho in theory, this is a backend problem :D
Do you have a video of your vscode setup by any chance?
I really like ur pokemon api
please correct me if i'm wrong. The point is instead of making n+1 requests on 'frontend' side, we turn into 1 request on 'backend of frontend' side, and due to Nextjs server component caching, the performance is improved, except for the first request?
Yeah, we get rid of the hidden N+1 pattern that NextJS allows us to make.
In the localhost:3000/iphones can't we make parallel requests to get each Iphone data rather sequentially looping it with a for loop?
You could, but it's only run once on startup so it's not a huge overhead in this case.
Hi jack, thank you for this tutorial. What if you want to fetch a list of 10000 ihpones in the server. Do we have a limit?
There are no framework specific limits. But the more data you get the more tags you'll make and data you'll transfer and all that. Nothing is free.
I miss the Pokemons already
But the phones are so cool! And I'm getting a new one!
Hi! thanks for the video!
Does this new fetching syntax work only with experimental Next 13? Not with stable next 13?
It only works with the App Router, which is experimental. It does not work with the NextJS 13 pages directory, which is stable.
Basically this is applying the Fundamental theorem of Software Engineering. The "aggregate" is nothing less than a glorified cache. Still really good info.
One thing I like about these meta frameworks is how easy it is to add this "extra level of indirection" by having a backend for frontend.
I find myself re-treading along of old SE wisdom, but hey, it works.
Hey Jack,
It would have been nice to do one more example with request batching, basically what dataloaders do. They are most used with graphql where the resolvers exhibit the same N+1 problem as RSCs.
That way we keep the data fetching in the components but while only a single request is actually made to the API.
Another topic I would have liked you to tackle is component streaming: making a single big request might actually make the page load feel slower because no individual Card component can start streaming to the client before the request is done.
I believe that a slightly longer total page load but where it is progressively streamed to the client is preferable to aggregating everything in a request.
sounds like you know what you're looking for
graphql (apollo with caching) along with ent and boom, your request is translated to sql in the service to do the most optimal sql and minimal data transfer.
No doubt. Sadly we don’t always have the option to replace the backend API.
@@jherr I'm a big fan of building it correctly from the start :P
Love his voice xd
Is there a benefit introducing the network split for Comp? Or was it just to showcase how to use `use client` correctly. Because if we don't have interactivity, why bother with client comps?
It was just to demonstrate a client split if it wanted to make it interactive.
Server components are fine, but is there any way we can work with refresh tokens like in client components ???
I have question about caching. All the requests are made to phones with different identifiers so what is cached here? Doesn't caching make sense when you are making say several requests to phone with same ID?
Do you have any totorial for nodejs with typescript?
dude your terminal look so good can i get know about your terminal setup
I might sound dumb, but why don't we change the express server code itself to provide all the Iphone's data at once? Is it assumed in this case that the backend API definition is written that way and we can't change that?
In some cases, you might not have access to the API codebase, if you have access to the codebase, then sure, you should improve the API instead, the video that shown is just a demonstration.
Exactly, the idea is to imitate a real world scenario where the backend team is like "this is good enough".
The biggest thing I would like to know is how to deploy a real-world nextjs application (with the API route).
It feels like a naive implementation to just deploy this as an EC2.
Omg, react server components can quickly become a footgun. People say that this is a step towards PHP and i don't know how to feel about that, but I feel like we'll have to create a new category of FE dev due to the tight couppling with backend with react server components. Maybe, It's a good thing from full stack dev's perspective, but for me, as a front end dev, just seems wrong hahahaha.
there is no cache function on react .. How did you import that ? import {cahce} from 'react'
I really liked your zsh theme/configurations, could you please share them?
look at desc
Great
how would you store a session token in next 13? there is no more localStorage!
I may not understand but in the part "Creating the backend for frontend" what is the point of defining a API route (route handler in app directory) instead of "just" and just calling an async function ?
You definitely can, but the only difference is the cache strategy.
Yeah, that's true, you could get away without the BFF and just have an async function that is cached that does all the work.
I might be blind, but I don't see the link for the survey. Could you please share?
Fixed.
The example of Client Iphone Bad component doesn't make sense to me. What use-case would there ever be to pass the entire list? You have access to the current iphone object within the map, which should be the only required json payload to client, for each render of that component. I cant imagine a scenario where anyone would pass the total list and an ID - seems like an unrealistic example. Just passing the current iphone should reduce that payload and performance issue, i would expect similar to the Phones client component implementation.
There is no practical use, it's just really important to know that everything you send across to the first level of a client component is going to be serialized.
@@jherr Worth highlighting this in the video or in the title, otherwise most beginners who developers who didn't read the official docs would try that which in my opinion is a bad approach
What for theme you use in your youtube videos?
Night Wolf [black]
I swear this is the same problem i had yesterday,
okay awesome demo but with next 13 and server components things look more confusing and wrong paths opening up -_-
How to customize zsh terminal
How am I only coming across this now??? #HiddenGems
what's that terminal theme?
Do we run into the same problem in next 12? If no, why?
Yes, but it was more obvious because you'd see the N+1 pattern right in getServerSideProps. Where with NextJS 13, where you can have any RSC make a request, you could end up running into this in a complex application and not know it.
is it not obvious that we're going to go around in circles and just end up back at graphql + relay ?
What theme do you use?
Night Wolf [black] (theme and font are always in the description)
After thinking about it, I see that with iPhones I can understand better than with Pokemons :D
Hey, at least it's looking different.
@@jherr I mean, seriously, it seems for me that things are easier to understand with everyday things like iPhones compared to using Pokémons
I responded to survey
Thank you!
Couldn’t you just move the code that you put in your endpoint into the very first server component?
This is what I have been doing with a lot of my data fetching in next 13. I typically have a server component for the page, do my data fetching there, and then pass that data into other server components, or client components as needed.
But using Pokémon is your thing 😮 it brings some consistency to the ramdomnes.
With your solution arent you just moving the problem from front end to backend? its still a problem, its just a little better because it would be the same cache shared for all users, but its still an issue when the cache invalidates.
sir plz start fully functional series on advance React or Next js from scratch plz sir
They both have good docs
If you were working on NextJS 12 and not thinking about these sorts of problems you were doing it wrong then too. This is not a Next problem.
You are still making a lot of requests to the original api
Not after the first request, no.
So basically, you are suggesting to change all backend infrastructure to get a bit more speed and create another level of complexity which you or someone else should maintain and support, while the business people changing things constantly like they always do. so, you need to maintain one more endpoint :) i dunno, this seems like another n + 1 in the context of endpoints you need to support. If you are an architect on your project, this can be feasible, but what if you are just another mere developer who stuck between sprints :) dunno man
Well, as a few folks have pointed out I could have just avoided the BFF altogether and done a cached version of the N+1 query up at the front and you'd pay the price once on page load or whenever the route revalidates.
@@jherr That is just another way to achieve that ofc, with it's pros and cons. What i was trying to say is, this kind of approaches generally getting a lot of pushback from other devs and business people, and sometimes you just need more than an amount of speed increase to persuade them. i struggle with these kind of people all day everyday, it is exhausting for real.
on the technical aspect, this is a good content imo, really educative. kudos from me
Just being honest here... it is a little sad that you have to even teach this considering the fact that if you did this same process in vanilla javascript you wouldn't even run into this problem... things like React and Next were supposed to make front developers into actual programmers! But all they actually did was make EVERYTHING more complicated.
I get that.
Jesus, this looks like such an unnecessary way to go. Absolutely awful. Then again i haven't touched the server components concept yet so I guess I'm in the complete dark here
Man, I'm so bored of iPhone examples...
🤣 Lists of Doctors in the Dr. Who franchise?
@@jherr 🤣
I go files left and diffs/git right column...heathens the lot of us..