Skip to content
Back to Blog

Tuesday, December 10th 2024

Next.js 15.1

Posted by

Next.js 15.1 brings core upgrades, new APIs, and improvements to the developer experience. Key updates include:

Upgrade today, or get started with:

Terminal
# Use the automated upgrade CLI
npx @next/codemod@canary upgrade latest
 
# ...or upgrade manually
npm install next@latest react@latest react-dom@latest
 
# ...or start a new project
npx create-next-app@latest

React 19 (stable)

Next.js 15.1 now fully supports React 19:

  • For the Pages Router: you can now use React 19 stable without needing the Release Candidate or Canary releases, alongside continued support for React 18.
  • For the App Router: we will continue to provide React Canary releases built-in. These include all the stable React 19 changes, as well as newer features being validated in frameworks, prior to a new React release.

Since the Next.js 15 release, a significant addition to React 19 was “sibling pre-warming”.

For a comprehensive overview of React 19’s updates, please refer to the official React 19 blog post.

Improved Error Debugging

We’ve made improvements to error debugging in Next.js, ensuring you can quickly locate the source of issues, whether they appear in the terminal, browser, or attached debuggers. These enhancements apply to both Webpack and Turbopack (now stable with Next.js 15).

Source Maps Enhancements

Errors are now easier to trace back to their origin through the improved use of source maps. We’ve implemented the ignoreList property of source maps, which allows Next.js to hide stack frames for external dependencies, making your application code the primary focus.

For slightly more accurate source mapping of method names, we suggest adopting Turbopack (now stable), which has improved handling and detection of source maps over Webpack.

For library authors: We recommend populating the ignoreList property in sourcemaps when publishing your libraries, especially if they are configured as external (e.g. in the serverExternalPackages config).

Collapsed Stack Frames

We’ve improved the logic for collapsing stack frames to highlight the most relevant parts of your code.

  • In the browser and error overlay: Stack frames from third-party dependencies are hidden by default, focusing on your application code. You can reveal the hidden frames by clicking “Show ignored frames” in the devtools or the overlay.
  • In the terminal: Third-party dependency frames are also collapsed by default, and error formatting now aligns with the browser output for a consistent debugging experience. Errors are replayed in the browser to ensure you don’t miss important information during development if you need the entire stack trace.

Enhanced Profiling

Ignored stack frames are also recognized by built-in browser profilers. This makes profiling your application easier, allowing you to pinpoint slow functions in your code without noise from external libraries.

Improved with the Edge Runtime

When using the Edge runtime, errors are now displayed consistently across development environments, ensuring seamless debugging. Previously, logged errors would only include the message and not the stack.

Before and after

Terminal Before:

Terminal
app/page.tsx (6:11) @ eval
Error: boom
    at eval (./app/page.tsx:12:15)
    at Page (./app/page.tsx:11:74)
    at AsyncLocalStorage.run (node:async_hooks:346:14)
    at stringify (<anonymous>)
    at AsyncLocalStorage.run (node:async_hooks:346:14)
    at AsyncResource.runInAsyncScope (node:async_hooks:206:9)
