On May 6, 2026, Vercel shipped a coordinated security release for Next.js. Thirteen advisories in a single drop. Six high severity, three moderate, two low, plus an upstream React CVE that breaks every App Router deployment. The Vercel changelog says it plainly: patching is the only complete mitigation.
My own portfolio runs Next.js 16.2.4, which means I sat squarely in the blast radius. I spent a Sunday morning walking my own upgrade, then writing this guide so the next person does not have to dig through eight advisory pages to figure out what actually matters. If you run a Next.js app in production right now, this is the patch you cannot defer.
What landed in the Next.js May 2026 security release?
The Next.js May 2026 security release is a coordinated patch covering 13 advisories across every supported and unsupported Next.js line. The severity split is six high, three moderate, two low, one upstream React Server Components CVE, and one categorization-only entry that ties the rest together.
Here is the breakdown by category, drawn from the Vercel changelog and the matching GitHub Security Advisories.
| Category | Count | Examples |
|---|---|---|
| Authentication bypass | 1 | Server actions transform was not applied to code inside node_modules in certain build paths |
| Denial of service | 4 | CVE-2026-27979 (PPR resume buffering), streaming fetch hang, image cache eviction, RSC payload deserialization |
| Server-side request forgery | 1 | http-proxy patch for internal-network bypass |
| Cache poisoning | 3 | Image lru disk cache, middleware cache key collision, RSC route cache cross-tenant collision |
| Cross-site scripting | 2 | Dev-only websocket leakage, RSC reflection in error overlay |
| Upstream React Server Components | 1 | CVE-2026-23870, react-server-dom-* deserialization DoS |
| Categorization entry | 1 | Internal tracking advisory grouping the others |
Six of these can be exploited unauthenticated over the network. That is the part that should make you stop reading this paragraph and check your package.json.
Which Next.js version do you need to upgrade to?
You need Next.js 15.5.18 or 16.2.6, plus the matching react-server-dom-* peer bumps. There is no patched line for 13.x or 14.x. If you are still on those, you have to move forward at the same time as patching.
Use this matrix to find your target.
| You are on | Target version | Why |
|---|---|---|
| Next.js 13.x (any) | 15.5.18 or 16.2.6 | No backport. Forced upgrade. |
| Next.js 14.x (any) | 15.5.18 or 16.2.6 | No backport. Forced upgrade. |
| Next.js 15.0.0 to 15.5.17 | 15.5.18 | Latest 15.x patch |
| Next.js 16.0.0 to 16.2.5 | 16.2.6 | Latest 16.x patch |
| react-server-dom-* 19.0.0 to 19.0.5 | 19.0.6 | RSC DoS fix |
| react-server-dom-* 19.1.0 to 19.1.6 | 19.1.7 | RSC DoS fix |
| react-server-dom-* 19.2.0 to 19.2.5 | 19.2.6 | RSC DoS fix |
I run Next.js 16.2.4 on this site, so 16.2.6 is the target for me. If you maintain multiple apps on different lines, do the 16.x apps first. They are larger blast radius because PPR is enabled by default in 16.x apps that opted into the App Router.
How do you run the upgrade safely?
You run the upgrade by pinning the lock file, bumping both the framework and the RSC peers in the same commit, then running npm audit to confirm zero advisories remain. The trap people fall into is bumping next but leaving the stale react-server-dom-webpack from a transitive resolution.
Here is the actual sequence I used on my own repo.
# 1. Confirm where you are
node -e "console.log(require('next/package.json').version)"
# 2. Pin the exact patch you want. Do not use ^ or ~ during the upgrade.
npm install next@16.2.6
# 3. Bump the RSC peer dependencies. Match the React minor you are on.
# On React 19.2.x:
npm install react-server-dom-webpack@19.2.6
# On React 19.1.x:
# npm install react-server-dom-webpack@19.1.7
# 4. Refresh the lockfile and prune any stale transitive copies.
npm dedupe
# 5. Verify nothing pinned an older RSC version transitively.
npm ls react-server-dom-webpackThe output of step 5 should show a single tree entry at 19.2.6 (or your matched minor). If you see two versions, something else in your dependency tree pinned the old one. That happens most often with Storybook, Vitest, and any package that ships its own RSC bundler integration. Update those before declaring the upgrade complete.
For pnpm and yarn, the equivalent commands are pnpm up next@16.2.6 react-server-dom-webpack@19.2.6 and yarn upgrade next@16.2.6 react-server-dom-webpack@19.2.6. Both leave you with the same audit step.
Finally, run the audit.
npm audit --omit=dev | grep -E "(next|react-server-dom)"If anything still shows up here, the upgrade is incomplete. Stop and re-check npm ls before going to verification.
What does CVE-2026-23870 actually exploit?
CVE-2026-23870 exploits a deserialization flaw in the React Flight protocol that lets an unauthenticated attacker repeatedly consume the same maliciously crafted model before it gets marked as processed. The result is excessive CPU and memory consumption on the React server, which crashes the process or starves every other request.
The vulnerable code is inside react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack. Every Next.js App Router deployment uses one of these. The CVSS base score is 7.5: network accessible, low complexity, no privileges, no user interaction. ZeroPath's brief calls out that this is the fourth in a series of React Server Components security issues since December 2025, and CVE-2026-23870 affects the exact versions released to fix CVE-2026-23869. That is the important detail. If you patched in April and moved on, you are still vulnerable.
The wire-level shape of the exploit is a POST to any server function endpoint with a crafted React Flight payload that contains a model whose chunk ids reference earlier chunks in a way that triggers re-deserialization. The fix in 19.0.6 / 19.1.7 / 19.2.6 marks each chunk as consumed on the first deserialization pass and rejects further references.
You do not need a public endpoint for this to matter. Any internal admin tool that uses Server Actions or any RSC route that accepts form posts is a target.
Why does CVE-2026-27979 break Partial Prerendering?
CVE-2026-27979 breaks PPR because the next-resume: 1 request header tells the server to buffer the request body for postponed state resumption, but the maxPostponedStateSize limit was not enforced in every buffering path. An attacker sends a POST with that header and a body larger than your server can hold in RAM, and the Node process runs out of memory.
The NVD entry confirms the affected window is Next.js 16.0.1 through 16.1.6, fixed in 16.1.7 and rolled forward into 16.2.6. If you turned on PPR in a 16.x App Router app, you are exposed.
The exploit request looks like this.
POST /any-ppr-route HTTP/1.1
Host: your-app.example.com
Content-Type: application/octet-stream
Content-Length: 5368709120
next-resume: 1
<5 GB of garbage>The server reads the next-resume: 1 header, opens a buffered stream for the postponed resume parser, and starts allocating memory until either the OS kills the process or the Node heap blows past its limit. The Vercel changelog notes that the inconsistent enforcement only happens in non-minimal deployments, which means most self-hosted setups and any custom Node server. The Vercel-managed runtime had separate guards.
Rate-limiting the next-resume header at your edge gives you a stopgap, but it is not a real fix because legitimate PPR resume traffic uses the same header. The only way out is the patch.
Why can't a WAF rule block these CVEs reliably?
A WAF rule cannot reliably block these CVEs because the malicious requests share the same wire shape, headers, and content types as legitimate Next.js traffic. The Vercel advisory is explicit on this point: there is no signature that distinguishes a crafted React Flight payload from a benign one, and there is no body length threshold that catches CVE-2026-27979 without also dropping real PPR resume requests.
Cloudflare's matching changelog post lists what their Managed Rules can do as partial mitigation. Their wording is careful. They call these "framework adapter mitigations" rather than full coverage. The recommendation in the same post is to patch the framework. WAF rules buy you minutes, not weeks.
Reading those two posts side by side, the lesson is the one we learned the hard way from the TanStack supply chain attack: when the vulnerability lives inside the framework's normal trust boundary, an edge firewall cannot meaningfully police it. Patching is the only complete control.
How do you verify the upgrade actually shipped?
You verify the upgrade by running a build, executing a smoke test against your streaming routes, and replaying the CVE-2026-27979 request shape against your local instance to confirm the server rejects oversized resume bodies. If any of those three fail, the upgrade is not done.
Start with the build.
npm run buildThe build should complete cleanly. The May 2026 release also fixed a streaming fetch hang that previously caused intermittent build timeouts in dev mode, so if your CI was flaky on dev rebuilds, that should also stop.
Next, smoke-test a streaming route. If you do not have one handy, this minimal route handler works.
// app/api/stream/route.ts
import type { NextRequest } from 'next/server'
export async function GET(_req: NextRequest) {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 3; i += 1) {
controller.enqueue(encoder.encode(`chunk-${i}\n`))
await new Promise((resolve) => setTimeout(resolve, 200))
}
controller.close()
},
})
return new Response(stream, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
})
}Curl it and confirm you see three chunks arrive with the right spacing.
Finally, replay the CVE-2026-27979 exploit shape. On a patched server, this returns a 413 or 400, not an OOM.
# Send a 100 MB body with the next-resume header. On a patched 16.2.6 server,
# this should fail fast with a clear error instead of allocating memory.
dd if=/dev/zero bs=1M count=100 2>/dev/null | curl -X POST \
-H "Content-Type: application/octet-stream" \
-H "next-resume: 1" \
--data-binary @- \
http://localhost:3000/any-routeIf the server hangs or your memory chart spikes, you are still running the vulnerable version. Roll back to the lockfile from before the upgrade and inspect why npm did not apply the new version.
What should you harden after the patch lands?
You should harden three things after the patch lands: the next-resume header, your dev environment websocket exposure, and the way your CI flags stale Next.js versions. The patch closes the immediate CVE windows, but the categories these advisories belong to keep recurring, so the cheap defense in depth is worth setting up once.
For next-resume, add a rate limit at the edge. Cloudflare Workers, NGINX, an Upstash-backed Next.js route handler limiter, or your Spring gateway with a token-bucket @RateLimit annotation can all enforce a per-IP cap on requests that carry the header. Set the cap low. Legitimate PPR resume traffic is a fraction of a percent of total request volume in most apps.
For dev websockets, the May 6 release blocked the privacy-sensitive websockets that previously leaked stack frames and source paths during local development. Confirm your dev server is not bound to 0.0.0.0 by default. Restricting dev mode to 127.0.0.1 was always good hygiene, and now it also closes one of the patched CVE windows for any dev environment running on a shared network.
For CI, add a check that fails the build if next resolves to a version with a known CVE. The lightest form is a one-line grep.
# In your CI pipeline, run before npm test
node -e "
const v = require('next/package.json').version;
const [maj, min, patch] = v.split('.').map(Number);
const safe = (maj === 15 && (min > 5 || (min === 5 && patch >= 18))) ||
(maj === 16 && (min > 2 || (min === 2 && patch >= 6))) ||
maj >= 17;
if (!safe) { console.error('Vulnerable Next.js:', v); process.exit(1); }
console.log('Next.js version OK:', v);
"This is the same pattern I use to fail builds when a Lombok or Spring Boot version drops below a CVE floor. It catches the obvious cases without needing a full SCA tool.
How do you stop the next RSC CVE from catching you cold?
You stop the next RSC CVE from catching you cold by treating React Server Components advisories like supply chain incidents, not framework patches. Subscribe to the React security mailing list, set a Renovate or Dependabot grouping rule for react-server-dom-*, and assume the next one is already in flight.
CVE-2026-23870 is the fourth React Server Components CVE since December 2025. The pattern is clear: an attacker finds a deserialization or chunk-handling flaw, the fix lands, and the same code area produces another flaw within months. CVE-2026-23869 was fixed in April, CVE-2026-23870 landed in May. The teams that left CI on stale Next.js versions paid both times.
The Cloudflare changelog and the Vercel changelog both make the same recommendation in slightly different language: patch the framework on every coordinated drop, do not wait for the WAF rule. After watching this play out in real time on my own portfolio, I added the CI gate above and I plan to keep it there for the next twelve months at least. The cost is one extra second of CI time. The downside of skipping it is being the team that gets paged at 2 a.m. for an OOM kill on a server that did not need to be vulnerable.
For deeper context on how React patches have rippled across the ecosystem this year, see The Day a React Patch Broke the Internet and the new server-action data-fetching patterns in Replacing useEffect with Server Actions.
For the original advisory text, see the Vercel May 2026 changelog, the Next.js release notes, the CVE-2026-23870 brief from ZeroPath, the CVE-2026-27979 advisory on GitLab, the NVD entry, and the Cloudflare WAF guidance.
Keep Reading
- The Day a React Patch Broke the Internet. How a single React point release crashed production for every app that pinned too aggressively, and what it taught us about upgrade discipline.
- TanStack npm Supply Chain Attack 2026. Why edge firewalls cannot police vulnerabilities that live inside the framework trust boundary, and what to change in CI.
- Next.js 16.2 Agents.md and Next/Browser. The 16.2 line you are now upgrading to, with the agent-runtime primitives the security release built on.
- Building a Modern Docs Generator with Next.js 16. A practical 16.x App Router build that exercises the same RSC paths the CVE-2026-23870 fix touches.
