How to upgrade to version 16

Upgrade your Next.js Application from Version 15 to 16.

Run the latest upgrade CLI from your app's directory:

Terminal
npx next@canary upgrade --agent=latest

The CLI prepares the Next.js 16 migration guides and codemod command for the agent, then asks it to apply the changes and verify the app. See Upgrade with a coding agent for details.

Or upgrade manually

Upgrading manually follows a few steps, in order:

  1. Set up AI agent docs if you use a coding agent now or want future agent work grounded in version-matched docs.
  2. Run the upgrade codemod.
  3. If you prefer not to run the codemod, install packages manually.
  4. Continue through the breaking changes below, run the relevant checks, and fix any remaining issues.

Set up AI agent docs

Before upgrading, point AGENTS.md at version-matched Next.js docs so a coding agent can help with the remaining steps:

Terminal
npx @next/codemod@canary agents-md

After upgrading, check that AGENTS.md points at the docs bundled with the installed version in node_modules/next/dist/docs/. On Next.js 16.2 and later, next dev keeps this block up to date when it detects a coding agent. If the setup downloaded docs into .next-docs/, remove that directory once nothing references it. See Version-matched documentation for the managed block and setup details.

Using the codemod

To update to Next.js version 16, you can use the upgrade codemod:

pnpm dlx @next/codemod@canary upgrade latest
npx @next/codemod@canary upgrade latest
yarn dlx @next/codemod@canary upgrade latest
bunx @next/codemod@canary upgrade latest

The codemod is able to:

  • Update next.config.js to use the new turbopack configuration
  • Migrate from next lint to the ESLint CLI
  • Migrate from deprecated middleware convention to proxy
  • Remove unstable_ prefix from stabilized APIs
  • Remove experimental_ppr Route Segment Config from pages and layouts

The upgrade codemod does not run every migration codemod. If your app still uses synchronous params, searchParams, cookies(), headers(), or draftMode() access from the Next.js 15 compatibility period, also run the async Request APIs codemod:

Terminal
npx @next/codemod@canary next-async-request-api .

Install packages manually

If you prefer to do it manually, install the latest Next.js and React versions:

pnpm add next@latest react@latest react-dom@latest
npm install next@latest react@latest react-dom@latest
yarn add next@latest react@latest react-dom@latest
bun add next@latest react@latest react-dom@latest

If you are using TypeScript, ensure you also upgrade @types/react and @types/react-dom to their latest versions.

Node.js runtime and browser support

RequirementChange / Details
Node.js 20.9+Minimum version now 20.9.0 (LTS); Node.js 18 no longer supported
TypeScript 5+Minimum version now 5.1.0
BrowsersChrome 111+, Edge 111+, Firefox 111+, Safari 16.4+

Turbopack by default

Starting with Next.js 16, Turbopack is stable and used by default with next dev and next build

Previously you had to enable Turbopack using --turbopack, or --turbo.

package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build --turbopack",
    "start": "next start"
  }
}

This is no longer necessary. You can update your package.json scripts:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

If your project has a custom webpack configuration and you run next build (which now uses Turbopack by default), the build will fail to prevent misconfiguration issues.

You have a few different ways to address this:

  • Use Turbopack anyway: Run with next build --turbopack to build using Turbopack and ignore your webpack config.
  • Switch to Turbopack fully: Migrate your webpack config to Turbopack-compatible options.
  • Keep using Webpack: Use the --webpack flag to opt out of Turbopack and build with Webpack.

Good to know: If you see failing builds because a webpack configuration was found, but you don't define one yourself, it is likely that a plugin is adding a webpack option

Opting out of Turbopack

If you need to continue using Webpack, you can opt out with the --webpack flag. For example, to use Turbopack in development but Webpack for production builds:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build --webpack",
    "start": "next start"
  }
}

We recommend using Turbopack for development and production. Submit a comment to this thread, if you are unable to switch to Turbopack.

Turbopack configuration location

The experimental.turbopack configuration is out of experimental.

next.config.ts
import type { NextConfig } from 'next'
 
// Next.js 15 - experimental.turbopack
const nextConfig: NextConfig = {
  experimental: {
    turbopack: {
      // options
    },
  },
}
 
export default nextConfig

You can use it as a top-level turbopack option:

