Next.js 15 end of life arrives on 21 October 2026. After that date a 15.x app stops receiving critical bug fixes and security updates on a schedule, and 2026 has shown how often those updates matter. This guide lays out a plan for moving apps to Next.js 16.4 and Node 24 on Azure App Service: in two releases, so the mandatory part ships small and the optional part waits for a calmer sprint.

Why the Next.js 15 end of life date is 21 October

According to the Next.js support policy, when a new major version ships, the previous one moves into Maintenance LTS. In that phase Next.js commits only to critical bug fixes and essential security updates, and each major stays in Maintenance LTS for two years from its initial release. Next.js 15 was released on 21 October 2024, so the window closes on 21 October 2026. The policy page states the rule rather than printing the date, but it is the same arithmetic that ended Next.js 14 on 26 October 2025.

After end of life, Vercel says it may patch an unsupported version only in rare cases, depending on severity. That is a possibility, not a plan you can run production on.

The 2026 security record shows why this matters:

  • July: 16.2.11 and 15.5.21 fixed four high and five medium severity vulnerabilities.
  • August: 16.3.3 and 15.5.24 fixed two critical vulnerabilities.
  • 30 September: 16.3.8 and 15.5.27 fixed one high, five medium and one low severity vulnerability. Fixes for one more critical and one more high severity issue were postponed because of upstream dependency delays.
  • 14 October: an out-of-band update, 16.4.1 and 15.5.28, shipped the two postponed fixes together with one more critical fix: two critical and one high severity vulnerability in total.

15.5.28 is the latest 15.x patch, and after 21 October further 15.x patches are no longer guaranteed. If you are still on 15.x, update to 15.5.28 today and plan the move to 16. 16.4.0 was published on 6 October; at the time of writing the current stable release is 16.4.2.

The plan: two releases, not one

Next.js 16 combines mandatory breaking changes with a new, opt-in caching model. Putting both into one deploy makes it hard to tell which change caused a regression. Split the work:

Release 1 (now) Release 2 (later sprint)
Goal Reach a supported version Adopt the new caching model
Runtime Node 24 everywhere No change
Bundler Turbopack, or --webpack if the build needs it Port webpack config, drop the flag
Middleware Rename to proxy unless it truly needs Edge Revisit remaining Edge middleware
Cache Components Off Enable route by route

Release 1 is about getting supported with the smallest possible diff. Release 2 is where you actually take advantage of what 16.x offers.

Release 1: upgrade Node and Next.js step by step

Step 1: Move to Node 24 everywhere

Change the runtime first, in its own commit, so a Node problem never looks like a Next.js problem. Update every place the version lives: developer machines, the Dockerfile, CI and App Service.

# Developer machines
echo "24" > .nvmrc

# Azure App Service (Linux)
az webapp config set --resource-group <rg> --name <app> --linux-fx-version "NODE|24-lts"
FROM node:24-alpine
# GitHub Actions
- uses: actions/setup-node@v4
  with:
    node-version: 24

Microsoft Learn notes that the App Service setting controls the Node.js version both at runtime and during automated package restore in Kudu, so build and runtime stay aligned.

Why 24 and not 22? Node 22 reaches end of support on 30 April 2027, the date Microsoft also lists for Node 22 on Azure Functions, while Node 24 is supported until 30 April 2028. Upgrading the runtime twice in six months is wasted effort. Two Azure details to keep in mind:

  • Target only the major version. Microsoft Learn recommends against pinning a specific minor or patch version on App Service, because those versions are not guaranteed to stay available.
  • Microsoft's Azure Functions documentation says Node 22 is the last Node.js version on the Linux Consumption plan, so Functions apps that need Node 24 must move to Flex Consumption. Treat that as separate work, not part of this release.

Step 2: Run the official codemod

npx @next/codemod@canary upgrade latest

The codemod handles the version bump, moving Turbopack config, migrating from next lint to ESLint, renaming middleware to proxy and removing unstable_ prefixes. On 16.4, next upgrade --agent also gives coding agents version-specific upgrade guidance. Treat both as a first pass and review the diff like any other pull request.

Step 3: Fix only what breaks

