Improving Page Architecture: Strategies for Optimizing Performance and Interactivity

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the Next.js Complex State Management Patterns with RSC course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Next.js Complex State Management Patterns with RSC, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Next.js Complex State Management Patterns with RSC
  • [00:00 - 01:35] Alright welcome to another video about page architecture. I know we already covered this topic on the first module but I want to do another pass by the concept and show you a couple of examples from the sample app so that you can use everything that we've seen so far up until this point about state about server and client components and to put that into perspective when we look at some components and think about ways to refactor them taking into account page architecture essentially. So a quick refresh before we continue remember that we introduced the concept of server components as a way to optimize our applications. Server components run exclusively on the server and we ship only the resulting HTML from that execution to the client as opposed to client components where there is a whole process of static rendering on the server side first and then hydration with a lot of shell script being shipped to the client for that to work. There's also the reasons why you will pick one or the other with server components anything that doesn't need interaction with the user or is heavy in processing or data fetching is a good idea to put on a server components and then anything that is reactive or has interaction components with the user then should go into a client component. So with that in mind let's now look at two examples from our sample application.

    [01:36 - 03:57] The first one is about listing data and how we can improve what we're doing on a client component. All right so the first component that I want to show you is the list of tasks essentially what get what renders these three items here that we see on screen. I will show you the code in a second but I really wanted to show you what that looks like before I go into the components but essentially remember that within the page you have your navigation bar and then there is a new task component that will contain this form here then there is a filter component that takes care of filtering the data and finally the list of tasks that we're going to be refactoring today. Now let's go to the code. All right now from the code perspective the page that we were looking at looks like this essentially you have again the this is a server component that has the navigation bar on top then wraps every other component within a task provider to make sure that everything within that provider has access to a centralized list of tasks which is how I managed to share the list of tasks which is a global state for this application. Now here is the new task component as I mentioned the form on the top then the filters again and finally the list of tasks which is the component that we want to focus on right now. So the first version of the list of tasks that I want to show you is this one. This list of tasks is a client component as you can see for the first by looking at the first line of the file. This component does not receive any props as you saw before we're not passing anything from the actual page and it's using the task context as we've seen to call the filter function which this filter function we we don't need to get into details right now but what it will do is it will grab the list of tasks from the global state and it will apply a set of filters if they are selected and then it will return that list.

    [03:58 - 05:01] So from that list of tasks that get filtered and returned we do a map and for every task we return a big HTML. This HTML is one of the rows on that list and it contains the title essentially the title of the task. Then this little bit here shows the batch next to the name remember depending on or done batch that we saw in a previous video we have it here as not a separate component but rather sent a part of this code. Then we have the description of the task the formatted date due date essentially of the task and then we add the two action buttons the mark has done or the remove button for each task. Now this is a very valid way of doing things.

    [05:02 - 06:33] This code will work and it will get the job done. However if we go back to module one and we think about page architecture and we consider everything that we've seen so far we have several questions to ask here. One of them being why don't we receive an initial list of tasks from our page. Our page if we go back to it. Remember this is a server component. Why are we not getting the list of tasks initially from here and then passing it to the list of tasks as a prop? That's one question. So that will give us a head start because right now if we actually want to answer the question of how are we getting the data we would have to go to the filter function to essentially we will have to go to the task context which we have here. This provider essentially what it's doing is if you see here this very tiny use effect that gets executed whenever we define the token. The token comes from the out context essentially and you can look at the code in more detail if you want but what is happening here is the out context it context is reading the authentication cookie. If it finds the cookie it will save on its internal state it will save the the value of the cookie there.

    [06:34 - 07:58] When that happens this component side effect there is effect here gets triggered because we have a dependency on the value of this stay variable. So whenever that changes this side effect gets triggered and when it asks we call the load tasks function. The load task function is actually performing local fetch to our own API which we don't have to look into it but essentially it's pulling data from the database and based on the user ID it gets the list of tasks and then sends it back to the client which is this code essentially. This code here will grab the data it won't perform any validations here we should be you know if this was a production application but here for sake of simplicity I'm just grabbing the data turning into a shayton and then setting the my local stay variable tasks with the serve function with the content directly received from the API. So this is also say that I'm waiting for the out provider to do its thing and trigger a change on the authentication token. Once that happens I'm then doing a round trip to the server to grab my list of tasks and then I am saving that list of tasks in my own stay variable.

    [07:59 - 10:13] Then if we go back to the filtered function that we saw before as you can see it's nothing complicated it is just filtering the list on the initial array and we don't really care about this at this time at this stage. If we go back to our component we see that there would be definitely an option for us to get the initial set of tasks as a prop and then set that use that value to set the context that would actually remove the round trip that we do to the server when this context takes place we don't really need it we would use the props sent by the server component which is our page into this client component and we would use that data to load immediately so we would not need an extra round trip which could potentially take a certain amount of time increasing the time for our page to actually fully load. So that is one question answered. Now the other question is how much interact ivity is in this piece of code. We have the title which is static the description which is static and the due date which is another static piece of information. Then we have a batch here that changes back and forth between done and pending and then we have these two buttons that are also interactive. So we have three static items and essentially three dynamic items. However most of the surrounding HTML is not really doing anything other than providing a nice visualization and a nice markup. So if we take that into account and we again think about page architecture and so on should we also remove all of this content into a server second component that is referencing some other client components within it. I think the answer is yes so we could perform these in two stages.

    [10:14 - 27:50] We could say that first we rewrite this into a version of the component that gets the initial load of data from its parent component so that would look something like this. Now we have a prop the initial value being received and we have a new function you obtain from the context called set initial list. So what we do here is when the component gets mounted on the page and that is just the first time because this is the empty array for the use effect hook we set the initial list we call the set initial list function from our task context. Now if we were to look at this code here the function is trivial essentially we're calling this function and it's just calling the set task set function it's not doing anything else. Now the interesting bit and I have it here commented to make it really obvious is that we're no longer calling the load task which was the function doing the round trip remember that we mentioned before so this is a measure improvement we just removed all the that delay on the on the round trip to get the data from our internal API and we also removed the internal API we don't need that internal API anymore which is got rid of a bunch of code and we improved the speed at which the page loads why because if we go back now to the new version of our page now this page which is the one again remember that sets everything up it's doing something it's getting the user ID from the cookie it is calling the get user tasks function which is pulling data from the database and then it's simply doing a little bit of processing of that data to turn it into an actual JSON so that we can later pass it as a prop to a to a client component this is this is something specific that I will cover later on but consider that this code is running on the server and we have our list of tasks on the server and we're passing it to a client component so you know there's no magic here so some data transfer has to happen and we've seen already that that that happens on the react server component payload the payload is going to contain these props the list of tasks is going to be part of the payload and for that list of tasks to be inside the payload it has to be serializable that's all we'll go into more details in a in a future video so the change here is that we've added server side code to this component which didn't have it before and now we are passing the processed list of tasks into our client component now our client component looks like this the client component is now receiving that data when and when it gets mounted into the page by react essentially when it gets initially added to the DOM tree it will set the internal state of the task provider so there is no round sheet anymore we got the data at the same time that we got the component great improvement so second question was should we have all of this code here or should it be somewhere else the question the answer is it can't completely be somewhere else it should be in fact a server component because there is no reason to have all that markup being returned as part of a client component that doesn't really need it so instead we'll simplify the list of tasks yet again into this version and this is what I mean by proper page architecture the component the client component actually got way slimmer way simpler because we got rid of an internal function that was related to something else that we didn't really care about at this stage and we're just iterating over the list and creating new server components that's it now if we go to the task item the new component that we just created the task item definitely has all that markup that we needed but this is a server component so we don't really care about that because this is pre-rendered and then streamed to the client now we also in this step we also took that batch that we were using that had certain logic we took it out of here because clearly this was this is no a server component so it doesn't really allow for that internal state to have to have some interactivity and we have here the buttons that we saw before so what happened here we simplified the whole data loading process by prefetching all the initial state on the server and then transfer in into the client we're getting ahead of ourselves by looking at a potential way of answering the question of how to share stay between these type components i'll review that answer in more detail in a future video so don't worry about that the point here is the architecture of the page by asking ourselves is this really should should this markup really be inside the client component is this really code that should be on the server side or or the client side by answering those questions we simplified greatly the code of our main client component we went and let me just take you back we went from this version which had internal function and a bunch of markup that we didn't need to this a very slimmed down version of the original we extracted code that didn't really belong on a client component into a server component in the form of the task item component and on top of that we also extracted another component that was meant to be dynamic into another client component small one but still we simplified this component even further and finally obviously we got rid of an entire API endpoint which we didn't even look at but it's just no longer there it's irrelevant for us so that's what i call proper page architecture and it's definitely a huge improvement over the performance of the application and the maintainability of the code so definitely a great win here all right now i want to show you another use case i want to show you a case of fetching data essentially having a client component fetching data and then using it on screen i know we 've seen already the nav bar component multiple times but i'm going to use it again as an example in this case i'm first going to show you the initial version of this component which was a client component and then i'm going to show you the reason that i went through to turn it into an actual server component and improve the performance of it here i'm showing you the app again and i want you to focus on the upper right corner of the screen here is my name and again if you remember when we talked about it in a previous video this is the only piece of interact ivity for the entire navigation bar essentially for the entire component so the whole point of this is i wanted to have my username the username of the user being logged in i wanted to show it on screen as a clickable button here so that then i can show the log out option which is its only form of interactivity now the thing is if you remember whenever a user is logged in into a system we create a cookie the cookie will hold the idea of the user inside the database that is all the information that we keep and that we send back and forth as a cookie on all requests and responses so for me to actually show the username here at tiny piece of information i have to actually hit the database and to hit the database from a client component i obviously have to also hit a local API input let me show you the code so that you can see all the work all the trouble that we go through just to show you the name there all right so here is the actual component one where we have the we have a state variable for the component now when the component gets mounted here we use the use effect hook for that we we call the load function essentially and what the load function is going to do is it's going to get my user ID from the cookie as we mentioned before and we can do that on the back end and front end as well and then it's going to call it's going to essentially set the user and so in other words set the local state variable with the response from the get user by ID function the get user by ID function though it's going to perform a fetch essentially on a local API endpoint the local API endpoint is the one that is going to pull the data from the database so again similar to to the case before we have a round trip to the server onto an unnecessary local API endpoint to pull the user information now look at the size of this client component we have this function here we have this other function here and then all of this it's really static I mean we have the user menu which is the dynamic part but the rest is static I actually had to add this loading state which let me go back and show it to you real quick if I refresh the page just look at the upper right corner of the screen you'll see the loading state and then my name showing if I didn't add that loading state then what you will see is nothing there for probably a second fraction of a second at least and then my name would show up that is definitely not the idea user experience and even if you add the the loading state which is like a fix for for the problem that we cost there is a better way to do this so let's go back to the code and let me show it to you so again this is all about thinking page architecture right so we have to ask ourselves are out of all of this markup how much of it is dynamic how much of it requires interactivity and the answer is zero the only markup that requires interactivity is inside the user menu component so honestly this is screaming to be turned into a server components and let me show you how that looks in the end so here is the new server component but that's it I don't have to scroll to show you the rest of the code this is it we've seen the code already but just to refresh you this is actually reading the cookie and then with the user ID from the cookie it's calling the get user by ID this function is not causing a round trip to the server because we're already on the server this function actually is calling the database I can show it to you this is the function in question which as you can see is actually calling at the mongo database because I'm using that as a backend so I'm selecting the collection and then finding a single record inside the collection with the ID there's nothing really to worry about this code in particular the whole point of this example is here the rest of the code is static I actually even removed the loading state because this is on the server so I don't need a loading state on the server when this code gets rendered the data for from the user is already there it's actually being shipped to the client with the server component payload option essentially this the bit that we stream to the client contains the prop remember that we pass to our client components so this object here the user it's already sent to the client so if I were to update my code and use this component instead let me show you how that looks now here I have again the same page just pay attention to the upper right corner where my name is and I'm going to refresh now do you see the loading state no right there's no loading state it just blinked because that's how much time it took for the page to reload but the name of the user was already there from the beginning that's because we also improved the loading time for this page for this piece of information actually the whole point of this component being server side is because it was a static component it was screaming to be a static component the whole markup was static the only dynamic bit of it was client component that was already extracted we didn't need it in anywhere so when I turned this component into a server component the whole the whole process shifted into pulling the data from the database directly on the server rendering everything but a small placeholder for the user menu component and then also shipping the user data with the whole payload so when it reached the browser react got that html grab the actual client component which is the user menu put gave the past in the data that it already had and it was able to instantiate everything even before we realized and so that's a huge improvement again in performance and most importantly we removed a completely useless API endpoint again we just got rid of it because we didn't really need it we also managed to simplify the code of the navbar again let's go back to the code this is a much simpler version of the previous one there are no functions there are no moving parts here I mean clearly there is a function call to the get user by id but there is no fetch request there is no round trip there is no any internal state to worry about this is a much simpler version of that code much easier to maintain so I hope that these two examples gave you a very idea of what I meant by page architecture when I started this course on the first module lesson three if you want to go there again let go watch it I hope that now by this by this time after everything that we 've covered the concepts that we talked about in that video will make a lot more sense so as a wrap up keep in mind that the separation of concerns between server and client components will provide a powerful combination of performance and interactivity we saw that already keeping and isolating the into activity inside slimmed down versions of those components and by doing so improving maintainability as well as performance for the entire application you'll require thinking about which part of your application benefit from the server render once pattern essentially the pattern around how server components are built and are rendered and cached versus those were dynamic clients interactions are needed so experiment with this pattern there is no easy there's no single way to solve these problems there's no easy way to answer the questions that you have to ask when your UI is complex enough it has way too many components then you'll have to start asking the question over and over again and maybe experimenting with different patterns different architectures until you find the right one the right combination and balance between server side rendering for your components and at the same time dynamic and interactive code in slimmed versions of the client components in our next module we'll finally hit the actual question that triggered the creation of this entire course how do we share state between client components and server components and how do we solve one of the biggest problems that has cost many developers to just ignore server components up until now so in the meantime keep caught in and i'll see you then