What’s New in Next.js 13: A Look at the Latest Features

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.

  1. The new /app directory (Beta)
  2. New rust-based module bundler: Turbopack (Alpha)
  3. The new @next/font (Beta)
  4. The new @next/image component.
  5. 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/registerCode 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.jsxCode 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.jsxCode 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.jsxCode 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.jsxCode 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.

Loading and streaming
Loading and streaming

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)
Error handling
Error handling

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.

0/10000

No comments so far