Routing in React 18 and Beyond

Bookmark

Concurrent React and Server Components are changing the way we think about routing, rendering, and fetching in web applications. Next.js recently shared part of its vision to help developers adopt these new React features and take advantage of the benefits they unlock.

In this talk, we’ll explore the past, present and future of routing in front-end applications and discuss how new features in React and Next.js can help us architect more performant and feature-rich applications.


Transcript


Intro


Hey everyone. I'm Delba, and I work for Vercel. As some of you may know, we're the creators of Next.js. And I'm curious to know how many of you here use or have used Next.js? Wow. So that's a lot of you. That's amazing. So although we create Next.js, we also have a platform, Vercel. And funnily enough, Vercel supports over, I think it's something like 35-plus front-end frameworks. So even if you don't use Next.js, you could still use Vercel. And today, I want to talk to you about some exciting stuff we have been working on, which is routing and routing in React 18.

[00:59] Now, as you may know, a few months ago, React 18 was released with new concurrent features. And on the React blog, the team mentions that they expect concurrent features to have a big impact on the way that developers build applications. So today, I want to discuss with you what this impact could mean in how we will change how developers build applications, especially with React server components as well. And this may not also change things for Next.js, but also for other frameworks and library maintainers. And I'm going to specifically look at routing, but I will also mention data fetching and rendering, or as I like to call them, the three pillars of the web, because those terms are very much interconnected.

Now, to give everyone here some context, and also the people who might be watching online, I think it's important to just take a step back and look how routing has evolved in front-end applications. Now, please note, I'm going to be condensing years of routing history in like five minutes, so there's a lot more nuance. And one way we can look at routing is through the type of applications we can build, multipage applications, single page applications, and more recently, hybrid apps.


History of Routing in Front-end Applications


MPA (Multi Page Apps)

[02:21] So, in the very early web, routing was very straightforward. Because if you think about it, each URL mapped to a specific file on a server. And then later on, using dynamic server side languages, we were able to generate a response from the server for a specific route. In a traditional mult ipage application, routing is done on the server, and navigating between pages causes the full page reload that we're all very used to.


SPA (Single Page Apps)

[02:54] Now, when native mobile apps came along, they brought smoother transitions and new UI patterns, and single page applications, you could say, were the web's response to native apps. We wanted our websites to feel like native apps. That is to say, we wanted them to have the same user experience as native apps. In a traditional single page application, routing is done on the client side. So on initial load, you may see a white, blank screen while the client side fetches and renders the content. Now, when you navigate on a single page application, the client will dynamically rewrite new content, and for given route, the client is deciding what content to fetch and render.


MPA vs SPA

[03:43] And last year, Rich Harris, the creator of Svelte, gave an amazing talk on the whole MPA versus SPA debate, where he discussed some of the pros and cons of each. It's a really great talk, and I recommend watching it if you haven't had a chance to do it already. But one takeaway from that talk that I want you to remember is that he recognized that there's an emergent pattern in our industry where applications are transitioning between different environments, client or server. And he called those type of applications, transitional apps. And transitional apps, they try to combine the benefits of both the client and the server, because if you think about it, why not both?


History of Routing in React


[04:31] So that's kind of a broad overview of routing on the web. Now, let's zoom into React. While React didn't invent single page applications, you could argue that it contributed to their popularity. The fact that React was and still is mainly concerned with UI and rendering means that the community has come up with a few different solutions for routing. And one client side solution that quickly rose to popularity was React Router. Now, how many of you have used React Router before? Yes.

So React Router's approach to routing is what I'd like to call component based routing, where you use code to map specific components in your application to your URL path. And if you combine it with a tool chain like Create React App, then you can easily create single page applications. And this led to, I think, not just the adoption of React itself, but also applications that were fully client side rendered.