digest: "380744807"
  4 | export default function Page() {
  5 |   const throwError = myCallback(() => {
> 6 |     throw new Error('boom')
    |           ^
  7 |   }, [])
  8 |
  9 |   throwError()
 GET / 500 in 2354ms

Terminal After:

Terminal
Error: boom
    at eval (app/page.tsx:6:10)
    at Page (app/page.tsx:5:32)
  4 | export default function Page() {
  5 |   const throwError = myCallback(() => {
> 6 |     throw new Error('boom')
    |          ^
  7 |   }, [])
  8 |
  9 |   throwError() {
  digest: '225828171'
}

Error Overlay Before

An example of the Next.js error overlay before version 15.1
An example of the Next.js error overlay before version 15.1

Error Overlay After

An example of the Next.js error overlay after version 15.1
An example of the Next.js error overlay after version 15.1

These improvements make errors clearer and more intuitive, allowing you to focus your time building your application rather than debugging.

We’re also thrilled to announce the introduction of a redesigned UI for the error overlay, coming in upcoming releases.

after (stable)

The after() API is now stable following its introduction in the first Next.js 15 RC.

after() provides a way to perform tasks such as logging, analytics, and other system synchronization after the response has finished streaming to the user, without blocking the primary response.

Key changes

Since its introduction, we’ve stabilized after() and addressed feedback including:

  • Improved support for self-hosted Next.js servers.
  • Bug fixes for scenarios where after() interacted with other Next.js features.
  • Enhanced extensibility, enabling other platforms to inject their own waitUntil() primitives to power after().
  • Support for runtime APIs such as cookies() and headers() in Server Actions and Route Handlers.
app/layout.js
import { after } from 'next/server';
import { log } from '@/app/utils';
 
export default function Layout({ children }) {
  // Secondary task
  after(() => {
    log();
  });
 
  // Primary task
  return <>{children}</>;
}

Read more about the after API and how to leverage it in the documentation.

forbidden and unauthorized (experimental)

Next.js 15.1 includes two experimental APIs, forbidden() and unauthorized(), based on community feedback.

We’d love your feedback — please try it in your development environments and share your thoughts in this discussion thread.

Overview

If you’re familiar with the App Router, you’ve likely used notFound() to trigger 404 behavior alongside the customizable not-found.tsx file. With version 15.1, we’re extending this approach to authorization errors:

forbidden() triggers a 403 error with customizable UI via forbidden.tsx.

unauthorized() triggers a 401 error with customizable UI via unauthorized.tsx.

Good to know: As with notFound() errors, the status code will be 200 if the error is triggered after initial response headers have been sent. Learn more.

Enabling the feature

As this feature is still experimental, you’ll need to enable it in your next.config.ts file:

next.config.ts
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  experimental: {
    authInterrupts: true,
  },
};
 
export default nextConfig;

Note: next.config.ts support was introduced in Next.js 15. Learn more.

Using forbidden() and unauthorized()

You can use forbidden() and unauthorized() in Server Actions, Server Components, Client Components, or Route Handlers. Here’s an example:

import { verifySession } from '@/app/lib/dal';
import { forbidden } from 'next/navigation';
 
export default async function AdminPage() {
  const session = await verifySession();
 
  // Check if the user has the 'admin' role
  if (session.role !== 'admin') {
    forbidden();
  }
 
  // Render the admin page for authorized users
  return <h1>Admin Page</h1>;
}

Creating custom error pages

To customize the error pages, create the following files:

app/forbidden.tsx
import Link from 'next/link';
 
export default function Forbidden() {
  return (
    <div>
      <h2>Forbidden</h2>
      <p>You are not authorized to access this resource.</p>
      <Link href="/">Return Home</Link>
    </div>
  );
}
app/unauthorized.tsx
import Link from 'next/link';
 
export default function Unauthorized() {
  return (
    <div>
      <h2>Unauthorized</h2>
      <p>Please log in to access this page.</p>
      <Link href="/login">Go to Login</Link>
    </div>
  );
}

We'd like to thank Clerk for proposing this feature through a PR and assisting us in prototyping the API. Before we stabilize this feature in 15.2, we're planning on adding more capabilities and improvements to the APIs to support a wider range of use cases.

Read the documentation for the unauthorized and forbidden APIs for more details.

Other Changes

  • [Feature] Use ESLint 9 in create-next-app (PR)
  • [Feature] Increase max cache tags to 128 (PR)
  • [Feature] Add an option to disable experimental CssChunkingPlugin (PR)
  • [Feature] Add experimental CSS inlining support (PR)
  • [Improvement] Silence Sass legacy-js-api warning (PR)
  • [Improvement] Fix unhandled rejection when using rewrites (PR)
  • [Improvement] Ensure parent process exits when webpack worker fails (PR)
  • [Improvement] Fixed route interception on a catch-all route (PR)
  • [Improvement] Fixed response cloning issue in request deduping (PR)
  • [Improvement] Fixed Server Action redirects between multiple root layouts (PR)
  • [Improvement] Support providing MDX plugins as strings for Turbopack compatibility (PR)

Contributors

Next.js is the result of the combined work of over 3,000 individual developers. This release was brought to you by:

Huge thanks to @sokra, @molebox, @delbaoliveira, @eps1lon, @wbinnssmith, @JamBalaya56562, @hyungjikim, @adrian-faustino, @mottox2, @lubieowoce, @bgw, @mknichel, @wyattjoh, @huozhi, @kdy1, @mischnic, @ijjk, @icyJoseph, @acdlite, @unstubbable, @gaojude, @devjiwonchoi, @cena-ko, @lforst, @devpla, @samcx, @styfle, @ztanner, @Marukome0743, @timneutkens, @JeremieDoctrine, @ductnn, @karlhorky, @reynaldichernando, @chogyejin, @y-yagi, @philparzer, @alfawal, @Rhynden, @arlyon, @MJez29, @Goodosky, @themattmayfield, @tobySolutions, @kevinmitch14, @leerob, @emmanuelgautier, @mrhrifat, @lid0a, @boar-is, @nisabmohd, @PapatMayuri, @ovogmap, @Reflex2468, @LioRael, @betterthanhajin, @HerringtonDarkholme, @bpb54321, @ahmoin, @Kikobeats, @abdelrahmanAbouelkheir, @lumirlumir, @yeeed711, @petter, and @suu3 for helping!