Lawrence Eagles Senior full-stack developer, writer, and instructor.

What’s new in Next.js 12

6 min read 1858

What's New in Next.js 12

Introduction

Currently, Next.js is the most loved framework in the React ecosystem, and with the release of Next 12, things are only going to get better.

Next 12 features include performance optimization, React 18 support, middleware, and more. Let’s learn about these features in the next section.

New features

There are a bunch of great features in the Next.js world. In this section, we will take a look at them in detail. Let’s dive in.

Rust compiler

One of the key features of Next 12 is performance optimization. To boost performance, Next.js replaced the Babel compiler with an extensible Rust compiler — SWC — that takes advantage of native compilation.

According to the documentation, SWC is 20 times faster than Babel on a single thread and 70 times faster on four cores.

With the new Rust compiler, Next.js promises optimized bundling and compiling with 3 times faster refresh locally and 5 times faster builds for production.

And the result of this is faster build time on production and instant feedback in local development, leading to better developer experience and faster development time.

Middlewares

One of the coolest features of Next 12 is middleware. According to the documentation, middlewares enable us to use code over configuration. This means we can run code before a request is completed, and based on the request, we can modify the response by rewriting, redirecting, adding headers, etc.

Middleware is not a new concept. Frameworks like Express.js use middleware to intercept an HTTP request and process it before it goes to the route handler. And Next.js similarly uses middleware to give us full flexibility.

To create a middleware in Next, create a _middleware.ts file in the pages directory. And inside the file, export a function called middleware as seen below:

We made a custom demo for .
No really. Click here to check it out.

import {NextFetchEvent, NextRequest, NextResponse} from 'next/server'
export function middleware (req, event) {
  // your code 
}

Also, we can scope our middleware to subdirectories if we only want it to run on a subset of pages by creating the _middleware.ts file in a child directory of the pages directory.

With middlewares, we can implement things like authentication, bot protection, redirects and rewrites, server-side analytics, logging, handling unsupported browsers, and more.

The Next middleware API extends the native Web API objects FetchEvent, Response, and Request to give us more control and flexibility when configuring a response based on an incoming request.

Below is the middleware function signature:

import type { NextFetchEvent, NextRequest } from 'next/server';

export type Middleware = (request: NextRequest, event: NextFetchEvent) => 
  Promise<Response | undefined> | Response | undefined;

The NextFetchEvent is an extension of the native FetchEvent object and adds the waituntil method, that can be used to extend the execution of the middleware after a response has been sent. Thus with the waituntil method, we can send a response and continue background work in the middleware.

The waituntil method can be useful when working with tools like Sentry to send logs of response times and errors after a response has been sent.

The NextRequest object extends the native Request object while the NextResponse object extends the native Response object.

Also, the Next.js middleware function runs before rendering takes place for every page in the pages directory, and it enables us to access and modify the incoming request.

And as a result of this, middlewares give us a more efficient way to share logic between pages, thereby keeping our code DRY and efficient.

For instance, to verify a user authentication credential on every page, the usual pattern is to import the authentication logic on every page. However, with middleware, we can just define our logic in the middleware and it would run on every page automatically.

In addition, Next middlewares work in tandem with Vercel Edge Functions. Edge Functions are serverless functions like AWS Lamda and Google Cloud Functions that are deployed like a CDN to Vercel’s Edge Network. This brings our server-side logic closer to the origin; consequently, our end user gets extremely fast execution with zero code start.

Traditionally, to gain speed web content is served from a CDN to an end user. But these are static pages and we lose dynamic content. Also, we render the content from the server using server-side rendering to get dynamic content but we lose speed.

But by deploying our middleware to the edge like a CDN, we move our server logic close to our visitor’s origin. And the result of this is we get both speed and dynamism.

React 18 support

React 18 is still in beta. However, React 18 features such as server-side Suspense and the automatic patching of updates are now available in Next.js 12 under an experimental flag as Next prepares for React 18 when it moves toward a stable release.

React 18 supports React Server Components and concurrent mode features such as server-side Suspense and SSR streaming.

Suspense is a component that allows you to wait for async data before rendering its children. Before React 18, Suspense was not available on the server, but React 18 built in support for server-side Suspense and SSR streaming, thus enabling us to server-render pages using HTTP streaming.

In Next 12, to get this feature, you can opt in to experimental concurrent features:

...
experimental: {
  concurrentFeatures: true,
}
...

Note that for the code above to work, ensure you have the beta version of React installed:

npm install [email protected] [email protected] [email protected]

And once this experimental concurrent feature is enabled, SSR will use the same Edge Runtime as middleware.

The result of this is that we can use ES 2020–based Suspense data fetching, next/dynamic, and React.lazy with Suspense boundary:

import dynamic from 'next/dynamic'
import { lazy } from 'react'