Run next build and work through the errors. These are the usual suspects:

  • Synchronous request APIs. Reading cookies(), headers(), params or searchParams synchronously only warned in 15. In 16 it fails.
import { cookies } from 'next/headers'

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const theme = (await cookies()).get('theme')?.value
  // ...
}
  • Custom webpack config. Turbopack is now the default for production builds, and a project with a custom webpack configuration fails the build deliberately. Don't port that config under deadline pressure. Set "build": "next build --webpack" and move it in Release 2.
  • Middleware. The new proxy.ts runs on the Node.js runtime and exports a function named proxy. The official guide warns that Edge middleware should not be blindly renamed. If yours genuinely depends on Edge, keep middleware.ts for now: it is deprecated and will be removed in a future version, but it still works.
  • Removed features: AMP, next lint, serverRuntimeConfig/publicRuntimeConfig, devIndicators options, the experimental.ppr, dynamicIO and useCache flags, and unstable_rootParams. Parallel-route slots now require a default.js.
  • revalidateTag now needs a second argument naming a cache profile.
revalidateTag('products', 'max') // second argument: cache profile

Before shipping, check the official upgrade guide for the minimum Node.js, TypeScript and browser versions your users and toolchain must meet.

Step 4: Test the silent changes in staging

Some changes never throw an error, which is exactly why they cause incidents:

  • Image cache TTL. The default images.minimumCacheTTL went from 60 seconds to 14400 (four hours). If your CMS replaces images under the same URL, editors will see stale images. Set the value explicitly if you need the old behaviour:
// next.config.js
module.exports = {
  images: { minimumCacheTTL: 60 },
}
  • Lint no longer runs in the build. next build skips linting now, so add an explicit eslint . (or Biome) step to CI or lint errors will reach main unnoticed.
  • Request-time rendering. Vercel explains that caching is now opt-in and dynamic code in pages, layouts and API routes runs at request time by default. Watch CPU and response times on App Service after the deploy.
  • Security fixes. Deploy 16.4.1 or later, so the release includes the 14 October fixes, and check the advisories for anything newer.

Release 2: adopt Cache Components route by route

Once Release 1 is stable in production, schedule a separate sprint for the new features: Cache Components, Partial Prefetching and ensureStatic. According to the 16.4 announcement, the release also brings lower memory and disk usage in dev, shorter compile times, smaller production bundles and React 19.3.

New apps created with create-next-app now have Cache Components enabled by default, and the 16.4 announcement says it will become the default in Next.js 17. Vercel hasn't dated 17, so treat that as the direction of travel rather than a deadline. Adopting gradually now means 17 won't arrive as a second big-bang migration.

A practical order:

  1. Start with marketing and SEO pages. Add ensureStatic, which fails the build if dynamic content sneaks into a route that should stay static.
  2. Move to read-heavy product pages and enable Cache Components there, measuring server load before and after.
  3. Port the custom webpack config to Turbopack and drop the --webpack flag.
  4. Revisit any remaining Edge middleware and decide whether it can become proxy.ts.

Make patching a monthly habit

The real lesson of 2026 is cadence. Since July, Next.js has used a preannounced security release model, publishing upcoming patches ahead of time so teams can plan. Use it:

  • Subscribe the team to the Next.js security advisories and route announcements to a shared channel.
  • Set Azure Service Health alerts for App Service so runtime retirement notices reach the people who own the apps.
  • Reserve a recurring patch slot each month. When the upgrade path is one patch release long, applying a critical fix is a routine deploy instead of a weekend scramble.

Key takeaways

  • Next.js 15 leaves Maintenance LTS on 21 October 2026; 15.5.28 is its latest patch, and later 15.x patches are not guaranteed.
  • Ship Release 1 now: Node 24 everywhere, the official codemod, and only the fixes the build demands.
  • Use --webpack and keep Edge-dependent middleware temporarily; neither should block the deadline.
  • Test silent changes in staging: image cache TTL, lint missing from the build, and request-time rendering load.
  • Adopt Cache Components, Partial Prefetching and ensureStatic route by route in a later sprint.
  • Make patching monthly with the security feed and Azure Service Health alerts.