[05:37] Now in 2016, a few years later, Vercel introduced Next.js, and Next.js was created as a framework to help developers build server side rendered applications. And Next.js took a different approach to routing. It used what I like to call file system based routing, where files in your application map to your URL. And although Next.js felt very similar to a multi page application, it actually used prefetching in client side navigation to give applications a SPA-like feel, if you could say. Now, another incremental step towards hybrid apps was the Next.js data fetching methods, like getInitialProps. And what these data fetching methods did was move the fetching outside of your rendering code, or outside of your component, so that you could fetch data both from the client and the server.

Now, I'm focusing on Next.js today, but it would be remiss of me not to acknowledge some projects that are also working on routing solutions for React, including Shopify's Hydrogen, Remix, and also Redwood.

[06:53] So fast forward a few years, in early 2020, and the members of the React team were publicly discussing moving more rendering work to the server. The idea was that if we are doing data fetching on the server anyway, could we move some rendering work to the server, and therefore reduce the amount of code that gets sent to the client?

Now, you could probably imagine the hot takes that followed that tweet. I think it can be best summarized as, this looks a lot like server side routing. Are we going back to MPAs? But to quote a not-so-serious meme from one of my favorite people on Twitter, this is less of a pendulum swing, and more a spiral of incremental improvement, so switchbacks. Not purely SPA, not purely MPA, but a convergence towards hybrid, that benefit from both the server and the client. And each time this conversation was brought up, the React team was careful to emphasize that they were looking for a hybrid solution. And one important thing to note here is that this hybrid solution wouldn't be creating additional requests to the server. It would take advantage of a request that has to exist anyways.


React Server Components

[08:13] So in December, 2020, the React team gave us an early Christmas present that will allow us to move towards more hybrid solutions. That was React Server Components. When you combine React Server Components with suspense and the streaming, we now have the primitives, or the building blocks, to address some disadvantages of multi page applications or server side rendered applications while maintaining the same user experience we love about single page applications or client side applications.

But there is one last piece to the puzzle. And in the last few months, the Next.js team has been considering this. If we need to go to the server to do a trip for data fetching, and now we're going to the server to do rendering with React Server Components, could we possibly also do routing on the server? Could we enable developers to build applications where routing, rendering, and data fetching happens where it makes more sense? And could we give them conventions that are easy to understand, but also allow them to move parts of their application either to the client or to the server?


Next.js Routing and Layouts


[09:29] So a few weeks ago, we shared an RFC. And in this RFC, we proposed a new router for Next.js. And this new router builds on top of React Server Components and React 18 features. There's a lot more detail in the RFC, and I won't go too much into the implementation details. If you're curious to know more about it, I do recommend reading it. But for now, let me just share with you some things that I'm most excited about and that we've been working on.

So if you think about it, with React Server Components, we can interweave client components and server components in a tree. This means that in a page or a route, you could potentially have both client and server components. So with this new model, it becomes increasingly important to break up our routes into independent fragments, which we call route segments. And these route segments, they map to an already existing term, URL segments. And although we are breaking up the routes, we want to maintain file system routing, because generally, it tends to be a more intuitive way for developers to define their routes.

[10:47] And breaking up the routes like this, or as we call it, doing nested routing, has three main benefits. The first one is that we are able to create layouts. And layouts have been a very long, going back, community ask. And the way that we want to define layouts is that a layout is UI that is shared across routes. In these layouts, they shouldn't re-render or lose state on navigation. It also means that, for components that don't change within the route, so a layout, we also want to make sure that they're still interactive as the user navigates between routes.


Partial Fetching and Rendering

[11:37] Secondly, if we combine it with server components, that means that on navigation, the server only has to fetch and render the segments that have changed, and we don't have to re-render the whole subtree for that route.


Eager and Parallel Data Fetching

[11:53] And thirdly, we can have more granular control over data fetching. So in Next.js, as you may know, currently we fetch data on the page level. And with this new model, we can fetch data in the segment level.

[12:08] And since we already moved data fetching outside of the rendering code or outside of the components, what we can do now is we can eagerly initiate those requests in parallel, and this reduces what some of you may be familiar with, which is waterfalls. And overall, the amount of time that it takes to load the content of a route is also reduced. So by building a new router with React Server Components, we're able to achieve three things, reduce the amount of code that we send to the client, reduce the amount of work the server has to do, and also reduce the amount of time that it takes to do that work.


Simplify loading states and improve navigation experience

Now, if we were to combine it with concurrent features, such as transition, suspense, and the future offscreen component, we can simplify the creation of loading states and improve the navigation experience. For example, if you want to use client side routing and you are fetching as you render, you may have too many staggered loading states or spinners. So really, what you want to do as a developer is consolidate them into fewer, more meaningful loading indicators.

[13:24] On the other hand, if you are using server side rendering, you have to fetch and render the content before navigation starts, so your application will appear unresponsive as the work is being done on the server. So in that case, you do want to include loading UI to indicate that work is being done in the background. In either case, we believe the framework should provide an easy convention that will allow developers to create loading states.

Now, we can also improve the navigation experience further by pre-rendering a very small but meaningful part of your swing. So this means that when you navigate between screens, the navigation will be immediate, and the user might see something like a cover photo or a title before the rest of the content loads.

[14:20] And in a similar fashion, and this one is one of my favorites, is that we will be able to stash routes. And what that means is that we can stop the previous routes, and then pre-render future routes, so that when the user navigates between the routes, we restore the state.

Now, I'm running a little bit out of time, so I'm just going to skip these two slides, but if you do want to find out more, we do have more information in the RFC.


[15:03] What I wanted to highlight here today is that there are many ways you can think about routing in React. And we have been thinking a lot about how we do routing in Next.js. It's not about routing itself, but it's what routing will enable us to do. So we'll be able to create more performance experiences with React Server Components, and also smoother experiences by improving navigation.

We also want to make sure that we create experiences that are shareable, that don't break the URL. And to do that, we need to create simple conventions that will allow developers to implement more complex routing patterns.

So last but not least, before my time is up, I want to take a moment to give a shout-out to the React team, because they're the ones giving us the primitives and the building blocks for us to be able to build the next generation of hybrid applications. Thank you so much.


Questions


Mettin Parzinski: Thanks, Delba.

Delba de Oliveira: Thank you. Yeah.

[16:23] Mettin Parzinski: All right. I don't know exactly what new React feature it was, but there was a certain point, there was a new feature that was released, and we updated our app, and it felt so fast that I didn't trust it. It feels like every app is going to be like that now, right? And every now and then, we get a new feature and React that's just, it's too fast, you don't believe how fast it is, that becomes the new normal, and you're like, "Damn, this takes a long time." So thanks for your talk. Great information, by the way. We're going to jump into the audience's question. First question is from Marten. Is Next.js adopting the version of Remix on component based routing here? Is it the same?

[17:01] Delba de Oliveira: Yeah. That's an interesting question. So when I think about Remix and the work that they've done in React with routing, they've done incredible work. There's no doubt about that. And if you think about it, most applications use React Router, which are from the creators of Remix as their backbone. Now, when I think about Remix itself, I don't really see it as adapting the vision of Remix as much as moving towards or converging towards a better solution. So what Remix has done really well is they've combined the idea of component based routing with file system routing and created nested routes, which makes sense, because file system routing is easier to implement, but then with component based routing, you can map your components to your URL path. And I think that is a great approach, but I also see it outside of the React ecosystem as well, for example, with SvelteKit. So I think overall as an industry, we're just kind of moving towards that solution of combining component based routing with file system routing.

[18:18] Mettin Parzinski: And it's borrowing ideas, right?

Delba de Oliveira: Sorry?

Mettin Parzinski: We're all borrowing ideas from each other and trying to improve the whole ecosystem for everyone.

