Next.js shipped a security release in May. Thirteen advisories at once, across every version line still in use: 13.x and 14.x entirely, 15.x up to 15.5.17, 16.x up to 16.2.5. React's react-server-dom-* packages got patched in the same window.
I ship Next.js sites for a living, so a release like this is just Tuesday. Here's the order I work in.
Upgrade first, read later
The patched versions are Next.js 15.5.18 and 16.2.6, and React 19.0.6 / 19.1.7 / 19.2.6. Every site I run is on 15 or 16, so the first thing is the same everywhere:
npm i next@15.5.18 react@19.1.7 react-dom@19.1.7
npm run build # if it builds, deploy it
A patch bump inside the same major almost never breaks a build. Waiting until I've fully understood all 13 CVEs before I deploy is how a one-hour job turns into next week's job. Upgrade, build, ship, then read.
The DoS bugs aren't the ones to worry about
Three of the advisories are denial of service, including CVE-2026-23870 in Server Functions and a connection-exhaustion bug in Cache Components. They read scary. They mostly aren't, for the kind of sites I build — they sit behind Vercel and Cloudflare, and both turned on managed WAF rules for the DoS class by default. Worst case someone makes your site slow for a bit.
The five I actually care about are the middleware bypasses, all rated High:
- auth skipped via segment-prefetch URLs
- auth skipped via dynamic route parameter injection
- locale handling in the Pages Router i18n layer letting requests through
These are the dangerous ones because the auth check you wrote runs, passes review, and then gets routed around anyway. The code looks correct. It is correct. The framework just lets a crafted request reach the page without going through it.
Middleware was never the lock
This is the part worth sitting with. A lot of Next.js tutorials — mine included, at some point — put the auth check in middleware.ts and call it done:
// middleware.ts — fine as a redirect, NOT a security boundary
export function middleware(req: NextRequest) {
const token = req.cookies.get('session')
if (!token) return NextResponse.redirect(new URL('/login', req.url))
}
Middleware is a good place to redirect logged-out users so they don't see a flash of a protected layout. It is a terrible place to be your only gate, because it runs at the edge, before the route resolves, and edge routing is exactly the surface these bypasses attacked. If skipping middleware exposes data, the data was never really protected.
So the real check goes where the data is read — in the Server Component or the Server Action, next to the query:
// app/dashboard/page.tsx
export default async function Dashboard() {
const user = await requireUser() // throws/redirects if no valid session
const rows = await prisma.invoice.findMany({ where: { userId: user.id } })
// ...
}
A bypass can route around middleware. It can't route around a requireUser() that sits between the request and the database.
The morning-of checklist
What I actually run when a release like this lands:
- Bump Next and React to the patched versions on every site. Build, deploy.
- Grep for auth that lives only in
middleware.ts. Move the real check next to the data. - Confirm WAF managed rules are on at the CDN — free coverage for the DoS bugs.
- Note the version I'm now on, so next month I know what "current" meant.
What I'd do differently
I'd stop treating "the build passed and the deploy is green" as the end of it. Auth that lives only in middleware is a pattern that's everywhere in tutorials — I've written it that way myself. Moving the real check down next to the data is a twenty-minute fix, and it's the kind of thing worth doing on principle, before a CVE makes you, not after.
Patch fast. Put the lock on the door, not on the hallway.
