Next.js 13.4 recently released the stable version of the "App Router" – a transformative shift for the core of the framework. In this talk, I'll share why we made this change, the key concepts to know, and why I'm excited about the future of React.
AI Generated Video Summary
Today's Talk is about the Next.js App Router, which has evolved over the years and is now a core feature of Next.js. The Talk covers topics such as adding components, fetching remote data, and exploring layouts. It also discusses submitting form data, simplifying code, and reusing components. The App Router allows for coexistence with the existing pages router and enables data fetching at the layout level using React Server Components.
1. Introduction to Next.js App Router
Today, I'm gonna be doing something a little different because my talk will be entirely in VS Code. I'm Lee and I work at Vercel and I lead the Next.js community. We recently released something called the Next.js App Router. Today, I'm gonna be walking through a demo of what the App Router looks like. Next.js has evolved a lot over the years. It was originally released in 2016 and it had a certain way of building. I'm gonna talk about the journey through a real application of how we landed on the App Router today. The core of Next.js is routing. We released Next.js in 2016 with file system-based routing. This is what Next.js was built on six years ago. We're going to be building an internal app dashboard that shows a list of invoices and has some settings pages with layouts. Let's start scaffolding out this page a little bit more. We'll use the home layout for the nav bar and invoices. This is the shell of our homepage.
Today, I'm gonna be doing something a little different because my talk will be entirely in VS Code. Woo! So, like she said, I'm Lee and I work at Vercel and I lead the Next.js community. If you haven't heard of Next.js, it's a React framework. And we recently released something called the Next.js App Router. Has anyone heard of the App Router? OK, that's more hands than I expected, that's awesome.
So for those who haven't, what I'm gonna be walking through today is a demo of what the App Router looks like. So the premise of this talk is called Next.js Metamorphosis. And the name for that is because Next.js has evolved a lot over the years. It was originally released in 2016 and it had a certain way of building. And I'm gonna talk about the journey through a real application of how we landed on the App Router today.
So what I have here is my editor on the left running a Next.js application and then my browser. And the first thing we're gonna start off with is the core of Next.js, which is routing. We released Next.js in 2016. We said we love file system-based routing. We wanted to make it as easy as possible for you to get started. Just drop a file in a folder and it will create a new route. So I have pages slash index.tsx here. So if I do export default function home page and I say return React summit and save. We see React Summit. This is all it takes to get your first route up and available on your local server or in production and this is what Next.js was built on six years ago.
Now what we're going to be building today is kind of an internal app dashboard that shows a list of invoices, it's got some settings pages that have some layouts and we'll get into more some of the advantages of some of the new things we've been using. So let's start scaffolding out this page a little bit more. So rather than returning a string here, let's return some layout component and we'll see if my VS code is not giving me my helpful auto-completes, but that's okay. We can manually type things out. That's fine. We'll do import layout for components and I have two different layouts from application. We have a home layout so we'll use that one here. There we go. Okay, so we have a nav bar that's going to be shared throughout our application and then we have my invoices. So this is kind of the shell of our homepage.
2. Adding Components and Fetching Remote Data
Now let's add some components like cards and tables to display statistics about our account. We'll fetch remote data using the get initial props API and pass it to our React components. This API runs on the server and on page transitions. To improve this, the API was split into two separate functions: getinitialprops and get server side props.
Now inside of here, we also want to have some components and display some UIs. So let's do some cards that are going to display some statistics about our account, maybe how much money is in our account, what the invoices are and then we'll also do, oh, there we go, it's coming back, it's trying to help me out a little bit here. Components slash table, let me just manually import the card as well too, import card from components card.
The core of Next.js is built on React, allows you to compose components but very frequently you need to actually fetch some remote data and pass it to your components. So the first API that Next.js released to do this back in 2016 was called get initial props. So let's use that to actually fetch some data and put it on the page. So I'll say homepage.getinitialprops and this is gonna be an async function that we want to fetch some data from. So we'll do cons data equals await get card data. And then we're gonna return that data to the page. So let's see. Nope, I'll type it out myself because oh, there we go. Okay. From our library from our database, I'm gonna import this function get card data that's gonna return some JSON here, we're gonna return that data from this function, and that gets forwarded to our React component. So inside of our React component, I have props so I can de-structure out data here. And we'll just throw on some any type here. Gotta love that. And then down below on our cards component, we can forward along that data by passing props. So I'll do data here and I'll hit save. We have data on our page. Woo! Love it. This works. And we're able to fetch remote data, but it does come with some tradeoffs or some things that maybe are not obvious. So this runs on the server, but it also runs on page transitions. So this first API left some room for improvement for us to clarify how you would actually import and use these functions for fetching remote data. So the metamorphosis of this, the next evolution of this API was breaking this into two separate guys. So export async function get server side props, and okay, cool.
3. Exploring Data Fetching and Layouts
Copilot helps with async functions that return objects. You can choose fresh data on each request or pre-render during build. Slow APIs can cause loading spinners and blank pages. It's challenging to define loading states next to components. Fetching data on the client side can have tradeoffs. We'll build out settings pages with different layouts.
Copilot is going to help me out today. We'll see if that works. So this is basically the same thing structure-wise. You have an async function that's going to return an object. In this case there's a key for props. So if I hit save, it also forwards the data long to the page. But the difference is that this is explicitly marked as get server side. So it makes it a little bit more clear to developers, hey, this is running on the server side and it's forwarding that information over to components.
Now the counter to this or the pair to this is get static props. So you get the option whether you want to choose do I want my data to be fresh on every request, or do I want to pre-render this during the build and potentially incrementally update it later.
Now let's talk about some of the potential areas for improvement with this. Let's say our API is slow. And to simulate this I'll just throw on a two second delay here into get card data and you see our browser loading spinner spin and spin and spin and spin. Now when you're doing this top down data loading you have all or nothing data fetching for your page. Either you have all of your data or you have none of it. So maybe to make this a little bit more dramatic I'll go to about blank and then I'll go back to local host and you see this is what your users are seeing. This is what your customers are seeing. A loading spinner and potentially a blank page. That's not a great experience. Ideally we could show some kind of visual indication to our viewers whether that's maybe something like a loading state on the components. That would be kind of nice. Now unfortunately one of the areas for improvement here is that it's not easy to declaratively define your loading state next to your component. Ideally this card component knows about the data it needs, it knows how to load and it knows how to define its fallback or the loading state that we're showing here. Now the way this would work in the Pages router of Next.js is that you often had to change your strategy of fetching data. So instead of fetching data on the server if I wanted to have this co-location of data, UI loading states, I would need to fetch data on the client side or just normal React in the browser. And that works but it can also cause some other tradeoffs as well, too. So there's some opportunities for improvement here that we're going to talk about in the app router, but we'll pause on this page and we'll move to another page. So in our application, we have our homepage with some invoices, some cards and a table, but we also have some settings pages that look slightly different and have a different layout. So we're going to build out those. So let me do an export default function settings page and we're going to return some layout here.
4. Creating Settings Page Layout and Form
This part focuses on importing the layout from the settings layout and creating a different settings page layout with a top bar and sub navigation. It also demonstrates how to create a form to submit data, including components like name, bio, and a button. The button can take in children and is used to update the profile.
This is going to be import layout from settings layout. And I have the you don't need to have all those paths. See if that works. Test. Yes. Could be a I don't remember if it's the default or name to export. Let's see if that works. Yes. Whoo.
Okay. So we have the same Nav bar that's shared across our application and then we have a slightly different settings page layout here that has this top bar but also this sub navigation for our page. So we're going to have two different pages here. We're going to have a profile page for settings and a display settings page.
Now inside of here, let's do a form. So we're going to show how to actually submit some data here and we're going to do this by scaffolding out name component. Let's do maybe a well let's see if this works. Name no. OK. Bio, these are just going to be inputs that we can use our application. Maybe a button. Yes, OK. And maybe this button is going to take in some children because we can just forward along those that child to our component. And we'll say this is going to be something like update profile, for example. Let's will it auto do it? No, it won't. OK. Import name bio components. Basically don't remember what the path is here. What is it? Component, is it in settings layout? It probably is. It's better when it automatically gives you the import, that helps a little bit. Let's see, cards, minimal, nav bar.
5. Submitting Form Data and Handling Tradeoffs
Where is it? It's in the settings layout. My editor doesn't want to update. Let's see if that works. It works. One day VS Code will help me out more.
I have a name input, a bio input, a normal button that takes in some child. And I want to submit this form. The way most of you have been building Next.js or React applications is you use an event handler to do this. Go to our form and say, you know what? On submit we want to call some function handle submit. Because I would absolutely fat finger this, I'm going to drop in a snippet for this one because it's a lot of code. And we'll see how we can delete some of this code here in a second.
6. Simplifying Code and Reusing Components
There's a lot of extra code that you need to write. We're going to see some ways that we can reduce a lot of that code and simplify the mental model. Let's talk about reusing layouts and components. We have a component called minimal mode that allows toggling of data shown in the invoice table. We can co-locate our data fetching with our components to avoid passing data from server to client. Let's make our application smoother by deleting unnecessary code.
And also, as you can tell, there's a little bit of ceremony code here. That would be the nice way of putting it. There's a lot of extra code that you need to write. And we're going to see some ways that we can reduce a lot of that code, and also simplify the mental model hopefully a little bit as well, too.
Now, while we're here, I also want to talk about that layout. You notice I've been importing this component that has the layout for our application. Well, what about when I want to have another page that uses the same layout? I actually won't write this one out fully because it's pretty small. But if I want to have this display page that's also in the settings, we're going to use... We're going to use... Let me just close this here. So pages slash settings slash display. We're going to use that same layout, and it's going to take in a component I'm going to call minimal mode. So let's take a look at what this looks like. I'll click over to display. We have this component called minimal mode that allows you to toggle how much data we want to show in our invoice table.
Now what you would love to do is reuse, that's the great power of React, reuse that table component, the same table component that we used on the home page. So this works. We can input it here, but this comes with another opportunity for improvement. Right now, this table data, if I actually jump into this file, this is not asynchronous, this is static data, which means that I'm able to actually do this. But if all of the sudden, this was instead data equals data, and I needed to forward in some data from a remote location, that would mean that this page would need to have some kind of data loading function, a get server-side props, the index page would need to have that same data loading function. Everywhere you would consume that component would need to pass that data and serialize it from server to client. It would be great if we could co-locate our data fetching with our components.
So, now that we've got this scaffolding of our application across the index route and the settings route, let's take a look at how we can make some of these things a little bit smoother. So, what I'm going to do is actually go to our index route. I'll just copy-paste all this code. And, then I'm going to go to this brand new app directory and I have a new subfolder and then a new file page. So, page is this special convention that marks a piece of UI that's routable. So, I'm going to paste in our code previously and let's start deleting code. That sounds fun. So, let's take this promise.
7. Fetching Card Data and Handling Slow Data
We removed the Next.js specific API and instead used an asynchronous function to fetch data. The layout for our application can now be defined through the file system, streamlining the process. We can handle slow data fetching by presenting a loading state to the user. The data fetching is now co-located with our cards component, allowing for more flexibility in handling data delays.
We want to go fetch some card data. We'll keep this. But what if we removed the Next.js specific API. What if we removed the forwarding of props to serialize this in between here and instead we just said, you know what? This is an asynchronous function and we're going to fetch some data in it. Well, that seems pretty simple, doesn't it? So, let me hit save here and then I'll go to locos3000 slash new and we see the, let me reload the page here. Oh, I have the loading state. It threw me through a loop there. And I was like, is it loading? We see the exact same thing that we saw before, which is we have data displayed in our cards.
Now for the good eye here, you might be wondering why are there two nav bars? And the reason for that is because that state is actually shared between all of the routes. That layout is the global piece throughout our application. Now, luckily in the App Router, there's a new way or a new convention that allows you to define your layouts through the file system. So the route layout for our application, it's importing our global styles, and it's using that nav bar, which is the part that's actually shared throughout the application. So what that means is that this layout can actually be the part that's only shared for the home page. So you know what? Let's get rid of this nav bar. We don't need that anymore. OK. So that streamlined that down a little bit.
But what about that problem I mentioned about if the data is slow, does that still exist? And the answer is yes, that problem still exists, because now you have the flexibility and the optionality to choose how you want to handle this. So let's say, you know what? We actually want to stream in data out of order. This card UI, we know that this API or this database call might be a little bit slow. We don't want to block the rendering of the page while waiting for this data to come in. Instead, we want to present the user with a loading state. So you know what? Let's remove this, and let's remove this, we'll remove this, we'll remove this, too. And we're going to do the data fetching co-located with our cards component. So I'll pop in here and let's mark this as an asynchronous function. This is a React server component. And then I'll say, you know what? If we pass the slow prop of true, then we're going to fetch our card data, and we're going to have this delay here. So now, back inside our main index route, let's declaratively define what we want the loading state to look like. So I'm going to take this and we're going to wrap it in React suspense, and we're going to throw the slow prop on here, and then we're going to define a fallback state of how we want loading to look like, and that's going to be cards loading. So I'll hit save here.
8. Reloading Page and Refactoring Code
And now, when I reload the page, I instantly see the UI with the data streaming in. The data is co-located with the component, making it much nicer to work with. Let's move on to the second problem, the profile page or settings page. We can refactor the code to run the component entirely on the server as a server component using React server actions. This eliminates the need for a separate API fetch and allows us to use an action on the form with a server-side function.
And now, when I reload the page, actually for dramatic effect, dramatic effect is fun. I'll go to about blank, localhost new. We instantly see the UI, the data that's slow gets to stream in out of order, and it gets to be co-located with the component. So you keep the data close to where the UI is actually being rendered. That's a big improvement. I think that's much nicer to work with. So that's one problem down.
I'll try to go fast here. The second one is the profile page or the settings page. Let me just go back to here. I'll copy-paste this code over into the new page, and let's look at how we can refactor this from the previous way to a new way in the app router. So as I mentioned, one of the opportunities for improvement is there's a lot of ceremony code. What if we didn't have to make a fetch to a separate API? And what if instead we could run this component entirely on the server as a server component? Well, we wouldn't be able to use an event handler. We need something new. And with the introduction of React server actions and the integration into Next.js app router with the caching and revalidating layer, we made it so that you can do something like this.
I want to have an action on my form, and actually I'm going to denote this button as a type of submit because it's just using the HTML form here. It's just using a button with type submit. And this action is going to take a function. This could be like save to db or something. Now, instead what we can do here, let's just get rid of this entirely, actually, because we don't need an event handler. We're going to have an async function save to db. Copilot is very wrong here. It needs to learn how this works still. And it's going to take in not from data, but form data, which uses the web form data here. And this function is going to run on the server. So the way that we denote this as a server action is through the use server directive. If I can type, there we go. And then we can get the data from form data, the name, and the bio. And then let's just console log these out here. That looks right to me.
9. Refactoring Layouts and Co-locating Data
We refactor the shared layout between the home page and settings pages into the file system layouts. The layout component is lifted up to the layout file, eliminating the need for separate API routes. The display page and settings page can reuse the shared layout. Data can be co-located with UI using react server components, allowing for asynchronous functions and caching of data.
Okay, new slash settings slash profile. But whoa! What is going on here? We have layout's inception. What is this? Like we showed on the home page, there's an opportunity to improve how we handle layouts. So I mentioned there was a piece of our layout that was shared between the home page and the settings pages. Let's actually refactor that into the file system layouts. And to do that, you see we have a new nested layout in the app router, and this is taking in that settings layout component that shared between the two routes. So we've actually moved that state up, and critically, this component will not re-render. So if we wanted to have state in here, when we transition between pages, like a toggle or something, that would not get reset. Now, that's not in the demo, but it's just good to know. So if I go back into here, I actually don't need this at all because it's been lifted up to the layout file, so I can remove that entirely. And we're making progress, but we still have the double nav bar, so we want to go into here and say, you know what? For the settings layout, we don't need this anymore. Boom. Goodbye. There's our new feature, double nav bar. OK. That's looking good.
10. Exploring App Router Features and Coexistence
We can periodically update the data every 60 seconds by setting the revalidate value. The App Router allows for a clear boundary between server-side and client-side code, enabling the use of React hooks on the client. The App Router can coexist with the existing pages router in the same Next.js application. You can incrementally adopt the App Router as you and your team are ready. The App Router is stable now, but you don't have to move away from pages immediately. Both routers will continue to be supported and maintained.
Well, we can still cache that to static data by default, but then maybe we say, you know what, export const revalidate 60 seconds. So we want this route segment or this component to update every 60 seconds. That's all it takes for us to periodically update that data at most every 60 seconds.
So I know I blew through a lot of things here. There's a lot more that I could cover, even getting into talking about how client components work. So let's say maybe this minimal mode toggle, we wanted to have some state. I'll just quickly show that as well, too, since I'm right here. Let's say we wanted to have data and set data or something. We want to have used state, something like this in our minimal mode for this toggle. Well, this is going to show an error. Well, can't find used state, that makes sense. But let me actually import it here. And now what we want to do here to actually be able to use used state, we would say, you know what, to have an interactive component on the client side, we have a much more clear boundary between what runs on the server and what runs on the client. So we're going to explicitly mark this with the used client directive to say that this component has access to using any React hook in the ecosystem, built-in hooks like used state or used effect. So now, hopefully, the code is more clear, which code has the ability to also run on the client, and which code runs only on the server.
This was a whirlwind run-through of the App Router. Hopefully, this gave you a better overview of some of the new features that we're working on in Next.js. The final hat trick here is that this all works in the same Next.js application. All of the APIs that we worked on and used from day one, get initial props, everything that we've built from the start, this can all coexist in one application and harmoniously live together. So you can keep your pages router, which is working great in production today, and then incrementally adopt the App Router as yourself or your team is ready and try out some of these new things.
So thank you all for listening to my talk and I appreciate it. Because the App Router is stable now, should we move away from pages ASAP or will it still be maintained for the long term? Yeah, great question. Hopefully, the goal of my talk was to show that it's been designed for these to live together. So you don't necessarily have to move from pages to App Router today. Even though the core is stable, there's still going to be bugs and things that we're working through. And we really appreciate everybody giving feedback and trying things out as we move the core over to stable. I would say adopt things as you feel ready for your team but you shouldn't feel pressured into moving over. Both can continue to live and the pages router will continue to be supported. We're even adding some new features there as well, too. So it's definitely going to continue to be maintained.
11. Data Fetching in SSR for Layouts
The great thing about React Server Components is that you can now fetch data at the layout level. This means that you can fetch data for a shared layout, such as user information, and share it across all pages. This was not possible before with the pages route.
Cool. Awesome. That's great news. Yes. A lot of votes for when is data fetching becoming available in SSR for the layout? For example, fetching and navigation for a shared layout. Yeah. So the great thing about React Server Components, the foundation of what we built in the App Router is that all the examples I showed today were fetching data inside of the page. But if we wanted to, we could actually lift that up to the layout. And for example, I had that layout component for our settings pages. Let's say we needed to fetch some user information and share it in a navbar between all of the pages, you can actually still do that. You can mark the layout as asynchronous. You can await some data fetch. And that was not possible before in the pages route. There was no data fetching on the top level.