next.config.ts
import type { NextConfig } from 'next'
 
// Next.js 16 - turbopack at the top level of nextConfig
const nextConfig: NextConfig = {
  turbopack: {
    // options
  },
}
 
export default nextConfig

Make sure to review the Turbopack configuration options. Next.js 16 introduces various improvements and new options, for example:

Resolve alias fallback

In some projects, client-side code may import files containing Node.js native modules. This will cause Module not found: Can't resolve 'fs' type of errors.

When this happens, you should refactor your code so that your client-side bundles do not reference these Node.js native modules.

However, in some cases, this might not be possible. In Webpack the resolve.fallback option was typically used to silence the error. Turbopack offers a similar option, using turbopack.resolveAlias. In this case, tell Turbopack to load an empty module when fs is requested for the browser.

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  turbopack: {
    resolveAlias: {
      fs: {
        browser: './empty.ts', // We recommend to fix code imports before using this method
      },
    },
  },
}
 
export default nextConfig

It is preferable to refactor your modules so that client code doesn't ever import from modules using Node.js native modules.

Sass node_modules imports

Turbopack fully supports importing Sass files from node_modules. Note that while Webpack allowed the legacy tilde (~) prefix, Turbopack does not support this syntax.

In Webpack:

styles/globals.scss
@import '~bootstrap/dist/css/bootstrap.min.css';

In Turbopack:

styles/globals.scss
@import 'bootstrap/dist/css/bootstrap.min.css';

If changing the imports is not possible, you can use turbopack.resolveAlias. For example:

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  turbopack: {
    resolveAlias: {
      '~*': '*',
    },
  },
}
 
export default nextConfig

Turbopack File System Caching

Turbopack stores compiler artifacts on disk between runs, for significantly faster compile times across restarts. Filesystem caching is enabled by default for both next dev and next build, through experimental.turbopackFileSystemCacheForDev and experimental.turbopackFileSystemCacheForBuild. See Turbopack FileSystem Caching to configure or disable either one.

React Compiler Support

Built-in support for the React Compiler is now stable in Next.js 16 following the React Compiler's 1.0 release. The React Compiler automatically memoizes components, reducing unnecessary re-renders with zero manual code changes.

The reactCompiler configuration option has been promoted from experimental to stable. It is not enabled by default as we continue gathering build performance data across different application types.

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  reactCompiler: true,
}
 
export default nextConfig

Install the latest version of the React Compiler plugin:

pnpm add -D babel-plugin-react-compiler
npm install -D babel-plugin-react-compiler
yarn add -D babel-plugin-react-compiler
bun add -D babel-plugin-react-compiler

Good to know: Expect compile times in development and during builds to be higher when enabling this option as the React Compiler relies on Babel.

middleware to proxy

The middleware filename is deprecated, and has been renamed to proxy to clarify network boundary and routing focus.

The edge runtime is NOT supported in proxy. The proxy runtime is nodejs, and it cannot be configured. If you want to continue using the edge runtime, keep using middleware. We will follow up on a minor release with further edge runtime instructions.

Terminal
# Rename your middleware file
mv middleware.ts proxy.ts
# or
mv middleware.js proxy.js

The named export middleware is also deprecated. Rename your function to proxy.

export function proxy(request: Request) {}

We recommend changing the function name to proxy, even if you are using a default export.

Configuration flags that contained the middleware name are also renamed. For example, skipMiddlewareUrlNormalize is now skipProxyUrlNormalize

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  skipProxyUrlNormalize: true,
}
 
export default nextConfig

The version 16 codemod is able to update these flags too.

next/image changes

Local Images with Query Strings (Breaking change)

Local image sources with query strings now require images.localPatterns.search configuration to prevent enumeration attacks.

app/page.tsx
import Image from 'next/image'
 
export default function Page() {
  return <Image src="/assets/photo?v=1" alt="Photo" width="100" height="100" />
}

If you need to use query strings with local images, add the pattern to your configuration:

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  images: {
    localPatterns: [
      {
        pathname: '/assets/**',
        search: '?v=1',
      },
    ],
  },
}
 
export default nextConfig

minimumCacheTTL Default (Breaking change)

The default value for images.minimumCacheTTL has changed from 60 seconds to 4 hours (14400 seconds). This reduces revalidation cost for images without cache-control headers.

