The Latest Groundbreaking Changes in Next.js 14

10.01.2024Tom Trapp
Mobile Next.js JavaScript Frontend Framework Hands-on How-to

banner.png

  • Almost all Next.js projects are outdated? 💥
  • Even worse, all Next.js courses and learning materials are outdated? 🤯
  • Next.js can now do SEO? 💡
  • Next.js allows SQL Injection? 💣
  • PHP comes back to life in Next.js? ⁉️
  • Next.js is 700x faster than Webpack? 🔍

We want to answer these and other questions in this TechUp!

Some time ago, we had two TechUps about Next.js, on the one hand Full-fledged Full-Stack with Next.js and then The Next best Update to Next.js 12. In the past months, we have often heard Next.js at various conferences, we have chatted with developers at KubeCon, talked to customers and interested parties at the Kubernetes Community Days and philosophized about Next.js at the Cloud Native Basel Meetup. Since Next.js 14 was released recently, we will of course also take a look at the latest updates here.

What is Next.js?

A quick recap: Next.js is a React Framework that allows you to deploy React applications in different ways. Behind Next.js is Vercel, the company that also operates the serverless platform Vercel.

More about this here: Full-fledged Full-Stack with Next.js.

Next.js Recent Updates

A lot has happened since the last blog post about Next.js. Meanwhile, Next.js 14 is available and there have been some innovations compared to Next.js 12. We will now take a look at the most important changes.

App Directory & App Router

The basic routing and thus the layout of a project has been revised. There is now an app folder at the top level, which contains a directory-based routing structure. Not only pages, but also inheritable layouts, components and other nested routes can now be stored here.

🤔 But wait, then only the folder structure changes, right? Unfortunately, it’s not that simple…

The App Router defines a subset of types of components that define and implement the behavior of a page per page or even inheritable.

  • layout: Common user interface for a segment and its children
  • page: Unique user interface for a route and public accessibility of the routes
  • loading: Loading user interface for a segment and its “children”
  • not-found: User interface for “Not found”, for a segment and its “children”
  • error: Error user interface for a segment and its “children”
  • global-error: Global error user interface
  • route: Server-side API endpoint
  • template: Specialized re-rendered layout user interface
  • default: Default user interface for parallel routes

All these files are rendered or processed in a defined order. This structure with reserved file names also offers another advantage: Tests, CSS etc. can be stored directly in the same structure.

For example, the structure could now look like this:

img_1.png Source: https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts

ℹ️ All information about the new project structure can be found here.

At this point we want to answer two questions:

Are almost all Next.js projects outdated? Assuming that the Pages Router will disappear at some point and everything should only rely on the App Router, then yes.

It is helpful here that the migration can run in parallel and both routers are supported simultaneously. However, the documentation clearly specifies that the App Router is preferred in case of URL conflicts. A clear sign of where things are going.

Even worse, are all Next.js courses and learning materials outdated now? Here, too, the answer is yes, although numerous courses have already been updated. Fortunately, Next.js now offers its own learning platform to make it easier to get started: https://nextjs.org/learn.

Fortunately, Next.js offers us an upgrade path from the Pages to the App Router, which can be found here. This way, you can migrate step by step, and both routers can be operated in parallel. In case of URL conflicts, the App Router is simply preferred.

Forget ISR, SSR, SSG, CSR, welcome Server and Client Components

The previous terms such as SSG, SSR, ISR and CSR are no longer current. These are only used in connection with the old Pages Router. Now there are Server Components and Client Components. As the name suggests, server components are rendered on the server and client components on the client. So far so good, so SSR and CSR. But what about SSG and ISR?

SSG (Static Site Generation) is still supported, but is now called Static Exports. Using a special configuration in the next.config.js, a project can be built as a static website.

ISR (Incremental Static Regeneration) is no longer found in the documentation, for this there are new concepts such as data fetching and streaming including caching.

Layouts

Layouts are one of the most important innovations. Now layouts can be defined that can be used on all pages or only on certain pages. These layouts are automatically inherited and can be overridden or extended at a deeper level.

The layout is defined in a layout.js file:

img_3.png

A layout component can look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
export default function HomeLayout({
    children, // will be a page or nested layout
}: {
    children: React.ReactNode
}) {
    return (
    <section>
        <nav></nav>
        <div class="main"
            {children}
        </div>
    </section>
)
}

The page or another layout is then rendered in the {children} tag. This makes it very easy to inherit and override pages and designs. This approach also has great performance advantages, as the layout only needs to be loaded once and then applies to all pages. More information about layouts can be found here.

Streaming

The App Router now supports streaming of server components. Here, only the layout with a loading state is loaded for the loading time.

img_4.png

In addition, the react/Suspense components provide the possibility to divide a page into different and independent segments, which can then be loaded individually. This improves performance but also the user experience, as the independent parts of the page are loaded faster and in parallel and the user does not have to wait for the complete rendering.

Such a structure could look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { Suspense } from 'react'
import { TechUps, Decodify } from './Components'

export default function Posts() {
    return (
        <section>
            <Suspense fallback={<p>Loading techups...</p>}>
                <TechUps />
            </Suspense>
            <Suspense fallback={<p>Loading decodify...</p>}>
                <Decodify />
            </Suspense>
        </section>
    )
}

Both components would be loaded in parallel and independently of each other.

SEO

With the new Metadata API, which works file-based, metadata can be defined directly in the layout.js or page.js file. Basically, there are two ways to set metadata:

  • The Metadata Object - metadata can be set statically via this object.
    1
    2
    3
    4
    5
    6
    7
    8
    
      import { Metadata } from 'next'
    
      export const metadata: Metadata = {
          title: 'Toms cooles TechUp',
          description: 'Heute gehts mal wieder um Next.js, juhu!',
      }
    
      export default function Page() {}
    
  • The generateMetadata method - metadata can be set dynamically based on data fetchings via this function, which is executed asynchronously.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    import { Metadata, ResolvingMetadata } from 'next'
    
    type Props = {
      params: { id: string }
      searchParams: { [key: string]: string | string[] | undefined }
    }
    
    export async function generateMetadata(
      { params, searchParams }: Props,
      parent: ResolvingMetadata
    ): Promise<Metadata> {
    // read route params
    const id = params.slug
    
    // fetch data
    const blog = await fetch(`https://b-nova.com/what-is-that/${slug}`).then((res) => res.json())
    
    return {
      title: blog.title,
      openGraph: {
          images: ['/some-specific-page-image.jpg'],
       },
      }
    }
    
    export default function Page({ params, searchParams }: Props) {}
    

Additionally, there are new predefined file names that can be used to define metadata elements in the HTML header. For example, the og:image element can be defined with a file opengraph-image.(jpg|png|svg).

Conveniently, dynamic content for the sitemap can be defined via a sitemap.js file. This is then automatically integrated into the sitemap.

💡 It is important to mention here that the Metadata API only works with the App Router. The Pages Router comes up empty…

Can Next.js now do SEO? Right, with the Metadata API, static or dynamic metadata can now be defined very easily. This is then automatically integrated into the HTML header. Even at the layout level, this information can be defined and inherited.

Data Fetching

All Next.js components are now Server Components by default, which allows you to switch between SSG, SSR and ISR very easily.

In the App Router, a distinction is made between four ways to read data:

  • On the server with fetch https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-server-with-fetch

    • fetch responses are cached on the server by default (application cache)
    • In addition to caching, there is also the mechanism of revalidation
      • time-based: revalidate: 60 - data is reloaded every 60 seconds
      • on-demand: Fetch calls can be tagged, this cache data based on the tag can then be revalidated in a route handler or a server action.
  • On the server with third-party libraries such as DB calls. Here there is the possibility to define a caching in the complete function:

    1
    2
    3
    4
    5
    6
    7
    8
    
    import { cache } from 'react'
    
    export const revalidate = 3600 // revalidate the data at most every hour
    
    export const getBlog = cache(async (slug: string) => {
      const item = await db.blog.findUnique({ slug })
      return item
    })
    
  • On the client with route handler

    • A Route Handler is a function that can be called in the client and runs on the server. This allows you to define API endpoints with a route.ts for GET, POST, PUT, PATCH, HEAD, OPTIONS and DELETE in the frontend as part of the normal project structure.
    • Sensitive data and login information for other services can be hidden in the backend and do not have to be exposed to the frontend.
    • These functions can of course also be cached.
  • On the client with third-party libraries

    • Here there is the possibility to use external libraries such as SWR or React Query.

So we see that in a monorepo approach an API First approach with clear communication between frontend & backend including caching is definitely possible.

Turbopack (Alpha)

The compiler of the underlying Rust engine, which significantly improves the performance of Next.js, can currently be used with next dev --turbo. According to Next.js itself, Turbopack is 10x faster than its predecessor Vite and 700x faster than Webpack when displaying updates in next dev mode.

Next.js is 700x faster than Webpack? Right, with HMR (Hot Module Replacement) with next dev!

And yes, our tests have confirmed it: Hot Code Replacement with Turbopack is incredibly fast. 🔥

Middleware

New middleware functions can now be defined that are executed before the actual rendering of the page. These middleware functions can be used for authentication, logging, caching, etc. For example, responses can now be returned that are sent directly to the client.

Next.js defines a certain order in which rendering is done, middleware functions are in 3rd place here. The functions can either be activated and executed fixed to paths, per regex to paths or with conditional statements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import {NextResponse} from 'next/server';

export function middleware(request: Request) {
    if (!isUserAuthorized(request)) {
        return NextResponse.json({message: 'Please log in'});
    }

    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-coolest-podcast-ever', 'decodify');

    return NextResponse.next({
        request: {
            headers: requestHeaders,
        },
    });
}

Edge Runtime

A lightweight subset of Node.js APIs can be specifically enabled to run Next.js API routes directly in the edge location. An edge runtime is defined as follows:

1
2
3
4
5
6
7
export const config = {
	runtime: 'edge',
};

export default function handler(req: Request) {
	return new Response('Hello from b-nova');
}

The goal is for this edge runtime to have a very small footprint, be compatible with browsers and edge locations, and thus always be able to run very quickly and close to the user.

This edge runtime can certainly be combined well with middleware functions. Next.js would then distribute these functions in an edge network across the globe when deployed to the Vercel platform. With a self-hosted approach, you get a Single Regon Runtime according to the documentation.

Server Actions (Stable)

img.png

Source: https://www.linkedin.com/posts/jseyehunts_daily-memes-programming-life-memes-software-activity-7069634192099192833-JO5S?utm_source=share&utm_medium=member_desktop

For example, write operations can now be defined directly in the Next.js component and called securely. So it is no longer necessary to define dedicated API routes and call them via fetch on HTTP.

ℹ️ And by “in the Next.js component” we really mean inline in the HTML. 🤯

img_2.png

Source: https://www.reddit.com/r/nextjs/comments/17hgtrt/so_whats_the_deal_with_the_code_on_this_been/

At this point it is worth mentioning that Server Actions caused a lot of wind with their supposed SQL Injection. When used correctly, there is no danger of a SQL Injection security vulnerability, more about this here: https://www.youtube.com/watch?v=2Ggf45daK7k&ab_channel=Joshtriedcoding.

Is PHP coming back to life in Next.js 14? At first glance, it looks like PHP has come back to life. But don’t worry, it’s not PHP, but a JavaScript function that runs on the server. This function can then, for example, write data to a database, create a file on the server or perform other write operations. Surely, especially as a backend or middleware developer, it can be noted here that this is not the cleanest solution. But for frontend developers, this is a very simple and fast way to write data.

Does Next.js now allow SQL Injection? No, a so-called TemplateStringsArray is used to prevent SQL Injection. This TemplateStringsArray is then converted into a Prepared Statement and thus prevents SQL Injection.

Pages vs. App Router

The question “Does it make sense to migrate to the new App Router?” can be answered very quickly and easily: Yes!

The App Router is significantly better than the Pages Router in terms of caching, partial rendering and performance, for example, and thus also ensures a better user and developer experience. Surely a migration is not easy, but it is worth it. This migration should be well prepared. It is essential to check whether all dependencies, plugins, etc. are already compatible with the App Router.

Conclusion

Next.js clearly stands out from other frameworks such as Svelte, Angular or Vue. The latest developments show that Next.js is increasingly developing into a full-stack framework that is very easy and fast to use. The App Router makes it even easier and better to define client and server functionalities centrally but deploy and use them separately from each other.

From my personal point of view, Next.js is increasingly developing into a jack of all trades. From my point of view, the go-to framework if you want to build and operate a full-stack application in the Node.js ecosystem.

More to come, stay tuned! 🔥

This techup has been translated automatically by Gemini

Tom Trapp

Tom Trapp – Problemlöser, Innovator, Sportler. Am liebsten feilt Tom den ganzen Tag an der moderner Software und legt viel Wert auf objektiv sauberen, leanen Code.