Exploring the Next.js App Router

August 11, 2023

Introduction

In the world of Next.js, the release of the app router alongside Next.js 13 has stirred up the community. Many are questioning the need for change when the pages router worked so well. However, after taking a closer look, it's clear that the app router leverages new React features and offers a comparable level of simplicity.

As a developer diving into the Next.js app router for the first time, I am excited about the advanced patterns it brings for optimization and configuration, but while building this blog, I focused on exploring its basic capabilities, and in this post, I'll share my thoughts on some key aspects that stood out to me.

File Structure

Having been accustomed to the previous pages router, I found it helpful to visually compare the file structures of the two systems. Here's a glimpse:

Ultimately, I found the app router's file structure to be more flexible, allowing for granular customization for each route. However, migrating existing Next.js projects from the pages router to the app router might be challenging. Luckily for this blog I'm starting fresh! That's giving me the freedom to embrace the app router system fully.

Server-side Routing, Client-side Navigation

By default, the app router performs server-side routing for components, while navigation occurs on the client side. This means that when navigating between sibling components with the Next.js Link component (or the useRouter hook), the root route segment layout they share will persist (not be rerendered). In my case, this means the app root layout (e.g. What renders my nav bar on every page) will not need to be rerendered when someone clicks on one of my blog post links, making it faster for my readers to see the post.

Enhanced Data Fetching Methods

In the pages router, we relied on getServerSideProps to fetch data, which resulted in a loading UI that remained until data for the entire route segment was loaded. With the app router, however, React's streaming and suspense features come into play, allowing users to interact with components that don't require data while other components on the same page are still pending data retrieval. This ability to provide a responsive interface, even when certain components are still loading, is an objective win for user experience! Data fetching on the client side is still being perfected by the Next.js team and it's still recommended we use SWR or ReactQuery for that.

Per Request Caching

One notable advantage of the app router is its support for per request caching. Unlike the segment-level caching mechanism used in the pages router, per request caching enables us to cache data on an individual request basis. When the same request is made multiple times, the app router automatically deduplicates the subsequent requests, eliminating the need for redundant data retrieval. This caching optimization not only saves time but also improves rendering efficiency. For instance, in my implementation of the [postId] leaf route, where I make several requests to getPostByName (see in the repo), per request caching plays a significant role.

Rendering

There are two new changes to the rendering strategy with the app router. The rendering environment (client or server) is determined on a component/route level rather than the app-wide decision required previously when we had to wrap our app in providers in order to customize layouts on a route level.

Application code can be rendered either on the client or the server. It used to be that every component was a client component, but the app router uses React server components for all components unless we explicitly use the 'use client' directive at the top of our component file. That allows us to opt in for when to use state, life cycle methods and client-side APIs that the browser provides, which results in sending javascript to the browser only when necessary.

Static rendering is the app router's counterpart to the pages router's static site generation. It allows both server and client components to be prerendered on the server during the build process. Developers can choose the rendering environment (server or client) for each component, and the results are cached and reused for subsequent requests.

Dynamic rendering, on the other hand, corresponds to the pages router's server-side rendering (using getServerSideProps). With dynamic rendering, neither server nor client components are prerendered. Instead, they are rendered on the server at request time, and the results are not cached.

By default, Next.js 13 employs static rendering, dynamically rendering the entire route when it detects dynamic functions or fetch requests.

It's worth noting that while the app router introduces new terminology, as far as I can tell, the concepts, static site generation and static rendering, or server-side rendering and dynamic rendering, are nearly identical. The difference having to do with the fact that we don't need to use the getServerSideProps function to implement asynchronous/dynamic rendering anymore, components themselves can be asynchronous functions (a new React feature) and we use the generateStaticParams function to implement static rendering instead of getStaticProps or getStaticPaths.

Conclusion

I personally love that the app router replaces all the old rendering methods (getServerSideProps, getStaticProps) with default server components and allows for control of the layout on a route level as well as per request caching. Overall I think the new app router will be adopted by most soon. Change can be painful, but there are some tangible benefits that might be worth switching over to on existing projects, and certainly if you are starting a new project. If you're interested in learning more I recommend watching the Front End Masters course Introduction to Next.js 13+, v2 or going straight to the Next.js docs.

Related

Back to home