What’s New in Next.js 13: A Look at the Latest Features
Next.js, a full-stack JavaScript framework build on top of React, is one of the most popular among developers. On October 26, 2022, Vercel, the company behind Next.js, announced Next.js 13. Next.js 13 introduced many new features and bought significant changes to the previous version. Many of its features are still in beta.
In this article, we’ll see what new stuff Next.js 13 has to offer and how to adopt all its new features incrementally in your prior Next.js projects. Don’t worry if you’ve never used Next.js before; after reading this article, you’ll have enough knowledge to start your first Next.js project confidently.
What is Next.js?
Next.js is a full-stack JavaScript framework built on top of React that supports features like Server Side Rendering (SSR), Static Site Generation (SSG), Incremental Static Regeneration (ISG), and much more cool stuff, which is otherwise quite challenging and complex if implemented from scratch. These features were highly beneficial to SEO. Instead of sending an empty HTML file as a traditional React.js application does, Next.js sends pre-rendered HTML so search engine crawlers can index the pages and improve page ranking.
A recap of Next.js 12
The former version of Next.js, i.e., Next.js 12, was already a huge game-changer. It introduced a rust-based compiler that improved the builds five times faster. Other features included middleware, concurrent mode, lazy loading, experimental support for React Server Components, URL imports, etc. These features were very handy. Now, with the introduction of Next.js 13, things are going to change a lot. Let’s look at what Next.js 13 offers and how it can be adopted in existing Next.js 12 projects.
What’s new in Next.js 13?
The Next.js team introduced many new features in the latest version, 13. These features also include some significant changes that are currently in beta. Next.js 13 supports incremental adoption of these new features, which means that existing projects that use Next.js 12 can gradually shift to Next.js 13 without breaking anything. The following is a list of the latest changes in Next.js 13.
- The new
/app
directory (Beta) - New rust-based module bundler: Turbopack (Alpha)
- The new
@next/font
(Beta) - The new
@next/image
component. - The improved
@next/link
component.
Out of the above changes, the most notable change was the introduction of the new /app
directory. The new /app
directory replaces the conventional /pages
directory and brings a ton of exciting features with it. Let us take a closer look at some of these new changes.
The new /app directory (Beta)
Next.js 13 introduces a new /app
directory to define all the routes and pages for the application. It has many new modern features, such as data streaming, nested layouts, better error handling, and much more. Let us break down the new /app
directory step by step.
How is it different from the Pages directory?
The new /app
directory is similar to the /pages
directory in that it defines application routes. What has changed is how we define the routes. The /app
directory still uses file-system routing, but it is now all directory-based rather than file-based, along with a few file naming conventions.
Defining routes using the new app directory
Each directory inside the new /app
directory is a route. The page.jsx
file inside the page directory exports a React component for that page. Since it’s a directory, we can place the necessary components inside it instead of creating a separate folder for keeping all the components.
└─ app
├─ about
│ └─ page.jsx => /about
├─ contact
│ └─ page.jsx => /contact
├─ dashboard
│ └─ page.jsx => /dashboard
└─ page.js => /
Code language: plaintext (plaintext)
Nesting of routes is accomplished by placing additional directories within the base page directory.
└─ app
└─ auth
├─ login
│ └─ page.jsx => /auth/login
└─ register
└─ page.jsx => /auth/register
Code language: plaintext (plaintext)
To create dynamic routes, we’ll put the dynamic parameter within square brackets []
in the directory name, similar to what we did in Next.js 12.
└─ blogs
├─ page.jsx
└─ [id]
└─ page.jsx
Code language: plaintext (plaintext)
Here is a sample of how the directory structure for our routes will look using the new /app
directory.
└─ app
├─ about
│ └─ page.jsx
├─ auth
│ ├─ login
│ │ └─ page.jsx
│ └─ register
│ └─ page.jsx
├─ blogs
| ├─ page.jsx
| └─ [id]
| └─ page.jsx
├─ contact
│ └─ page.jsx
└─ dashboard
└─ page.jsx
Code language: plaintext (plaintext)
The new layout system
Apart from the changed directory structure, the new /app
directory also features a new layout system that is more simple. Inside a page directory, we can now create a layout.jsx
file that defines the layout for that page and its children. The current page and subpages will use a common layout we define using this file.
└─ dashboard
├─ layout.jsx
└─ page.jsx
Code language: plaintext (plaintext)
export default function DashboardLayout({ children }) {
return <Sidebar>{children}</Sidebar>;
}
Code language: JavaScript (javascript)
The subpages may also have their own layout.jsx
, resulting in nested layouts. The layout.jsx
files in subpages (or subdirectories) will be applied only to their children.
└─ dashboard
├─ layout.jsx
├─ page.jsx
├─ sales
│ ├─ layout.jsx
│ └─ page.jsx
└─ users
├─ layout.jsx
└─ page.jsx
Code language: plaintext (plaintext)
One of the fantastic features of these routes is that we can perform data fetching directly within the routes, eliminating the need to refresh or fetch data in the child routes. Another cool thing is that these layouts can persist states across page navigation, preventing a complete re-render that improves the application’s performance.
Loading states and streaming
Loading and streaming is another excellent feature of the new /app
directory. We can display a loading component while the data is fetched by creating a loading.jsx
file, which exports a React component to show the loading state.
export default function BlogsLoading() {
return <div>Loading...</div>;
}
Code language: JavaScript (javascript)
The cool thing about this method is that data units are incrementally streamed to the pages by Next.js, eliminating the need to wait for the entire page to load.
Error fallbacks
There is now a more straightforward way to show a fallback UI in case of a component error. You can create an error.jsx
file under any page directory and export a component to be displayed when an error occurs. The fallback UI will be displayed whenever there is any unhandled error inside the component.
export default function DashboardError() {
return <div>Error</div>;
}
Code language: JavaScript (javascript)
Server and Client components
With the introduction of the new /app
directory, we also got support for the React Server Components and Client components. It’s part of the new /app
directory, but we’ll discuss it in depth in the next section.
Server components
Every component you create inside the new /app
directory is a React Server Component by default. For those who don’t know, React Server Components are the new way of doing Server Side Rendering (SSR), with almost no JavaScript sent to the client.
How to use Server Components?
We can write server-side code directly inside React Functional Components (RFCs) using React Server Component. We can mark the server component async
and run all promise-based syntax here. That means we don’t need to use getStaticProps
and getServerSideProps
to fetch data on the server; we can directly fetch the data inside our component using async/await
.
export default async function Blogs() {
const response = await fetch('/api/blogs');
const blogs = await response.json();
return (
<div>
{blogs.map((blog) => (
<div key={blog.id}>
<img src={blog.image} />
<h2>{blog.title}</h2>
</div>
))}
</div>
);
}
Code language: JavaScript (javascript)
How to change the caching behavior?
You might wonder how we will change the caching behavior if we’re not using getStaticProps
and getServerSideProps
to fetch data. Well, all the pages will behave as static by default, i.e., like getStaticProps
.
If we’re using the fetch
API for data fetching, we can pass a cache: no-store
option to the fetch
API call. This will fetch the data from the server for every request. It’s equivalent to the getServerSideProps
.
const response = await fetch('/api/blogs', {
cache: 'no-store',
});
Code language: JavaScript (javascript)
But if we use some SDK instead of the fetch
API, we can export some config variables from the page.jsx
file to change the behavior. For example, we can export a constant called revalidate
for Incremental Static Regeneration (ISG).
export const revalidate = 60;
export default async function Blogs() {
...
}
Code language: JavaScript (javascript)
Why use Server Components?
We can significantly enhance our application’s performance using Server Components, as there is little JavaScript shipped on the initial page load. The additional JavaScript is only loaded if the page contains client-side interactivity, such as click events, intersection observers, etc. This interactivity is added using Client components.
Client components
We use client components for client-side interactivity, such as event listeners, timeouts, intervals, etc. Client components, unlike server components, are rendered on the client rather than the server.
How to use Client Components?
To declare a component as a Client component, we’ll write 'use client'
flag at the top of the file. It tells Next.js not to render the component on the server but instead on the client.
'use client';
export default function Contact() {
// Write client-side logic here.
// Here you can use React hooks such as useState, useEffect, etc.
// Here you can create forms and attach event handlers to them.
}
Code language: JavaScript (javascript)
Using Client components inside Server components
A page might not have static content only. Instead, it will consist of some form of user interaction. Therefore, we might want to combine the server and client components. Inside a Server component, we can import a Client component.
export default async function Blogs() {
const response = await fetch('/api/blogs', {
cache: 'no-store',
});
const blogs = await response.json();
return (
<div>
<div id='blogs'>
{blogs.map((blog) => (
<div key={blog.id}>
<img src={blog.image} />
<h2>{blog.title}</h2>
</div>
))}
</div>
{/* Client component */}
<AddBlogForm />
</div>
);
}
Code language: JavaScript (javascript)
One thing to remember here is that any props passed from the Server component to the Client component must be serialized. We could use a JSON string for this.
Another important thing is that we cannot directly import a Server component inside the Client component. But we may receive them as a children
prop and render them.
'use client';
export default function ClientComponent({ children }) {
// Client-side logic
return (
<div>
. . .
{/* Can be a server component */}
{children}
</div>
);
}
Code language: JavaScript (javascript)
Why use Client Components?
A Server component can contain only server-side logic, i.e., browser APIs such as event listeners won’t work. Therefore, we use a client component to bring client-side interactivity.
Why not combine the Server and the Client component and write client-side logic directly inside the Server component like we used to do in Next.js 12?
The answer to this is the separation of client and server logic. The server logic may involve database access, secret keys, etc. Therefore to prevent any data comprise, there must be a separation between the Server and the Client components. Thus there is a network boundary between the Server and the client components. Because of the same reason, data serialization is required if we want to transfer data from the Server to the Client component.
Turbopack
Apart from the above new features, Next.js 13 includes a new module bundler called Turbopack. It is written in the rust programming language and thus has a much faster performance than its competitors like Vite and Webpack. It is claimed to be roughly ten times faster than Vite.
Turbopack is currently in Alpha. Therefore many features are not yet supported. If you still want to try it, you must pass the --turbo
flag while starting a development server. To do this, simply edit the scripts in the package.json
file.
"scripts": {
"dev": "next dev --turbo",
},
Code language: JSON / JSON with Comments (json)
The new Image component
Next.js 13 brings in the new next/image
component, which mitigates the issues like layout shifts and improves image optimizations for better performance. It supports browser-native lazy loading, so there is no need for hydration. As a result, it ships less JavaScript and improves performance.
The new Font system
Another cool feature that Next.js 13 has is the new font system @next/font
. Using the new font system, we can conveniently use any Google font or Custom font without worrying about any CSS, layout shifts, external network requests, self-hosting, etc. Next.js does everything behind the scenes.
To use any Google font, simply import it from @next/font/google
, create an instance and use it inside the elements by passing it inside the className
prop.
import { Lato } from '@next/font/google';
const lato = new Lato();
export default function Contact() {
return (
<div className={lato.className}>
<h1>Contact</h1>
</div>
);
}
Code language: JavaScript (javascript)
Because the font files are downloaded and self-hosted during the build process, no separate network requests for the font are sent when the user requests the page.
The new Link component
Next.js 13 has a more convenient way of linking different pages with the new next/link
component. In Next.js 12, you had to include an <a>
element, as a child, inside the Link
component.
<Link href='/dashboard'>
<a>Dashboard</a>
</Link>
Code language: JavaScript (javascript)
It is pretty unintuitive and inconvenient. With Next.js 13, there is no need for a separate <a>
element. The new Link
component renders an <a>
element by default.
<Link href='/dashboard'>
Dashboard
</Link>
Code language: JavaScript (javascript)
Summary
The Next.js team introduced many new features in the latest version, 13. Many are stable, but some are still in beta. In addition to improvements over the previous version, it introduced a few major changes, such as the new /app
directory. The new /app
directory adds many much-needed features, such as support for React Server Components, a new layout system, and so on, but it also has some drawbacks. Because it is still in beta, a few things might change in future releases. Next.js 13 supports incremental adoption of these new features, which means that existing projects that use Next.js 12 can gradually shift to Next.js 13 without breaking anything.
For more information about Next.js 13, please visit the beta documentation.
This article hopefully provided you with some new information. Share it with your friends if you enjoy it. Also, please provide your feedback in the comments section.
Thank you so much for reading 😄
Sharing is caring
Did you like what Varun Tiwari wrote? Thank them for their work by sharing it on social media.
No comments so far
Curious about this topic? Continue your journey with these coding courses: