🚀 What's Coming in Next.js 15?

The Next.js 15 Release Candidate (RC) is now available. This early version allows you to test the latest features before the upcoming stable release.


Getting Started

To use Next.jsW 15, you can run this command:

pnpx create-next-app@rc

and follow the prompts to generate a new project.

React Compiler (Experimental)

The React Compiler is a feature of React 19 that allows it to add automatic optimizations to your code and reduces the necessity of doing manual memoization with APIs such as useMemo and useCallback, improving code simplicity and maintainability.

Next.js 15 now supports for the React Compiler,

  1. Install babel-plugin-react-compiler:

    pnpm add -D babel-plugin-react-compiler
  2. Add experimental.reactCompiler option in next.config.mjs:

    next.config.mjs
    const nextConfig = {
      experimental: {
        reactCompiler: true,
      },
    }
    export default nextConfig

    Or you can configure the compiler to run in "opt-in" mode as follows:

    next.config.mjs
    const nextConfig = {
      experimental: {
        reactCompiler: {
          compilationMode: 'annotation',
        },
      },
    }
    module.exports = nextConfig

    This makes it so the compiler will only compile components and hooks annotated with a use memo directive. Please note that the annotation mode is a temporary one to aid early adopters, and that React don’t intend for the use memo directive to be used for the long term.

    export default function App() {
      'use memo'
      // ...
    }

The React Compiler is currently only possible to use in Next.js through a Babel plugin, which could result in slower build times.

Hydration error improvements

Next.js 14.1 made improvements to error messages and hydration errors, but Next.js 15 makes them even better. Hydration errors now display the source code of the error with suggestions on how to fix the problem.

For example, this was a previous hydration error message in Next.js 14.1: Hydration Error Before

Next.js 15 RC has improved this to: Hydration Error After

Caching updates

With Next.js 15, they’re changing how caching works for fetch requests, GET Route Handlers, and Client Router Cache. Before, things were cached by default, now they won’t be unless you choose to. If you want to retain the previous behavior, you can continue to opt-into caching.

fetch Requests are no longer cached by default

Next.js uses the Web Fetch API cache option to configure how a server-side fetch request interacts with the framework's persistent HTTP cache:

fetch('https://...', { cache: 'force-cache' | 'no-store' })
  • no-store - fetch a resource from a remote server on every request and do not update the cache. In Next.js 15, this is used by default if a cache option is not provided. This means fetch requests will not be cached by default.
  • force-cache - fetch a resource from the cache (if it exists) or a remote server and update the cache. In Next.js 14, this was used by default if a cache option was not provided, unless a dynamic function or dynamic config option was used.

GET Route Handlers are no longer cached by default

In Next 14, Route Handlers that used the GET HTTP method were cached by default unless they used a dynamic function or dynamic config option. In Next.js 15, GET functions are not cached by default.

You can still opt into caching using a static route config option using the following code:

export dynamic = 'force-static'

Special Route Handlers like sitemap.ts, opengraph-image.tsx, and icon.tsx, and other metadata files remain static by default unless they use dynamic functions or dynamic config options.

Client Router Cache no longer caches Page components by default

In Next.js 14.2.0, an experimental staleTimes flag was introduced to allow custom configuration of the Router Cache.

In Next.js 15, the client router cache no longer caches Page components by default, setting the default staleTime to 0. This ensures that navigating through the app always reflects the latest data for Page components. Despite this change, some behaviors remain the same:

  • Shared layout data isn’t refetched to support partial rendering.
  • Cache is used for back/forward navigation to maintain scroll position.
  • The Loading.js file is cached for 5 minutes or according to the staleTimes.static configuration.

You can opt into the previous Client Router Cache behavior by setting the following configuration:

next.config.mjs
const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
    },
  },
}
module.exports = nextConfig

Incremental adoption of Partial Prerendering (Experimental)

Next.js 14 introduced Partial Prerendering (PPR) - an optimization that combines static and dynamic rendering on the same page.

Next.js currently defaults to static rendering unless you use dynamic functions such as cookies(), headers(), and uncached data requests. These APIs opt an entire route into dynamic rendering. With PPR, you can wrap any dynamic UI in a Suspense boundary. When a new request comes in, Next.js will immediately serve a static HTML shell, then render and stream the dynamic parts in the same HTTP request.

To allow for incremental adoption, there is an experimental_ppr route config option for opting specific Layouts and Pages into PPR:

import { Suspense } from "react"
import { StaticComponent, DynamicComponent } from "@/app/ui"
export const experimental_ppr = true
export default function Page() {
  return (
     <>
	     <StaticComponent />
	     <Suspense fallback={...}>
		     <DynamicComponent />
	     </Suspense>
     </>
  )
}

To use the new option, you’ll need to set the experimental.ppr config in your next.config.mjs file to incremental:

next.config.mjs
const nextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}
module.exports = nextConfig

Once all the segments have PPR enabled, it’ll be considered safe for you to set the ppr value to true, and enable it for the entire app and all future routes.

Executing code after a response with next/after (Experimental)

When you process a user request, you probably need to perform tasks such as logging, analysis, and other external system synchronization.

Since these tasks are not directly related to the response, the user should not have to wait for them to complete. Deferring the work after responding to the user poses a challenge because serverless functions stop computation immediately after the response is closed.

after() is a new experimental API that solves this problem by allowing you to schedule work to be done after the response has finished streaming, allowing secondary tasks to run without blocking the primary response.

To use it, add experimental.after to next.config.mjs:

next.config.mjs
const nextConfig = {
  experimental: {
    after: true,
  },
}
module.exports = nextConfig

Then, import the function in Server Components, Server Actions, Route Handlers, or Middleware.

import { unstable_after as after } from 'next/server'
import { log } from '@/app/utils'
export default function Layout({ children }) {
  // Secondary task
  after(() => {
    log()
  })
  // Primary task
  return <>{children}</>
}

Changes to the create-next-app

The template design has been changed to a more minimalist design: Create Next App Design

When running create-next-app, there’s a new prompt asking if you want to enable TurboPack for development (defaults to No).:

✔ Would you like to use Turbopack for next dev? … No / Yes

To make getting started on a new project even easier, a new --empty flag has been added to the CLI. This will remove any extraneous files and styles, resulting in a minimal "hello world" page.

pnpx create-next-app@rc --empty

Optimizing bundling of external packages (Stable)

Bundling external packages can improve the cold start performance of your application. In the App Router, external packages are bundled by default, and you can opt-out specific packages using the new serverExternalPackages config option.

In the Pages Router, external packages are not bundled by default, but you can provide a list of packages to bundle using the existing transpilePackages option. With this configuration option, you need to specify each package.

Summary

  • React: Support for the React 19 RC, React Compiler (Experimental), and hydration error improvements
  • Caching: fetch requests, GET Route Handlers, and client navigations are no longer cached by default
  • Partial Prerendering (Experimental): New Layout and Page config option for incremental adoption
  • next/after (Experimental): New API to execute code after a response has finished streaming
  • create-next-app: Updated design and a new flag to enable Turbopack in local development
  • Bundling external packages (Stable): New config options for App and Pages Router