Next.js 13, the latest version of one of the biggest frontend frameworks, introduces many exciting features that refine the way developers build applications, while also improving user experience and application performance.
One of the most significant changes in this release is the complete overhaul of the file-based routing system and the introduction of server-side components. This blog post will go into the motivations behind this significant change, discuss the current limitations, and show developers how to adapt to and fully leverage these new features.
Understanding the reasons behind this significant change
The biggest and most significant ongoing development changes originate from the library behind Next.js - React.
In late 2020, the React team announced they were working on React Server Components. However, it wasn’t until the beginning of 2023 that these components were established as the new application architecture, making them the default choice for new projects.
Shortly after, the Next.js team released version 13 of the framework, aligning it closely with the innovations and modifications introduced in the React library. They aimed to utilize these innovations to their fullest potential while seamlessly adapting the existing file-system-based router structure to fit the new React architecture.
The result is an architecture that prioritizes a server-first approach, one that needs thorough understanding before its implementation in applications.
The first step in familiarizing yourself with the new file-system-based architecture is to use the new app directory. In previous architecture, which will continue to be supported in the foreseeable future, a routing system was used, where each file created represented an application route. This simplified the process of adding new routes and pages without requiring manual routing configuration.
The new routing system operates differently, while still retaining similar features.
In Next.js 13, creating a route requires a couple of steps. Firstly, Next.js will not recognize a new route unless a page.tsx file is created. At first glance, this may seem like a significant change from the file-system-based routing used in older Next.js versions. However, it should be viewed more as an upgrade because in the latest version, the folder containing the page.tsx file should be named to represent the route.
With this approach, the architecture provides flexibility for folders to contain additional files that can be used for additional purposes that benefit the route, without necessarily designating them as individual routes within the application.
Expanding the folder structure
Although the file naming in Next.js is flexible and can be named to anyone’s liking, it’s important to be aware of specific naming conventions designed to serve distinct purposes. These prescribed file names must be named by design because Next.js automatically recognizes and, when the criteria are met, modifies or complements the content within the page.tsx file. Understanding these Next.js file naming criteria is crucial for optimizing your project.
Replacing the content
Examples of specific files used for replacing the primary content include files designed for displaying loading or error screens.
For example, in the same folder as page.tsx, you can create a file named loading.tsx. This file is displayed while page.tsx is loading. Once page.tsx has finished loading, it automatically replaces the content with the loaded page.
error.tsx on the other hand, is used differently. As the name suggests, the file loads fallback content in case of unexpected runtime errors within the application.
Creating a consistent structure
While some files are used to replace the content of page.tsx, others serve to complement the content within the same folder. An example is layout.tsx. This file is used to wrap the content of page.tsx and its nested files, ensuring consistent structural patterns for routes within the same category.
Scoped globally or locally
Although these particular files are used to complement page.tsx Next.js ensured their application to all nested routes as well. This allows for the creation of global files loading.tsx or error.tsx, which can replace page.tsx across the entire application. If a specific page requires more refined error handling, you can create a dedicated file within that folder. Next.js will then automatically detect what to do and apply the appropriate handling as it searches for the nearest such file.
Example of error handling on a global level (if error.tsx in contact folder didn't exist)
Example of error handling on nested level
When it comes to layout.tsx, there’s a slight difference. Instead of replacing the layout file, Next.js combines all the layout files within the tree. This way allows for the creation of a global navigation layout that won’t be overwritten when adding a layout file for another feature in the future.
Example of global layout
Example of layout combination on global and nested level
The files explained are not all of the additional optional files. The whole list can be found in the Next.js documentation.
Creating different routes under the same folder
Often applications under development may have various routes, yet share common design elements, such as registration and login interfaces. To streamline this process, app directory includes a feature: routes are not generated if the folder name is enclosed in round brackets. Additional files can be placed within the folder, ensuring that Next.js incorporates nested routes as intended. This approach allows us to share design principles across multiple routes, eliminating the need for duplicating code within the application.
With this, both /login and /register pages will have design elements derived from layout.tsx page.
Shifting the focus
In the process of developing applications, users frequently interact with a small amount of written code - like search bars, buttons, or data input field. Meanwhile, the rest of the screen is dedicated to presenting meaningful data and should remain non-interactive.
To ensure smooth application function properly, non-interactive and other important components, such as data fetching, processing, rendering UI components, and many other complex functionalities, can and should be implemented as server-side components.
Purple color represents server components, blue color represents client components
(Source: Building Your Application: Rendering )
This results in a significant portion of the application being server-side. That’s why, in the latest version of Next.js, every component created in the app directory is inherently considered server-side, unless otherwise specified using the “use-client” directive.
Next.js is no stranger to server-side rendering, a feature that has been available for some time now through functions like getStaticProps or getServerSideProps.
However, with the new folder structure introduced in the app directory, the focus has shifted towards adopting a server-first approach for applications.
Limitations of the server-first approach
While making components entirely server-side offers several advantages, it does come with some downsides and limitations. One of the most significant is the inability to use React hooks or event handlers within server components.
Using React hooks
In the context of server components, the use of React hooks and event handlers is not possible. This restriction arises from the fact that server components in Next.js are executed on the server side, whereas React hooks are specifically designed for use within client-side components. Hooks like useState, useEffect, and others are optimized for the browser environment and the interactions that occur within it. Since server components execute on the server before sending the page to the client, the typical flow of these hooks may not align well with server-side rendering, as there are no browser interactions at that level.
Implementing server components
As the picture from Next.js documentation suggests, you should use the client component only in user-interaction areas at the end of the component tree. This is necessary because client components often undergo re-rendering, and implementing server components alongside them would result in unnecessary repeated calls to server components. There’s no need for multiple server component calls when the purpose is fetching the same data from the database.
That’s why it’s not possible to incorporate server components within client ones in Next.js 13. While there’s a possibility of passing them as props, this approach also requires careful handling to avoid the same issue.
State management in Next.js 13
Implementing state management with the newly updated file-system-based routing can become a significant challenge. For example, Redux still lacks full support for React server components, necessitating a workaround to make it functional temporarily.
API calls should be implemented as server components, but accessing state management within server components is not supported. However, that could change in the future.
Client components offer a powerful tool for calling server components to handle API calls. However, this approach can hinder application performance, leading to slower load times and potential delays as each request operates asynchronously. Even though the documentation suggests otherwise, an alternative strategy is needed.
Consider employing a client component designed specifically as a preloader. Its primary component is to seamlessly receive data from server-side components and store it efficiently in state management. As there are no other effects on the preloader, it will operate just once during initialization and never again throughout its lifecycle. This makes it a perfect extension to server-side components.
Looking into the future, as advancements are made to allow access to state management, the preloader component can be effortlessly retired, ensuring a smooth transition. What is most important here is that this change won’t have an effect on other aspects of your application.
Impact of server-first approach on library integration
The transition to a server-first approach in Next.js happened suddenly, catching many libraries off guard. These libraries, built on top of the React library and relying on hooks, faced challenges in adapting to this shift on short notice. So, using them in server components is not possible.
Libraries like Emotion and Material-UI (MUI) now have the opportunity to be used in Next.js 13, but exclusively in client components. The Next.js team has been vocal about this and is actively collaborating with these libraries during this transitional phase so they can be used in every aspect of the application.
How can Next.js transform your project's performance and efficiency?
Choosing Next.js in your upcoming project can significantly enhance your development process and deliver substantial value to your business. Next.js, built on top of React, offers a robust framework for constructing modern web applications, prioritizing performance and enhancing the developer experience. Its standout feature lies in its ability to render on the server side, improving page load speed and optimizing SEO - a crucial aspect for boosting user engagement and search engine rankings. Its seamless integration with popular frontend libraries and built-in features like routing make it an adaptable and time-efficient option for developers. By leveraging Next.js, your business can efficiently deliver high-performance web applications, leading to improved user satisfaction and a competitive edge in the digital landscape.
This is the biggest release and the biggest change to Next.js architecture since its original release. While it brings a range of new features and optimizations that can enhance development workflows and user experiences, there are certain hurdles that developers might encounter during the migration process.
Transitioning from version 12 to version 13 of Next.js can be challenging due to the limitations of the server-first approach. If the developers want to start using the app directory structure, every file must be transitioned with care and tested for potential errors. If there are errors, they may need to be rewritten to better fit with the new architecture. The old file-based system router is still supported within the pages directory, so existing applications can continue functioning accordingly. Many developers still choose to use the pages directory because it offers easier integration of libraries and provides readily available solutions for potential errors.
The newest release, Next.js 13.4, marked the app directory as stable and is going to be the cornerstone for ongoing Next.js development. Embracing this directory structure lays a solid groundwork for creating new applications with enhanced code organization and streamlined workflows.
Unifying Applications: Creating a Seamless Digital Ecosystem
As the practice of connecting various applications into a unified platform becomes increasingly common, it’s essential to understand how to integrate your existing applications. We’ll delve into the advantages and challenges associated with this process. One crucial aspect to always keep in mind is the essential role of user experience.Read
The Importance and the True Value of Product Discovery
Explore the importance of having a good product team in an IT company. Discover how Devōt’s product team adds value to clients and end-users through effective product discovery and why problem statements are the heart of the product discovery process.Read