Delba de Oliveira: Exactly. Yeah. Switchbacks, so incremental improvements.

[18:28] Mettin Parzinski: Yeah. All right. Next question from Anonymous, "Do you need connected-react-router in a monorepo, or can you just do everything with React Router?

Delba de Oliveira: I actually haven't used a connected-react-router before, so I do apologize.

Mettin Parzinski: Me neither. So I can't answer either.

Delba de Oliveira: No, I'm not able to answer this question.

Mettin Parzinski: All right. Well Delba, then thanks a lot. If anyone wants to continue the conversation with Delba, you can go to the speaker's booth right now. And now, it's time for a big round of applause for Delba.

Delba de Oliveira: Thank you.



Transcription


♪♪ ♪♪ Hey, everyone. I'm Delba, and I work for Versailles. As some of you may know, we're the creators of Next.js, and I'm curious to know, how many of you here use Next.js? Wow! So that's a lot of you. That's amazing. So, although we create Next.js, we also have a platform, Versailles, and funnily enough, Versailles supports over, I think, something like 35 plus front-end frameworks. So even if you don't use Next.js, you could still use Versailles. And today, I want to talk to you about some exciting stuff we have been working on, which is routing. And routing in React 18. Now, as you may know, a few months ago, React 18 was released with new concurrent features. And on the React blog, the team mentions that they expect concurrent features to have a big impact on the way that developers viewed applications. So today, I want to discuss with you what this impact could mean, and how we will change how developers view applications, especially with React's other components as well. And this may not also change things for Next.js, but also for other frameworks and library maintainers. And I'm going to specifically look at routing, but I will also mention data fetching and rendering, or as I like to call them, the three pillars of the web, that are very much interconnected. Now, to give everyone here some context, and also the people who might be watching online, I think it's important to just take a step back and look how routing has evolved in front-end applications. Now, please note, I'm going to be condensing years of routing history in like five minutes, so there's a lot more nuance. And one way we can look at routing is through the type of applications we can build, multi-page applications, single-page applications, and more recently, hybrid apps. So in the very early web, routing was very straightforward, because if you think about it, each URL mapped to a specific file on a server. And then later on, using dynamic server-side languages, we were able to generate a response from the server for a specific route. In a traditional multi-page application, routing is done on the server. And navigating between pages causes the full-page reload that we're all very used to. Now, when native mobile apps came along, they brought smoother transitions and new UI patterns. And single-page applications, you could say, were the web's response to native apps. We wanted our websites to feel like native apps. That is to say, we wanted them to have the same user experience as native apps. And in a traditional single-page application, routing is done on the client side. So on initial load, you may see a white, blank screen while the client side fetches and renders the content. Now, when you navigate on a single-page application, the client will dynamically rewrite new content. And for a given route, the client is deciding what content to fetch and render. And last year, Rich Harris, the creator of Svelte, gave an amazing talk on the whole MPA versus SPA debate, where he discussed some of the pros and cons of each. It's a really great talk, and I recommend watching it if you haven't had a chance to do already. But one takeaway from that talk that I want you to remember is that he recognized that there's an emerging pattern in our industry where applications are transitioning between different environments, client or server, and he called those type of applications transitional apps. And transitional apps, they try to combine the benefits of both the client and the server, because if you think about it, why not both? So that's kind of like a broad overview of routing on the web. Now let's zoom in into React. While React didn't invent single-page applications, you could argue that it contributed to their popularity. The fact that React was and still is mainly concerned with UI and rendering means that the community has come up with a few different solutions for routing. And one client-side solution that quickly rose to popularity was React Router. Now, how many of you have used React Router before? Yes. So React Router's approach to routing is what I like to call component-based routing, where you use code to map specific components in your application to your URL path. And if you combine it with a tool chain like create React app, then you can easily create single-page applications. And this led to, I think, not just the adoption of React itself, but also applications that were fully client-side rendered. Now, in 2016, a few years later, Vercel introduced Next.js. And Next.js was created as a framework to help developers build server-side rendered applications. And Next.js took a different approach to routing. It used what I like to call file-system-based routing, where files in your application map to your URL. And although Next.js felt very similar to a multi-page application, it actually used prefetching and client-side navigation to give applications a SPA-like feel, if you could say. Now, another incremental step towards hybrid apps was the Next.js data-fetching methods, like get initial props. And what these data-fetching methods did was move the fetching outside of your rendering code or outside of your component, so that you could fetch data both from the client and the server. Now, I'm focusing on Next.js today, but it would be remiss of me not to acknowledge some projects that are also working on routing solutions for React, including Shopify's Hydrogen Remix and also Redwood. So, fast forward a few years in early 2020, and the members of the React team were publicly discussing moving more rendering work to the server. The idea was that if we are doing data-fetching on the server anyway, could we move some rendering work to the server and therefore reduce the amount of code that gets sent to the client? Now, you could probably imagine the hot takes that followed that tweet. I think it can be best summarized as, this looks a lot like server-side routing. Are we going back to MPAs? But to quote a not-so-serious meme from one of my favorite people on Twitter, this is less of a pendulum swing and more a spiral of incremental improvements, so switchbacks. Not purely SPA, not purely MPA, but a convergence towards hybrid that benefit from both the server and the client. And each time this conversation was brought up, the React team was careful to emphasize that they were looking for a hybrid solution. And one important thing to note here is that this hybrid solution wouldn't be creating additional requests to the server. It would take advantage of a request that has to exist anyways. So, in December 2020, the React team gave us an early Christmas present that will allow us to move towards more hybrid solutions. That was React server components. When you combine React server components with suspense and the streaming, we now have the primitives or the building blocks to address some disadvantages of multi-page applications or server-side rendered applications while maintaining the same user experience we love about single-page applications or client-side applications. But there is one last piece to the puzzle. And in the last few months, the Next.js team has been considering this. If we need to go to the server to do a trip for data fetching and now we're going to the server to do rendering with React server components, could we possibly also do routing on the server? Could we enable developers to build applications where routing, rendering, and data fetching happens where it makes more sense? And could we give them conventions that are easy to understand but also allows them to move parts of their application either to the client or to the server? So, a few weeks ago, we shared an RFC. And in this RFC, we proposed a new router for Next.js. And this new router builds on top of React server components and React 18 features. There's a lot more detail in the RFC, and I won't go too much into the implementation details. If you're curious to know more about it, I do recommend reading it. But for now, let me just share with you some things that I'm most excited about and that we've been working on. So, if you think about it, with React server components, we can interweave client components and server components in a tree. This means that in a page or a route, you could potentially have both client and server components. So with this new model, it becomes increasingly important to break up our routes into independent fragments, which we call route segments. And these route segments, they map to an already existing term, URL segments. And although we are breaking up the routes, we want to maintain file system routing because generally, it tends to be a more intuitive way for developers to define their routes. And breaking up the routes like this, or as we call it, doing nested routing, has three main benefits. The first one is that we are able to create layouts. And layouts have been a very long, like, going back community ask. And the way that we want to define layouts is that a layout is a UI that is shared across routes. And these layouts, they shouldn't re-render or lose state on navigation. It also means that for components that don't change within a route, so a layout, we also want to make sure that they're still interactive as the user navigates between routes. Secondly, if we combine it with server components, that means that on navigation, the server only has to fetch and render the segments that have changed. And we don't have to re-render the routes for that route. And thirdly, we can have more granular control over data fetching. So in Next.js, as you may know, currently we fetch data on the page level. And with this new model, we can fetch data in the segment level. And since we already moved data fetching outside of the rendering code or outside of the components, what we can do now is we can eagerly initiate those rendering components. What we can do now is we can eagerly initiate those requests in parallel. And this reduces what some of you may be familiar with, which is waterfalls. And overall, the amount of time that it takes to load the content of a route is also reduced. So by building a new router with React server components, we're able to achieve three things. Reduce the amount of code that we send to the client, reduce the amount of work the server has to do, and also reduce the amount of time that it takes to do that work. Now, if we were to combine it with concurrent features, such as transition, suspense, and the future off-screen component, we can simplify the creation of loading states and improve the navigation experience. For example, if you want to use client-side routing and you are fetching as you render, you may have too many staggered loading states or spinners. So really, what you want to do as a developer is consolidate them into fewer, more meaningful loading indicators. On the other hand, if you are using server-side rendering, you have to fetch and render the content before navigation starts. So your application will appear unresponsive as the work is being done on the server. So in that case, you do want to include loading UI to indicate that work is being done in the background. In either case, we believe the framework should provide an easy convention that will allow developers to create loading states. Now, we can also improve the navigation experience further by pre-rendering a very small but meaningful part of your screen. So this means that when you navigate between screens, the navigation will be immediate, and the user might see something like a cover photo or a title before the rest of the content loads. And in a similar fashion, and this one is one of my favorites, is that we will be able to stash routes. And what that means is that we can stop the previous routes and then pre-render future routes so that when the user navigates between the routes, we restore the state. Now, I'm running a little bit out of time, so I'm just going to skip these two slides. But if you do want to find out more, we do have more information in the RFC. What I wanted to highlight here today is that there are many ways you can think about routing in React. And we have been thinking a lot about how we do routing in XJS. It's not about routing itself, but it's what routing will enable us to do. So we'll be able to create more performant experiences with React server components, and also smoother experiences by improving navigation. We also want to make sure that we create experiences that are shareable, that don't break the URL. And to do that, we need to create simple conventions that will allow developers to implement more complex routing patterns. So last but not least, before my time is up, I want to take a moment to give a shout-out to the React team, because they're the ones giving us the primitives and the building blocks for us to be able to build the next generation of hybrid applications. Thank you so much. Thank you. All right. It was a blast from the past. I don't know exactly what new React feature it was, but there was a certain point, there was a new feature that was released, and we updated our app. And it felt so fast that I didn't trust it. It feels like every app is going to be like that now, right? And every now and then, we get a new feature in React that's just, yeah. It's too fast. You don't believe how fast it is. That becomes the new normal, and you're like, damn, this takes a long time. Yeah. So thanks for your talk. Great information, by the way. We're going to jump into the audience question. First question is from Martin. Is Next.js adopting the version of Remix on component-based routing here? Is it the same? Yeah. That's an interesting question. So when I think about Remix and the work that they've done in React with routing, they've done an incredible work. There's no doubt about that. And if you think about it, most applications use React Router, which are from the creators of Remix, as their backbone. Now, when I think about Remix itself, I don't really see it as adapting the vision of Remix as much as moving towards or converging towards a better solution. So what Remix has done really well is they've combined the idea of component-based routing with file system routing and created nested routes, which makes sense, because file system routing is easier to implement. But then with component-based routing, you can map your components to your URL path. And I think that is a great approach, but I also see it outside of the React ecosystem as well. Like, for example, with SouthKit. So I think overall as an industry, we're just kind of moving towards that solution of combining component-based routing with file system routing. And it's borrowing ideas, right? Sorry? We're all borrowing ideas from each other and trying to improve the whole ecosystem. Yeah, switchbacks or incremental improvements. Right, next question from Anonymous. Do you need connected React Router in a monorepo, or can you just do everything with React Router? I actually haven't used connected React Router before, so I do apologize. Me neither. So I can't answer either. No, I'm not able to answer this question. Well, Delba, then, thanks a lot. If anyone wants to continue the conversation with Delba, you can go to the speakers booth right now. And now it's time for a big round of applause for Delba. Thank you. Thank you.
20 min
17 Jun, 2022