// These two methods are identical:
const Users = dynamic(() => import('./user'), { suspense: true })
const Footer = lazy(() => import('./footer'))

const Dashboard = () => {
  return (
    <div>
      <Suspense fallback={<Spinner />}>
        <Users />
      </Suspense>
      <Suspense fallback={<Spinner />}>
        <Footer />
      </Suspense>
    </div>
  )
}

export default Dashboard;

React Server Components allow you to natively render HTML from a React component on the server. It uses HTTP streaming to progressively render a web page on the server. With React Server Components, you can stream HTML from an edge function immediately and progressively show updates as your data comes in.

Also, we can fetch data directly inside a React Server Component without using getStaticProps or getServerSideProps. And when React Server Components are rendered, they require zero client-side JavaScript. This results in fewer kilobytes for the end user to download and faster page rendering.

In Next 12, you can opt in to this experiential feature by configuring your next.config.js file:

...
experimental: {
  concurrentFeatures: true,
  serverComponents: true,
}
...

To create a React Server Component in Next, we simply append .sever.js to a component’s filename. Also, to create a client component, we append .client.js to the component’s filename.

Consider the code below:

// pages/home.server.js
import React, { Suspense } from 'react'

import Users from '../components/users.server'
import Cart from '../components/cart.client'

const Home = () => {
  return (
    <div>
      <h1>React Server Component Demo</h1>
      <Suspense fallback={'Loading...'}>
        <Users />
      </Suspense>
      <Cart />
    </div>
  )
}

export default Home;

In the code above, both the Home and Users components are server components and will not be included in the client runtime.
Both Home and Users will always be server-side rendered and streamed to the client, but Cart will still be hydrated on the client-side, like normal React components.

N.B., according to the documentation, components without “server/client” extensions will be treated as a “universal component” and can be used and rendered by both sides, depending on where it is imported.

ES module support and URL import

In version 11.1, Next added experimental support for ES modules, but in Next 12 this now defaults.

ES Modules is the official ECMAScript module standard for JavaScript and it is supported by Node.js and all major browsers.

Next 12 prioritizes ES modules over CommonJS. However, it still supports importing NPM modules that use CommonJS. This helps developers to incrementally adopt ES modules without breaking changes.

Also, Next 12 experimentally supports URL imports of packages that use ES modules.
This means that a package can be imported directly from a URL and require no installation or separate build step. Also, these packages are cached locally to support offline development.
The result of this is that Next.js can process remote HTTP(S) resources exactly like local dependencies.

When Next detects a URL import, it generates a next.lock file to track the remote resource. Next supports both server and client URL import.

To use this feature, add allowed URL prefixes inside next.config.js:

module.exports = {
  experimental: {
    urlImports: ['https://cdn.skypack.dev']
  }
}

And import modules:

import confetti from 'https://cdn.skypack.dev/canvas-confetti'

Any CDN that serves ES modules will work. Examples are jsDelivr, JSPM, and unpkg.

Bot-aware ISR fallback

Incremental Static Regeneration, or ISR, enables us to incrementally update static pages after we have built our site without needing to rebuild the whole site. With ISR, static pages are generated dynamically at runtime instead of build time.

When using ISR, Next.js determines the pages to be generated by static site generation using the paths returned from the getStaticPath function. So if we return the paths to the 1,000 most viewed articles, these pages are generated at build time.

The other pages in our application can be generated on demand by using fallback:blocking or fallback:true.

fallback:blocking is preferred because when a request is made to a page that has not been generated, Next will server-render that page the first time and serve subsequent requests from the cache.

But when using fallback:true, Next.js will immediately serve a static page on the first request with a loading state. And when the data finishes loading, Next will re-render the page and cache the data.

However, with the Bot-Aware ISR fallback feature, Next.js will automatically server-render ISR pages when using fallback: true for web crawlers such as search bots. But Next will still serve a static page with a loading state to noncrawler user agents. Thus, this prevents crawlers from indexing loading states.

Smaller images using AVIF

The Next.js inbuilt image optimization API now supports AVIF images. And this enables 20 percent smaller images compared to WebP.

This feature is opt-in and can be enabled by modifying the image.format property in the next.config.js file:

module.exports = {
  images: {
    formats: ['image/avif', 'image/webp']
  }
}

Output file tracing

This has been improved by bringing Vercel’s @verce/nft package to Next.js 12. With this, Nextjs can automatically trace the files needed by each page and API route and output these trace results next to the output. This allows integrators to automatically leverage the traces Next provides.

Conclusion

Next 12 is a complete game changer and is set to alter the way we build React applications for the better. Although some of the awesome features are not stable, you can still opt in and use them in your application as we await a stable release of React 18.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Lawrence Eagles Senior full-stack developer, writer, and instructor.

Leave a Reply