For some Next.js users, image revalidation was happening frequently, often because the upstream source images missed a cache-control header. This caused revalidation to happen every 60 seconds, which increased CPU usage and cost.

Since most images do not change often, this short interval is not ideal. Setting the default to 4 hours offers a more durable cache by default, while still allowing images to update a few times per day if needed.

If you need the previous behavior, change minimumCacheTTL to a lower value, for example back to 60 seconds:

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  images: {
    minimumCacheTTL: 60,
  },
}
 
export default nextConfig

imageSizes Default (Breaking change)

The value 16 has been removed from the default images.imageSizes array.

We have looked at request analytics and found out that very few projects ever serve 16 pixels width images. Removing this setting reduces the size of the srcset attribute shipped to the browser by next/image.

If you need to support 16px images:

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  images: {
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
}
 
export default nextConfig

Rather than lack of developer usage, we believe 16 pixels width images have become less common, because devicePixelRatio: 2 actually fetches a 32px image to prevent blurriness in retina displays.

qualities Default (Breaking change)

The default value for images.qualities has changed from allowing all qualities to only [75].

If you need to support multiple quality levels:

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  images: {
    qualities: [50, 75, 100],
  },
}
 
export default nextConfig

If you specify a quality prop not included in the images.qualities array, the quality will be coerced to the closest value in images.qualities. For example, given the configuration above, a quality prop of 80, is coerced to 75.

Review custom image qualities. Add required values to images.qualities, and test direct /_next/image requests because unsupported q values return 400.

Local IP Restriction (Breaking change)

A new security restriction blocks local IP optimization by default. Set images.dangerouslyAllowLocalIP to true only for private networks.

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  images: {
    dangerouslyAllowLocalIP: true, // Only for private networks
  },
}
 
export default nextConfig

This might be necessary when hosting Next.js in a VPC with split-horizon DNS and you receive status 400 Bad Request. Only enable once you understand the SSRF risk.

Maximum Redirects (Breaking change)

The default for images.maximumRedirects has changed from unlimited to 3 redirects maximum.

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  images: {
    maximumRedirects: 0, // Disable redirects
    // or
    maximumRedirects: 5, // Increase for edge cases
  },
}
 
export default nextConfig

next/legacy/image Component (deprecated)

The next/legacy/image component is deprecated. Use next/image instead:

// Before
import Image from 'next/legacy/image'
 
// After
import Image from 'next/image'

images.domains Configuration (deprecated)

The images.domains config is deprecated.

next.config.js
// image.domains is deprecated
module.exports = {
  images: {
    domains: ['example.com'],
  },
}

Use images.remotePatterns instead for improved security:

next.config.js
// Use image.remotePatterns instead
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
      },
    ],
  },
}

Concurrent dev and build

next dev and next build now use separate output directories, enabling concurrent execution. The next dev command outputs to .next/dev.

Additionally, a lockfile mechanism prevents multiple next dev or next build instances on the same project.

The Turbopack tracing command should be:

pnpm next internal trace .next-profiles/trace-turbopack.bin
npx next internal trace .next-profiles/trace-turbopack.bin
yarn next internal trace .next-profiles/trace-turbopack.bin
bunx next internal trace .next-profiles/trace-turbopack.bin

ESLint Flat Config

@next/eslint-plugin-next now defaults to ESLint Flat Config format, aligning with ESLint v10 which will drop legacy config support.

Make sure to review our API reference for the @next/eslint-plugin-next plugin.

If you're using the legacy .eslintrc format, consider migrating to the flat config format. See the ESLint migration guide for details.

Scroll Behavior Override

In previous versions of Next.js, if you had set scroll-behavior: smooth globally on your <html> element via CSS, Next.js would override this during SPA route transitions, as follows:

  1. Temporarily set scroll-behavior to auto
  2. Perform the navigation (causing instant scroll to top)
  3. Restore your original scroll-behavior value

This ensured that page navigation always felt snappy and instant, even when you had smooth scrolling enabled for in-page navigation. However, this manipulation could be expensive, especially at the start of every navigation.

In Next.js 16, this behavior has changed. By default, Next.js will no longer override your scroll-behavior setting during navigation.

If you want Next.js to perform this override (the previous default behavior), add the data-scroll-behavior="smooth" attribute to your <html> element:

pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document'
 
export default function Document() {
  return (
    <Html lang="en" data-scroll-behavior="smooth">
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

Performance Improvements

Significant performance optimizations for next dev and next start commands, along with improved terminal output with clearer formatting, better error messages, and improved performance metrics.

Next.js 16 removes the size and First Load JS metrics from the next build output. We found these to be inaccurate in server-driven architectures using React Server Components. Both our Turbopack and Webpack implementations had issues, and disagreed on how to account for Client Components payload.

The most effective way to measure actual route performance is through tools such as Chrome Lighthouse or Vercel Analytics, which focus on Core Web Vitals and downloaded resource sizes.

next dev config load

In previous versions the Next config file was loaded twice during development:

  • When running the next dev command
  • When the next dev command started the Next.js server

This was inefficient because the next dev command doesn't need the config file to start the Next.js server.

A consequence of this change is that, when running next dev checking if process.argv includes 'dev', in your Next.js config file, will return false.

Good to know: The typegen, and build commands, are still visible in process.argv.

This is specially important for plugins that trigger side-effects on next dev. If that's the case, it might be enough to check if NODE_ENV is set to development.

next.config.js
import { startServer } from 'docs-lib/dev-server'
 
const isDev = process.env.NODE_ENV === 'development'
 
if (isDev) {
  startServer()
}
 
const nextConfig = {
  /* Your config options */
}
 
module.exports = nextConfig

Alternatively, use the phase in which the configuration is loaded.

Build Adapters API (alpha)

Following the Build Adapters RFC, the first alpha version of the Build Adapters API is now available.

Build Adapters allow you to create custom adapters that hook into the build process, enabling deployment platforms and custom build integrations to modify Next.js configuration or process build output.

next.config.js
const nextConfig = {
  experimental: {
    adapterPath: require.resolve('./my-adapter.js'),
  },
}
 
module.exports = nextConfig

adapterPath was promoted to a stable, top-level option in 16.2.0. See adapterPath for the current API reference.

Modern Sass API

sass-loader has been bumped to v16, which supports modern Sass syntax and new features.

Removals

These features were previously deprecated and are now removed:

AMP Support

AMP adoption has declined significantly, and maintaining this feature adds complexity to the framework. All AMP APIs and configurations have been removed:

  • amp configuration from your Next config file
  • next/amp hook imports and usage (useAmp)
// Removed
import { useAmp } from 'next/amp'
 
// Removed
export const config = { amp: true }
  • export const config = { amp: true } from pages
next.config.js
const nextConfig = {
  // Removed
  amp: {
    canonicalBase: 'https://example.com',
  },
}
 
module.exports = nextConfig

Evaluate if AMP is still necessary for your use case. Most performance benefits can now be achieved through Next.js's built-in optimizations and modern web standards.

next lint Command

The next lint command has been removed. Use Biome or ESLint directly. next build no longer runs linting.

A codemod is available to automate migration:

pnpm dlx @next/codemod@canary next-lint-to-eslint-cli .
npx @next/codemod@canary next-lint-to-eslint-cli .
yarn dlx @next/codemod@canary next-lint-to-eslint-cli .
bunx @next/codemod@canary next-lint-to-eslint-cli .

The eslint option in the Next.js config file is also removed.

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  // No longer supported
  // eslint: {},
}
 
export default nextConfig

Runtime Configuration

serverRuntimeConfig and publicRuntimeConfig have been removed. Use environment variables instead.

Before (Next.js 15):

next.config.js
module.exports = {
  serverRuntimeConfig: {
    dbUrl: process.env.DATABASE_URL,
  },
  publicRuntimeConfig: {
    apiUrl: '/api',
  },
}
pages/index.tsx
import getConfig from 'next/config'
 
export default function Page() {
  const { publicRuntimeConfig } = getConfig()
  return <p>API URL: {publicRuntimeConfig.apiUrl}</p>
}

After (Next.js 16):

For server-only values, access environment variables directly in Server Components:

app/page.tsx
async function fetchData() {
  const dbUrl = process.env.DATABASE_URL
  // Use for server-side operations only
  return await db.query(dbUrl, 'SELECT * FROM users')
}
 
export default async function Page() {
  const data = await fetchData()
  return <div>{/* render data */}</div>
}

Good to know: Use the taint API to prevent accidentally passing sensitive server values to Client Components.

For client-accessible values, use the NEXT_PUBLIC_ prefix:

.env.local
NEXT_PUBLIC_API_URL="/api"
app/components/client-component.tsx
'use client'
 
export default function ClientComponent() {
  const apiUrl = process.env.NEXT_PUBLIC_API_URL
  return <p>API URL: {apiUrl}</p>
}

To ensure environment variables are read at runtime (not bundled at build time), use the connection() function before reading from process.env:

app/page.tsx
import { connection } from 'next/server'
 
export default async function Page() {
  await connection()
  const config = process.env.RUNTIME_CONFIG
  return <p>{config}</p>
}

Learn more about environment variables.

devIndicators Options

The following options have been removed from devIndicators:

  • appIsrStatus
  • buildActivity
  • buildActivityPosition

The indicator itself remains available.

Troubleshooting

Installation fails after upgrading to React 19

Installation fails after upgrading ESLint

Next.js 16 removes next lint, but migrating to the ESLint CLI does not require upgrading ESLint. eslint-config-next@16 requires ESLint 9 or later, and eslint-config-next@16.4.0 or later supports ESLint 10. If your existing lint configuration does not use eslint-config-next@16, you can keep its compatible ESLint version.

  • If an ESLint upgrade causes peer dependency conflicts, restore the previous compatible versions of eslint, eslint-config-next, and @next/eslint-plugin-next (if present), along with the previous lint configuration.
  • If installation interrupted the upgrade, retry the same exact target with --skip-eslint-upgrade to preserve the repaired lint stack and skip the lint codemod. If Next.js already matches the target, install dependencies and complete interrupted migrations individually. Replace next lint scripts with ESLint CLI commands compatible with the preserved configuration. Confirm installation and lint pass.

Review migration checklist

After applying the changes relevant to your app, review these action items before you consider the migration complete:

  • The app meets the Node.js, TypeScript, and React requirements in this guide.
  • Development and build scripts agree with the effective bundler configuration, including plugins, loaders, aliases, and affected Sass imports.
  • Middleware and proxy changes preserve the required runtime and routing behavior. Edge middleware is not blindly renamed to proxy.
  • Affected image requests succeed under the documented restrictions and defaults while preserving the intended quality.
  • Existing cache API calls follow the documented signatures and semantics. React Compiler and Cache Components are not enabled merely to complete the upgrade.
  • Removed APIs and configuration have no remaining consumers. Runtime configuration replacements preserve server and client visibility and the required evaluation time.
  • Scripts and CI no longer invoke next lint; linting runs separately where the app requires it.
  • The app's supported checks and affected runtime behavior pass. A green build alone does not close runtime findings.

On this page

Use a coding agent (recommended)Or upgrade manuallySet up AI agent docsUsing the codemodInstall packages manuallyNode.js runtime and browser supportTurbopack by defaultOpting out of TurbopackTurbopack configuration locationResolve alias fallbackSass node_modules importsTurbopack File System CachingAsync Request APIs (Breaking change)Migrating types for async Request-time APIsAsync parameters for icon, and open-graph Image (Breaking change)Async id parameter for sitemap (Breaking change)React 19.2React Compiler SupportCaching APIsrevalidateTagupdateTagrefreshcacheLife and cacheTagEnhanced Routing and NavigationPartial Prerendering (PPR)middleware to proxynext/image changesLocal Images with Query Strings (Breaking change)minimumCacheTTL Default (Breaking change)imageSizes Default (Breaking change)qualities Default (Breaking change)Local IP Restriction (Breaking change)Maximum Redirects (Breaking change)next/legacy/image Component (deprecated)images.domains Configuration (deprecated)Concurrent dev and buildParallel Routes default.js requirementESLint Flat ConfigScroll Behavior OverridePerformance Improvementsnext dev config loadBuild Adapters API (alpha)Modern Sass APIRemovalsAMP Supportnext lint CommandRuntime ConfigurationdevIndicators Optionsexperimental.dynamicIO and experimental.useCacheunstable_rootParamsOther experimental flags removed in Next.js 16TroubleshootingInstallation fails after upgrading to React 19Installation fails after upgrading ESLintReview migration checklist