My learnings from using Next.js’s new router

My learnings from using Next.js’s new router

Author
Date
Tags
 

Context:

NextJS released App Router a few months ago. We have been using the page router since the start of the project. There will be a time when the page router will be deprecated/go into maintenance mode. Hence, there is a need to understand what the App Router brings to the table, what it adds and removes, its benefits and drawbacks.
We also need to determine a path forward for continuance/migration of the existing pages and components and whether a migration of the components is necessary in the near future
 
 

What the App Router brings to the table?

React Server Components
React Server Components introduces the ability to write React code that can run on the server; connect with databases and other services via drivers, connectors and APIs; pre-populate and stream data to the client. This approach helps in
  1. Making SEO friendly pages
  1. Pre-rendering information decreasing perceived load times
  1. Connecting with data sources that cannot be enabled on the client side
Server components also integrate seamlessly with Client Components creating allowing delegation of certain tasks to the server and rest to the clients
 
For the full details please read the RFC linked

Points to note

  • RSC introduces “use client” & “use server” pragma
  • Hooks and Event listeners cannot be used in Server components
  • RSC lacks support from almost all of the client side libraries associated with React
  • All routes in Next default to Server components unless specified
  • Its basically a “spec” with implementation left to frameworks. Only Next.js supports RSC right now with Remix planning to add support in V3
 
 
Expansion of the Folder Based Routing
Next.js expands upon the previous routing to include Loaders, Templates, Error Boundaries, Fallbacks, Common Layouts, Parallel Routes and Grouped Routes.

Points to Note

  • Stopgap solutions like assigning layouts to components via assignment on the function itself have been replaced with a layout.js file.
  • Routes can now be grouped together to use the same layouts
  • Same endpoints can host multiple different pages based on different conditions
  • Separate client side Error Boundaries have been added as a wrapper on the main files
  • api folder has been removed. It has been replaced with route.js
 
For more details, refer to the link attached
Streaming Data
♻️
Not exactly a React Server Components change but usage of Suspense and fallbacks is bound to increase with more tasks being performed at the server
Streaming & Suspense is a new addition to React where content need not be present right from the start to be rendered to the client.
Suspense wraps components that are not immediately available. Whenever a component throws a promise, Suspense renders a fallback component until the promise is resolved.
This fits well with React Server components where the data which is not available immediately can be streamed to the client later. Check this example and the blog
💡
loading.js file from the App Router uses Suspense under the hood to show the fallback.
Another example of suspense would be Lazy Loading an Image
Simple Example code
function useSuspenseImage(src: string) {
  if (!imageCache.has(src)) {
    throw new Promise((resolve) => {
      const img = new Image();
      img.src = src;

      img.onload = () => {
        imageCache.add(src);
        resolve(img);
      };
    });
  }
}

function LazyImage({
  src
}: {
  src: string
}) {
  useSuspenseImage(src);

  return (
    <Box
      component="img"
      src={src}
    />
  );
}

function Page() {
	return (<Suspense fallback={"Loading..."}>
					 <LazyImage src="https://google.com/logo" />
				 </Suspense>)
}
Layouts
In the Page Router, common layouts for different pages is a complicated process where the layout is assigned to the function itself.
function Page() {
	return <div></div>
}

Page.getLayout = getLayout()
App router replaces this approach with a layout.js file which is present in the route group for all the pages in the group. For more details, refer the link provided in the toggle but in general the code will now become with the editor and new-projects pages having a common layout
app
├── (loggedIn)
│   ├── (dashboardLayout)
│   │   ├── editor
│   │   │   └── [documentId]
│   │   │       └── page.tsx
│   │   ├── layout.tsx
│   │   └── new-projects
│   │       ├── [projectTopic]
│   │       │   └── page.tsx
│   │       └── page.tsx
│   └── layout.tsx
 
App Router also expands upon the util methods adding server-side methods for performing actions on cookies and headers, revalidating cache, and server actions like redirects and not found errors. These methods simplify the APIs since nothing needs to be specifically handled by the developer unlike Page router where cookies needed a separate lib to parse them.
 
App Router also introduces the next/navigation components, involving a new router class and hooks for retrieving path and params. The new router and the hooks split the responsibility of the old router wherein,
  • the new router only handles the navigation.
  • the new hooks namely, useSearchParams, useParams handle searchParams and uriParams respectively.
  • usePathname returns router path.
 

Lessons & Challenges with the App Router

The App router opens up new avenues of thought that was not present earlier in React.

-” Where does this code run?”

 
Some history, In the era of Server rendered pages of PHP, ASP, Spring, HTML was generated on the server with additional interactivity added via JavaScript. This was lost in the move to Client-side rendering where we introduced a fat client and thin APIs.
 
React Server components and the App Router repeat this history. Some processing happens on the server, some on the client. This leads to the repeat of the question,
 
“Where should this code run?”
 
Specifically, the data that is shown on a page can be pre-populated or rendered on the client. Which approach to use depends on the use case.
For example,
  1. If the page is needed for SEO, code has to run on the server.
  1. If the page does not involve a search engine, initial data can be fetched on the server to prevent extra client server hops while the interactivity happens on the client.
  1. If the page requires a lot of interactions and there is no requirement of some data being present right from the page load, then that code should run on the client side.
 
However, the point still stands that a thought needs to be given to where the code needs to run.
 

- High Memory usage

App Router introduces a lot of new variables in. These currently are causing spikes in memory usage. A lot of potential reasons have been put forth. Some of them are:
  1. Improper imports of large libraries. Icon libraries and UI frameworks like MUI have tons of modules barrel exported. Improper imports can cause all of the modules to be compiled in one go. This leads to extreme usage spikes that cause the node process to go OOM.
    1. A potential fix is to use the organizePackageImports option from next.config.js.
    2.  
      Please track the issue here
 

- Lack of support from most of the React ecosystem

This is a major issue. Libraries like Material UI, Emotion and a few more CSS-in-JS solutions depend on react to pass along data. Since effects and hooks dont work on the server side, these components end up running on the client and we lose the major benefit of smaller bundle size.
 
Purely CSS based solutions like tailwind work with RSC very well due to them being compiled to plain CSS before use, however the rest of the ecosystem is basically cut off from RSC.
 
💡
Material UI DOES NOT SUPPORT server-side rendering at all.