Server Components are the hot new thing, but so far much of the discourse around them has been abstract. Let's change that. This talk will focus on the practical side of things, providing a roadmap to navigate the migration journey. Starting from an app using the older Next.js pages router and React Query, we’ll break this journey down into a set of actionable, incremental steps, stopping only when we have something shippable that’s clearly superior to what we began with. We’ll also discuss next steps and strategies for gradually embracing more aspects of this transformative paradigm.
A Practical Guide for Migrating to Server Components
AI Generated Video Summary
1. Introduction to Server Components
I'm a freelance consultant and a React query contributor. React query or TAN stack query version five is live since Tuesday. We'll be talking about planning and preparing, setting up for server components, migrating a single page, adding layouts, and moving stuff to the server. Our goal is to approach the use of server components as an incremental migration and bring the whole team along. We'll use Next and React Query to demonstrate the process.
This is what I'm talking about. I'm a freelance consultant and a React query contributor, as he mentioned, and you can find me on Twitter. We actually changed that from X after I saw the polls, but I do say Twitter too. I'm FMJ.
So usually announcement comes at the end of a talk, but since TK Dodo announced this earlier this week, React query or TAN stack query version five is live since Tuesday. So we'll try that out. So we won't be using any of these new APIs in this talk, but we will be using some of the new syntax.
Okay. This is what we'll be talking about today. Planning and preparing, setting up for server components, migrating a single page, adding layouts, and also we'll talk a little bit about moving stuff to the server, but not that much. We only have 20 minutes. This is going to be a whirlwind talk. I won't be able to cover everything, but I hope to give you a roadmap to get you started and some helpful tips along the way.
First, let's talk about some goals. If you have an existing app today and you want to use server components, you are going to want to approach this a bit differently than if you have a Greenfield app. We want this first and foremost to be an incremental migration, because big bang migrations never ever work. This is also a huge new paradigm to learn, so we want to learn this step by step, and we also want to bring the whole team along as we do so. Maybe people here at React Advanced have already read all the docs, seen all the talks, but your team might not have. So you have to gently lead them up these stairs, not just run up there. And to do this, we are going to keep as much as possible of the existing mental model of your application and your existing code. Or put a bit differently, don't try to do everything at once. And this kind of summarizes my talk, I guess. First we want to get this to work, then we can adopt all these new features that we're itching to adopt, right? And, again, if you're starting at Greenfield Lab, you might want to embrace the future right away.
In this talk, we are going to use Next and React Query. It is kind of a React Query talk in disguise, because I'll show you how to use server components with React Query, but that's not the main point of it. It will focus a bit on data fetching, though, and the reason for that is this. A lot of examples and migration guides that exist today have something like this. You're doing a fetch in a server component. You're adding some configuration. And that's it.
2. Planning and Preparing for Migration
If you're building a complex web app today, you also have to deal with mutations and invalidations of data. The approach we're going to take is to use server components mainly as data loaders and not render that much in them. We're also going to keep using React Query and your existing caching logic. To prepare for this migration, read all the documentation, inventory your utils and third-party libraries, and think about deployment, DevOps, tests, and authentication. Plot out which pages you think you're going to need to migrate together and pick a basic first route to migrate. Then wrap those in strict mode.
If you're building a complex web app today, you also have to deal with mutations and invalidations of data, and right now, before server components, there's only one way to do that, right, and that's on the client. So I want to talk a bit about how you do this if you're using a third-party library to manage your data fetching today. That doesn't have to be React Query, of course.
Another thing is that the Next docs currently list four ways to fetch data in server components or when using server components. And I want to claim there's a fifth way, which is the way a lot of us might be doing it today, which is prefetching data on the server, but doing all the invalidations and refetching and things on the client. Possibly with third-party libraries.
The approach we're going to take is to use server components mainly as data loaders and not render that much in them. Keep the rest of the application as client components, and we're gonna keep using React Query. We're also going to keep using your existing caching logic in your application. Because you already have working caching, and caching is hard to get right, right? And the app router has four. Four caches that interact with each other. And these are super powerful. They are great, and you want to opt in to them later after your migration is done.
So, to do that, we'll try to mimic the existing behavior that you probably have with get server side props or get static props, and the way we'll do that is by exporting this configuration, force dynamic or force error, and we'll get into that. But first we need to plan and prepare for this migration. And the very first thing you want to do is read all the documentation. Grab a coffee or a tea or something, spend an afternoon with these documents, the React documents, especially the next migration guide and the rest of the next documents, they are really, really good, very comprehensive, and any third party library documentation too.
Then you want to inventory your utils and your shared code, think about if there's anything you need to do there to support the app router. You want to inventory your third party libraries. Do they actually support the app router? What are you going to do if they don't? You want to think about deployment, DevOps, tests, authentication, but you don't have to solve all of these things now, but it's very good to get a feel for the scope and anticipate any hurdles you might have for your application. Where is this going to get tricky for you? So the pages and the app router are in a sense two separate frameworks. They have their own bundles and navigating between them is a full page reload. So that is probably fine for a lot of your pages, but for some pages it might not be. If you have a category page and a product details page that you navigate between very often or something like that. So plot out which pages you think you're going to need to migrate together. Then you want to pick a basic first route to migrate. This is probably your company about page, right? If I care to guess. And then you pick a more complex second route to migrate. And the first thing you want to do is to wrap those in strict mode.
3. Setting Up Server Components
The first step is to wrap your components in strict mode to identify and fix any errors or warnings. This is crucial because the app router uses concurrent features by default, revealing bugs in use effects and other areas. Once you've resolved these issues, deploy to your existing pages router and update your versions. Next-specific considerations can be found in the documentation. Remember, you can utilize these features in the pages router as well.
And the first thing you want to do is to wrap those in strict mode. This component is going to give you a bunch of errors to fix. Or warnings, at least. And the reason you want to do this first is that the app router actually uses concurrent features by default. So when you do page transitions, page navigations, that's a concurrent transition in the app router. So, this is when all those bugs you have in your use effects and all of that is going to show themselves.
In a sense, you could say that this is the real React 18 upgrade. When you're done with that, you can deploy it to your existing pages router. You will probably have fixed a few bugs along the way. And then you can bump your versions. You can do that before, actually, next and React and the third party libraries you have. You want to take care of a few next specific things. You can read about these in the docs. They're really good. But the only point I want to make is that you can use all of these in the pages router as well. So you can take care of all this and deploy it in your existing app.
4. Setting up App Router and Migrating First Page
And then it's time for setting up the app router, migrating your first page, adding a demo, moving to a homebrewing page, and migrating the custom 404 page. To start, add an app folder and restart the dev server. Drag the app folder to the desired location and make it a client component. Rename the error component to 'not found'. Restart the server to fix a bug in the React Server Components bundler. Now, the custom 404 page is working with the basic root layout.
And then it's time for setting up the app router, migrating your first page, adding We're all tired of this presentation now. So let's do a demo, instead. Let's move over here. See if that works. Oh, that's the schedule. Nice. Okay.
So this is a homebrewing page. As you heard, I'm a homebrewer myself. So I picked out some classic British ingredients to brew British beer. We're also selling some casks here in the Firkin and Hogshead sizes. What's wrong with you? We also have our standard company about page, and this is the page we'll be migrating. It does have some data fetching for this timeline and these job openings. I want to be the head of Hops. That's my goal. This page also has, like, your custom 404 page. And we'll start by migrating that.
So the first thing we'll do is actually add a app folder here. And we also want to restart the dev server. But as soon as we do and we reload this page, you'll notice that we no longer have our 404 custom 404 page. As soon as this app folder even exists, this stops working. So I'll just drag it over here. We want to make this in this case a client component. Because this error component requires that. And we also want to rename this to not found. Now, there is a bug in the React Server Components bundler. But if we restart, it's gonna work fine. So we'll have our custom 404 page back. And this is currently working because I've already added a root layout here. It's a very basic one with just HTML and body tags.
5. Migrating Components from Pages Router
We'll migrate the necessary components from the pages router. The app component has providers for React Query and other complex providers. We'll add 'use client' to the providers component and wrap it in the layout. This keeps the providers in sync for both the pages and app router. We'll also include metadata and global CSS. Additionally, we'll add the error components, each with its own route layout. Refer to the Next.js documentation for more details.
So we'll migrate anything we need from the pages router next. The root layout is a combination of the document and the app components in the pages router. We don't have anything to migrate from the document. You might though. But this app component does have a few things we want to migrate. We have a few providers here that we'll look at next. We have some metadata, we have some global CSS. And we want to move all of that over. But let's start with the providers. This providers component is setting up React query with a query client and a query client provider. It has some very complex other provider. I don't know what that does. And the first thing we want to do is just add use client at the top of this. This is using a bunch of features that you can only have in client components. So it makes sense to do that. Next we'll go over to the layout and we will wrap this in the providers component. And the nice thing about this now is that if you change these providers, they're gonna apply to both the pages and the app router. So it's kept in sync. We also have some metadata that we want to include. We can now export that as a metadata field. This is typed, which is nice. So you catch any typos. And we want to add this global CSS as well. The final thing that I've already done is you want to add these error components to. The global error one needs to include its own route layout because that catches errors in the route layout, and this other error component you want to include as well. These are detailed very well in the next docs, so you can go read those.
6. Moving Company Page to App Router
Let's move the company page to the app router. We'll start with GetServerSideProps, where we prefetch data and dehydrate it. The company section and navigation footer components use useQuery to read job openings and timeline data. We'll move the data fetching to the component and make it asynchronous. Exporting the dynamic force dynamic configuration option ensures revalidation on each page request.
Oh, sorry for letting you stare at that 404 page for a few minutes. Let's next move this company page over. So what we want to start off by doing is looking at what this looks like today. And let's start with the GetServerSideProps that we have here. In this GetServerSideProps, we're currently creating a query client. We are prefetching a bunch of data that we need, navigation footer, timeline and job openings. We are dehydrating that data to the client and putting it on a hydration boundary. This was called hydrate in V4 but it's now hydration boundary. And this is what makes that data available on the client or during the SSR pass so to speak. So, GetServerSideProps is prefetching and this is making it available. And then we have this component with navigation company section and footer. This company section, that's the component that actually uses useQuery to read the job openings and timeline data. And the navigation footer is doing the same. So, let's start by moving this over to the app router and we'll rename that company. I'm gonna keep this as a client component. So, we want to go ahead and add useClient at the top here. But I'm gonna move the rest of this into a server component. This is the prefetching part. So, I've just set up some imports here to save us some time. We'll paste this in. We will move all of this data fetching over to the component instead, which we can now make asynchronous because this is a server component. We'll remove this. We actually need to remove that. It's no longer coming from props. We don't really need the props here. Let's go ahead and remove that. And the final thing we want to do is to export this configuration option, dynamic force dynamic, to mimic the caching behavior of getServerSideProps. So, it always revalidates as soon as this page is requested. And in my case, I also want to remember to rename this to page. So, when we reload this page, everything works.
7. Migration Demos and Layout Changes
Migration demos always look the same afterwards. We need to check the network and reload to see the app router features. Clicking back to the main page triggers a quick full page reload. Moving back to the company page also triggers a full page reload. We want to move the navigation and footer to a shared layout. Prefetching navigation and footer data in the company page doesn't make sense. We can use multiple hydration boundaries to prefetch data. Adding a suspense boundary enables streaming with the app router.
Migration demos are probably the most boring ones ever because it always just looks the same afterwards. How do we even know this is using the app router, right? We actually have to go into the console here and maybe check out network and reload to see that this is using the app router features.
And if we click back to the main page, we'll see that this is a full page reload, but it's very quick, so you won't notice unless you actually stare at the DevTools. And if we move back to the company page, it's a full page reload again. Okay. Cool.
But this looks a bit weird. Why do we have to have the navigation and footer inside of the company component here? We want to move that to a shared layout, the step four of this presentation. So I'll just go ahead and remove this. We could remove this entire component now, but I'll keep it in the interest of time. It also doesn't seem to make sense that we're prefetching footer and whoops, footer and navigation data in the company page. So I'll go ahead and remove that as well. If this plays nicely.
And the place that seems to make sense to have this navigation layout is in the root layout for this page. So what we can start off by doing is grabbing this. Which is exactly what we just deleted. Prefetching the navigation and footer data and these queries. And we can grab this. And the secret here is that you can use multiple hydration boundaries. So this root layout is using one hydration boundary to hydrate all the queries needed for the root layout. And the page is using another hydration boundary to hydrate all the company page data. You can prefetch data wherever you have a server component. Okay. And now the navigation and the footer is back, as you could see. Last point in this demo is that we are not using suspense here. This is just the regular use query API. Which means that this page is not streaming SSR. It's just the regular, like, here's your HTML, right? As soon as you add a suspense boundary, if you have an application that uses suspense now that you're migrating, as soon as you add that, this opts into streaming when you're using the app router.
8. Streaming SSR with Suspense Boundary
Adding a suspense boundary to the app router enables streaming SSR. It works out of the box on Vercel, but custom deployments need to be checked for streaming support. If not using suspense, it's not necessary for migration. However, adding a suspense boundary can still improve performance by allowing quick navigation data retrieval.
Which means that this page is not streaming SSR. It's just the regular, like, here's your HTML, right? As soon as you add a suspense boundary, if you have an application that uses suspense now that you're migrating, as soon as you add that, this opts into streaming when you're using the app router. So if you're deploying to Vercel, that's going to work out of the box, but if you're doing a custom deployment, you are going to want to check that it supports streaming, check your CDM, that it supports streaming, that it's configured for it, et cetera. If you're not using suspense today, there's no need to add suspense as part of the migration journey. This works fine without it. But a cool thing is even if we're not using the react query suspense APIs here, we can actually add a suspense boundary anyway. Because the server component is suspenseful. So now the navigation data is really quick, but the company data is slow. So when we reload the page, we can get the navigation immediately and have a spinner for the rest. Even if the third party library in this case is not doing the suspense heavy lifting so to speak.
9. Migrating to Server Components
Okay. Let's move back to the slides. When you've migrated your first page and want to deploy it to production, find any bugs, Then you can keep adding more pages, adding more layouts, structuring your application. And at some point you're gonna feel the itch to not do migration work anymore and start moving stuff to the server.
So when you do start migrating stuff to server components, I do have one piece of very important advice. Let's pretend we're migrating this navigation component and especially this shopping cart. And then it might be tempting to do something like this if you're using React query. You're fetching a query. And you are dehydrating the data to the client as you were before. But you're also passing cart items here to the cart item. And why is this bad? Well if you're using React query here, you're probably also using it to mutate that cart. You're adding cart items. And when you're doing that, you're probably invalidating this query in the client components. But that doesn't make the server component re-render. So the cart items.length is going to stay 0 in the server component while items are being added to the cart.
So here's my advice. Don't render dynamic data in server components if you're using third-party libraries at least. If you're opting in to the whole new next paradigm, this is going to work. It will revalidate the server component. But that's only a later step if we're talking about the migration journey. So while you're migrating, use as many client components as you want or need, because there's nothing wrong with them, especially when you're migrating. Another way to put this is don't mix data ownership between server components and client components. So a few takeaways. Prepare step by step. Try to keep as much of your existing mental model as possible. Opt out of any complexity that you can at the start, like caches. And to mimic what you're probably doing today if you're building a complex web app, preload data in server components, but let client components handle the rest of the data lifecycle.
Reasons for Migrating Server-Side Components
And this should hopefully be applicable whether you're using React query or some other library. But read their docs, of course. If you want to learn more, Lance has a talk where he talks more about suspense and streaming. And Jack has a great talk where he goes more into state management and server components in general. And that Jack's talk will be at the start of the stream, if you're joining us remotely. And Lance's talk will be a little bit later in the day for those joining us via stream.
Thank you. I really enjoyed this talk. Actually, as you were talking, I've already decided on like some things I want to change and try out on the next JS app that I've been building. So, I'm gonna probably go get my laptop in the break and see if I can clear around with that. It should be OK. It should be OK.
Rendering in Server Components and Handling Auth
If you're rendering basically nothing in the server component, it ultimately depends on your application and if you see any benefits from this new paradigm. Let's jump onto the next question about auth and handling third-party APIs for both the client and server. It's a complex topic, and I don't have a definitive answer. RSEs are still experimental, but they can be used in production apps. There are still some bugs, but they have been fixed for the most part. It's important to thoroughly test RSEs because they represent a new paradigm.
If you're rendering basically nothing in the server component. So there's also that. But it ultimately depends on your application and if you see any benefits from this new paradigm. The first of many, it depends, answers for today.
Let's jump onto the next question, which is about auth. Especially when you talked about having third-party APIs as well. If you have an API behind your authentication, how do you handle it for both the client and the server? That's a great question. And it's not one I'm super familiar with. I have built some off with a third-party library for server components. And it's still a little bit rocky, I'd say, those APIs. Like, you have to consider these server components themselves. Those are like an API. You can access data in them. You have to consider your actual APIs, which I didn't talk about in the talk. We had a pages slash API folder, and that you can just keep in pages. That'll keep working. So you'll have to keep your auth there. And keep the auth in the client as well. So I don't have a great answer for that, really. Well, that's an answer that can be another talk next time. So maybe someone should do some research and give a talk at React Advance for next year.
Speaking of RSEs, they're still an experimental feature, but in your mind, are they safe to use in production apps? That's a tricky one. I do think they are. Like if you would have asked me a couple of months ago, I think this story was quite a bit rocky. As you saw, there was still a Server Components Bundler that popped up, but that, for, this just pops up for that 404 specifically. Like there are bugs still. They've fixed most of the essential ones, like Alvar is sitting over there. Thanks for the help with the talk, by the way. It's reported, I don't know how many, and most of them are fixed, I think. So yes, but the key thing is to really test it thoroughly, not just because there might be bugs in like the Server Components part, but because this is a new paradigm.
Migrating Code and Server Load
When migrating code, expect bugs, but it's stable. Experimental features become mainstream. Moving to Server Components increases server load and costs, but improves user experience. Caching and efficient rendering can reduce load. Server rendering is beneficial for the ecosystem. Decision: server or client rendering depends on user experience. Thank you, Frederic!
And when you're migrating your code, there might be bugs as well. So I'd say it's stable, but do test it thoroughly. Now for sure. I don't think it's classic, right? With experimental features, they become mainstream before they actually come out of the experimental phase more and more so. So definitely another it-depends kind of question. And let's just do one more as well, just for time, but you can always come and have more answers with him over in the speaker Q&A session.
If we are moving to Server Components, we're putting more load on the server and increasing the cost of running the server. And this again came with Mark. Is that something we really want to do? Is that something that's worth doing? And maybe what are some factors that you would consider when making that decision? That's good. It depends, right? Number two. But it does. Of course, it's going to cost more if we do more work on the servers, but that's also going to bring a better user experience for the users, because a server is quite a bit more powerful than this, right? So it does depend on your application. Though I'd say that it also has the opportunity to reduce load on the servers, because you're being more efficient, especially if you opt into these caches, which are granular, you can cache different parts of the page. If you have one part of the page that's static, you can cache that forever, while the rest is dynamic, which could actually reduce load. And all of these API calls that we're making, which is probably one of the more expensive things, we have those anyway. So just doing the server rendering, server components part, sure, that's gonna add load. And especially if you do server components for your entire application. But I think it definitely makes sense. I think in the ecosystem, we need more server rendering and not less. Yeah. I mean, it's got to go somewhere. And I guess your decision to make is do you want it on the server where it doesn't necessarily affect the user experience or do you want to put that over to the client? But once again, Frederic, thank you so much. Let's give him one more round of applause. I do.