What's New in Next.js 13.4?
Introduction
The latest updates to Next.js are a game-changer. If you have built a new app using Next.js, you will understand what I mean. In this article, I will also be discussing some issues with the pages router. As you may know, we can create multiple pages using the pages directory. This may seem overwhelming at first, but I will be breaking it down further.
A look at the topics to discuss in detail:
- The AppRouter; this is the main part,
- Then there are the Server Actions, and
- The Turbopack.
The last two are in the alpha version. This article will mostly focus on the AppRouter, in which we will be talking about
- React Server Components
- Data Fetching
- Routes and Layouts
Let us first try to understand server-side rendering.
What is Server-side Rendering?
Suppose you are creating a normal React app. If you put local host 3000, then what you get is the JavaScript bundle from the server. You also get an initial as an empty HTML file. So the JavaScript bundle that you get gets loaded, it executes its task, it loads the doc in your browser, and you can see the whole React app.
But this is very time-consuming and not very performant as the whole execution of the task that we wrote for our components is getting executed on the browser.
Next.js has introduced server-side rendering. With server-side rendering, suppose I create a component app, and I put H1 Hello World. What it will do is it will create an HTML of that Hello World in the server, and it will send it back to my browser. It will not be sending me the entire JavaScript. It would be sending me an HTML on my webpage.
This would increase my SEO, and I can see the HTML on my browser. Also, it sends some little JavaScript because suppose I create a button and I put on click listener onto it. The on-click listener cannot be executed on the server because it is a browser event. So what I get from the server is the normal button element. But the JavaScript that I get from the server-side rendering (it is a little bit JavaScript) executes and puts my on-click event onto that button, and then I can interact with that whole page.
This sounds perfect, but there are some flaws in server-side rendering that we will find out later in this article. One flaw that I can point out is that suppose you have a component that you want to show a list of posts in your app. You do not have any interactivity on that post. You show a to-do app where you can see all your to-dos. There is no need to send the JavaScript bundle to the browser to get it executed. The drawback here is that for all the components, we are sending the JavaScript bundle also along with the HTML.
If there was a way to differentiate server components, if I could differentiate that these components should only be rendered on the server and no JavaScript should be passed from the server to execute them, things would have been easier. If I could have some components that I know will be executed in the browser because they have some event listeners added to it, I would need to access the local storage that is only the browser events.
React Server Components
We need to differentiate between two components, the server-side components, and the client-side components. Server-side components should not have any JavaScript to be executed on the browser. Because for them, we only need to get the HTML that we can review. For client-side components, we need JavaScript because we need to click on a button, hover on it, and so on. This was the main idea behind introducing React Server Components. But there are many other reasons why React Server Components were introduced in Next.js 13.4.
Why React Server Components?
Have you heard of the waterfall model?
Let us look at this code:
Suppose I have three components – a parent component, a child component, and inside that, I have a child component. This is a three-level parent-and-child relationship. There will be three components, first, second, and third. I want my second component to be rendered only when the first component has been rendered. And I want my leaf component to be rendered only when the second component has been rendered.
In my parent component, I will execute my async action. In useEffect
, I will call my API. Then if loading is true, it will show loading. And if loading is not false, it will show the child component. Similarly, the child will also do this. It will get its own data; if loading is true, it will show loading is true. And if loading is not false, it will show its child component. We can see that the third component, the child component, is dependent on the second component, and the second component is dependent on the parent component. Only when the parent component is rendered the other components will be rendered. This is called a waterfall model.
What can we do to improve this? What if I could tell all my parent-child components that you can request for data only in your component? You do not need to depend on the other components. You call your API in your function. This can be done through React Server Components. In React Server Components, we can call our API. We do not need any hooks like useEffect. We can call our API inside its server component. So my parent component can get its data in its component. The child will get its data. They will not be dependent on the other components. This is another problem that React Server Component has solved.
Render Only Required Components on Client
As we can see in the image above, we have the Navbar, Search, Sidebar, and Main element button. You can also see that different colors denote them. The server and client components have different colors. We do not want all our components to be executed by the JavaScript bundle. Suppose in my Navbar, I will show only an image and some text. It does not need any JavaScript to be executed because it can be rendered on the server. But for the Search button, I need to input my text there, and I need the browser interactivity. We have divided these components into server and client. The client components will also be rendered on the server.
They will be getting a JavaScript bundle that will be executed on the browser, and we will be able to see the interaction. But for server-side components, they will not be executed on the client but on the server itself. This has reduced the JavaScript bundle to very less. Because the server components have a JavaScript bundle of size zero, they do not need any JavaScript. Only client components need JavaScript to be executed. This is a game-changer for server-side rendering. Previously, a simple “Hello, World” app would require sending over 70 kb of data just to have a div. Now, if we do not need interactivity (I just want to send a JavaScript bundle to my browser), we only get HTML and CSS. No JS whatsoever. This will improve the performance of the app.
The advantages of this are many:
- Massive savings in bundle size
- Initial page load will be faster
- SEO will be improved (better than Next 12)
- Components are server by default
If you create components in Next.js 13.4, they will all be servers by default. If you want to make it into a client, for example, if you want to add a button in your app, then you can use client directive. You can place it at the top of your file. Then the JavaScript will be sent to the browser. And you can use client components. But for the server side, the components are by default server. This is in the AppRouter that has been released in version 13.4. In the previous version (v12), there were some problems, like the pages directory where we used to keep all our files to load our pages. In Next.js 13.4, we have an app directory and we have layouts. You can explore them in the Next.js docs.
Routing
Next.js has introduced a new routing. It is an entirely different architecture because it enables React Server Components. You must have heard of React Streaming or React Suspense in the latest React 18 version. The new routing also enables us to use these. Below, you can see a dashboard layout.
Every page that you can create in Next.js will have a dashboard layout. Root layout is the layout for all your pages, and dashboard layout will be the layout for your individual page. So if I create a dashboard, it will have a dashboard layout. If I create an admin page, then it can have an admin layout. We can separate every page with its own layout. And there will be one root layout, which will be sitting on the top of the app. And we can do anything here. We can put HTML body and HTML tags here as well. The features of routing can be broken down as below:
- Route Groups: You can separate your routes from each other. You can have different layouts in them.
- Dynamic Routes: In V12, we had ..slug file. This is almost similar to that.
- Parallel Routes: if I have a layout, a page where I want to show two different pages, for example, an analytics page and a dashboard page, I can combine those two pages into one single page. These two pages would have their own loading states, error states, etc.
- Intercepting Routes: On Instagram, you can see a list of feeds on your first page. If you click on a post, you go back to another page. Intercepting routes ensure that if I press a photo of someone, a new modal will appear, and that person's page can appear. So I can load a different page altogether into the existing page that I have.
- Middlewares: You can run your code before the request is over. You can redirect it and change the headers of your requests.
Simplified Data Fetching
No more getStaticProps or getServerSideProps. In v12, we used to write different functions, such as getStaticProps
and getServiceSideProps
, to get all the data. Then we used to pass that to our component, and it was a lot of heavy stuff that we used to do. Now, things have changed. We can now write our own async functions inside our component itself.
I Tweeted last year about one of the problems with Next.js that I faced.
Suppose I have different pages for showing my data- like the homepage, dashboard page and other pages. I used to have a header on every page. In Next.js 12, we used to call our getStaticProps
and getServiceSideProps
functions. We used to get our data and pass that to the header component every time because the header was the persistent component. It sits on top of every page.
We wrote our async functions on every page to show data in the header component. And then we used to pass that to the header. But that meant writing a lot of code for us. And the bundle size would also increase. The new layouts RFC solves the problem of passing my data to a header component every time. Through React Server Components, I do not need every page to send my data to the header component. I can call my async function inside only the header component. And that would execute every time.
Async and Await in Server Components
I tried an experiment a few days back using the Prisma ORM to get data. I created a Users table in Prisma. And then, I got that data inside the component itself.
In the image above, you can see that I just called new PrismaClient
. I initiated the client. I wrote users = await prisma. user. findMany()
. This is an SQL query. It is running an SQL query inside the React component. Instead of writing multiple APIs, we need two lines to get our data. We can use async and await in our server components.
Combined SSG, SSR, and ISR
In Next.js v12, there were three different methods of building your app. The first one was static site generation, where I could build all my pages on my build time. The second was server-side rendering; whenever I call my browser, I get an HTML from it, and all the data is executed and sent back to me. The third was ISR, or incremental static regeneration, where I could put a validation of 10 seconds. The server-side components enable us to pass using JavaScript fetch in the latest version. If I put normal fetch, it is cached until manually invalidated, as shown in the image above. If I reload my page every time, it is similar to SSG or static site generation because data will not return every time. And the other is no- store. If I put cache as no-store, it will be fetched every time.
It is similar to SSR. Anytime I reload, I will get all my data again and again. The final one is similar to ISR cached with a lifetime of 10 seconds. The data will not be executed on the server whenever I reload my app. It will be executed after every 10 seconds. This is a definite advantage as we used to write whole functions inside our React components in Next.js 12, but now that has changed. Also, in Next.js 12, we could not combine these two. If I wanted some async function to be prefetched on every request or cached with a lifetime of 10 seconds, I did not have that option in Next.js 12. Now in v13, I can have three all three running simultaneously.
Server Actions
Server Actions is still in alpha. This enables us to mutate data on the server, calling functions directly without creating an in-between API layer. Earlier, when I wanted to create a user on my database, I had to create an API, send that form data to my API, then it would pass that data and send it to the backend, and then the backend would do all those stuff of inserting in database. But that is not the case anymore. This has:
- Reduced client-side JavaScript
- Enhanced user experience with features like optimistic updates and progressive enhancement (experimental for now). I can make my changes on the client, and I can tell my client that this has been done, but I send my request to the server. The server will tell after some time whether it was a success or failure. Until then, I can tell my client that this was a success. So after the request comes, I can optimistically check whether the response was a success.
- Enhanced developer experience by simplifying offloading data updates to the server, which would’ve required an API in the past. As seen below, I have created a
createUser
function inside my component and added a directive‘use server’
. I initialize the Prisma Client and I create a User. I get the form data from the JSX I have written and can directly put my data on the database without needing the API layers.
Automatic Code Splitting
In server-side components, as you know, we can write async await inside our component itself. Based on a boolean variable, I can show the app I want to show. As you can see above, if I am logged in, I show a different app and landing page than if I am not logged in. This has greatly decreased the bundle of JavaScript.
Turbopack
This incremental bundler (this is in beta) is optimized for JavaScript and TypeScript, written in Rust. The following are its features:
- 700x faster updates than Webpack
- 10x faster updates than Vide
- 4x faster cold starts than Webpack
- Fast and flexible developer experience for apps of any size
Summing Up
I was trying to experiment a few days ago with Prisma and Drizzle. Drizzle is a new ORM with type safety. In the video shared below, you can see how I created a railway app that allows me to add users to the database.
In conclusion, the release of Next.js 13.4 brings exciting new features and improvements that enhance the development experience and performance of Next.js applications. It will be interesting to find out how all these new features will work together for building better, upgraded apps.
For the complete code and talk, watch video below 🔽
Book a Discovery Call.