Guide
Next.js security update, October 2026: check and patch CVE-2026-96889
By Aman Singh · Published · 10 min read
On 14 October 2026 Vercel released Next.js 16.4.1 and 15.5.28 to fix two critical remote code execution flaws and a denial-of-service bug. One of them, CVE-2026-96889, sits in the image optimizer that most Next.js sites use through next/image. This guide shows which apps are affected, gives a small script that checks a project's installed versions and features, and covers the one upgrade path that leaves the image flaw in place.
This site runs on Next.js, and was on 16.3.0 until today. The upgrade I ran is in the guide. The checker was tested on 23 October 2026 with Node.js 26 against real installs of Next.js 16.3.0, 15.5.28 and 16.4.1, and against fake npm and pnpm projects.
The Node.js runtime under Next.js needs this month's fix too: Node2Shell (Brotli) check and patch →
What was fixed
| Advisory | What | Severity | Affected Next.js |
|---|---|---|---|
CVE-2026-96889 | Remote code execution in the Image Optimization API with SVG files, through sharp, on glibc-based Linux | Critical, 9.5 | 10.0.0 to 15.5.27, 16.0.0 to 16.4.0 |
GHSA-673q-mjm6-r5xr | Unauthenticated remote code execution when a "use cache" function returns multiple typed arrays (App Router with Cache Components) | Critical, 9.5 | 16.0.0 to 16.4.0 |
GHSA-p3m5-59mp-jv5r | Crafted requests to Server Actions cause excessive CPU use, blocking the process | High, 8.2 | 13.0.0 to 15.5.27, 16.0.0 to 16.4.0 |
Fixed releases: 16.4.1 and 15.5.28. The image flaw's fix is sharp 0.35.5, and the two Next.js 16 flaws come from React Server Components code that React fixed in 19.0.9, 19.1.10, 19.2.9 and 19.3.1.
Is your app affected?
| Your app | Affected by | Action |
|---|---|---|
| Next.js 16, anything below 16.4.1 | All three (use cache and Server Actions only if you use them) | Upgrade to 16.4.1 |
| Next.js 15 below 15.5.28, with Server Actions | Image RCE and Server Actions DoS | Upgrade to 15.5.28, check sharp |
| Next.js 15.5.28 with sharp 0.34.x | Image RCE | Install sharp 0.35.5 or later |
| Pages Router only, no Server Actions | Image RCE only | Upgrade, or update sharp |
| Next.js 14 or older | Image RCE (and Server Actions DoS from 13.0) | No fixed release: move to 15.5.28 or 16.4.1 |
Vercel's advisory says to upgrade even if your app has no use cache functions, so treat every Next.js 16 app below 16.4.1 as needing the update.
Check your project with a script
package.json shows ranges, not what is installed, and sharp can be installed more than once. This script reads every installed copy of next, sharp and react-server-dom-* (npm, pnpm or yarn layouts), looks through your source for "use cache", "use server" and next/image, and reports each advisory. It has no dependencies.
The script and its tests are also on GitHub as AmanSin001/next-security-check (MIT licence). Download it with curl -O https://raw.githubusercontent.com/AmanSin001/next-security-check/main/next-security-check.mjs, or copy it from below.
#!/usr/bin/env node
// next-security-check.mjs: checks a Next.js project against the October 14,
// 2026 advisories (CVE-2026-96889, GHSA-673q-mjm6-r5xr, GHSA-p3m5-59mp-jv5r).
// No dependencies. Run it in the project root, after installing packages:
// node next-security-check.mjs [path]
// It reads the versions actually installed in node_modules (npm, pnpm or yarn),
// not package.json ranges. Exit code: 0 = patched, 1 = action needed.
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
// --- version helpers ---------------------------------------------------------
export function parse(version) {
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(version ?? "");
return m ? { major: +m[1], minor: +m[2], patch: +m[3], pre: m[4] ?? "" } : null;
}
export function compare(a, b) {
const x = parse(a);
const y = parse(b);
for (const k of ["major", "minor", "patch"]) if (x[k] !== y[k]) return x[k] - y[k];
if (x.pre === y.pre) return 0;
if (!x.pre) return 1; // a release is newer than its pre-releases
if (!y.pre) return -1;
return x.pre < y.pre ? -1 : 1;
}
const between = (v, lo, hi) => compare(v, lo) >= 0 && compare(v, hi) <= 0;
// --- the advisories ----------------------------------------------------------
// Returns one row per advisory: { id, title, affected, fixedBy, applies, note }
export function assess({ next, sharp = [], rsd = [], features }) {
const rows = [];
const nextFix = next && parse(next).major >= 16 ? "16.4.1" : "15.5.28";
// CVE-2026-96889: RCE in Image Optimization with SVG, via sharp (glibc Linux).
const imgRange = next && (between(next, "10.0.0", "15.5.27") || between(next, "16.0.0", "16.4.0"));
const oldSharp = sharp.filter((v) => compare(v, "0.35.5") < 0);
rows.push({
id: "CVE-2026-96889",
title: "Image Optimization RCE with SVG (critical)",
// sharp is the real fix: 15.5.28 still accepts sharp ^0.34.3, and 0.34.x has
// no fixed release, so an old sharp is a problem on any Next.js version.
inRange: Boolean(imgRange) || oldSharp.length > 0,
affected: Boolean(next && compare(next, "10.0.0") >= 0) && oldSharp.length > 0,
fixedBy: imgRange ? `next ${nextFix}, or sharp 0.35.5+ for every copy` : "sharp 0.35.5+ for every copy (npm install sharp@^0.35.5)",
applies: features.nextImage || features.svgAllowed ? "your app uses next/image" : "no next/image found, but the endpoint still exists",
note:
imgRange && oldSharp.length === 0
? `next ${next} is in range, but every sharp copy is 0.35.5+ (the documented workaround).`
: oldSharp.length
? `sharp ${oldSharp.join(", ")} installed; needs 0.35.5+${imgRange ? "" : " (this Next.js still accepts it: run npm install sharp@^0.35.5)"}.`
: "",
});
// GHSA-673q-mjm6-r5xr: RCE in `use cache` returning typed arrays (16.x only).
const cacheRange = next && between(next, "16.0.0", "16.4.0");
rows.push({
id: "GHSA-673q-mjm6-r5xr",
title: "use cache RCE (critical, Next.js 16)",
inRange: Boolean(cacheRange),
affected: Boolean(cacheRange),
fixedBy: "next 16.4.1",
applies: features.useCache ? "your code has \"use cache\"" : "no \"use cache\" found; Vercel still says upgrade",
note: "",
});
// GHSA-p3m5-59mp-jv5r: Server Actions denial of service.
const dosRange = next && (between(next, "13.0.0", "15.5.27") || between(next, "16.0.0", "16.4.0"));
rows.push({
id: "GHSA-p3m5-59mp-jv5r",
title: "Server Actions denial of service (high)",
inRange: Boolean(dosRange),
affected: Boolean(dosRange) && features.serverActions,
fixedBy: `next ${nextFix}`,
applies: features.serverActions ? "your code has \"use server\"" : "no Server Actions found: not vulnerable",
note: dosRange && !features.serverActions ? "In the affected range, but only apps with Server Actions are vulnerable." : "",
});
// React Server Components packages installed directly (GHSA-xqc2-xf4c-x3h3).
const rsdFixed = { "19.0": "19.0.9", "19.1": "19.1.10", "19.2": "19.2.9", "19.3": "19.3.1" };
const badRsd = rsd.filter(({ version }) => {
const p = parse(version);
const fix = p && rsdFixed[`${p.major}.${p.minor}`];
return fix && compare(version, fix) < 0;
});
rows.push({
id: "GHSA-xqc2-xf4c-x3h3",
title: "React Server Components packages (critical)",
inRange: badRsd.length > 0,
affected: badRsd.length > 0,
fixedBy: "react-server-dom-* 19.0.9 / 19.1.10 / 19.2.9 / 19.3.1",
applies: rsd.length ? "installed directly" : "not installed directly (Next.js bundles its own)",
note: badRsd.map((r) => `${r.name}@${r.version}`).join(", "),
});
if (next && parse(next).major < 15 && imgRange) {
rows[0].note += " Next.js 14 and older get no fixed release: upgrade to 15.5.28 or 16.4.1.";
}
return rows;
}
// --- reading the project ---------------------------------------------------------
function readJson(file) {
try {
return JSON.parse(readFileSync(file, "utf8"));
} catch {
return null;
}
}
/** Every installed copy of the named packages, including nested and pnpm copies. */
export function findInstalled(root, names) {
const wanted = (name) => names.some((n) => (n.endsWith("*") ? name.startsWith(n.slice(0, -1)) : name === n));
const found = [];
const visited = new Set();
const walkModules = (dir) => {
let real;
try {
real = realpathSync(dir);
} catch {
return;
}
if (visited.has(real)) return; // pnpm symlinks can loop
visited.add(real);
for (const entry of safeReaddir(dir)) {
if (entry === ".pnpm") {
for (const store of safeReaddir(path.join(dir, entry))) walkModules(path.join(dir, entry, store, "node_modules"));
} else if (entry.startsWith("@")) {
for (const sub of safeReaddir(path.join(dir, entry))) visitPackage(path.join(dir, entry, sub), `${entry}/${sub}`);
} else if (!entry.startsWith(".")) {
visitPackage(path.join(dir, entry), entry);
}
}
};
const visitPackage = (dir, name) => {
if (wanted(name)) {
const pkg = readJson(path.join(dir, "package.json"));
if (pkg?.version) found.push({ name: pkg.name ?? name, version: pkg.version, path: dir });
}
const nested = path.join(dir, "node_modules");
if (existsSync(nested)) walkModules(nested);
};
walkModules(path.join(root, "node_modules"));
return found;
}
function safeReaddir(dir) {
try {
return readdirSync(dir);
} catch {
return [];
}
}
const SOURCE_DIRS = ["app", "src", "pages", "components", "lib"];
const SOURCE_EXT = /\.(m?[jt]sx?)$/;
export function detectFeatures(root) {
const features = { appRouter: false, useCache: false, serverActions: false, nextImage: false, svgAllowed: false };
features.appRouter = existsSync(path.join(root, "app")) || existsSync(path.join(root, "src", "app"));
const scan = (dir) => {
if (!existsSync(dir)) return;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
const full = path.join(dir, entry.name);
if (entry.isDirectory()) scan(full);
else if (SOURCE_EXT.test(entry.name)) {
const text = readFileSync(full, "utf8");
if (/["']use cache(?::[^"']*)?["']/.test(text)) features.useCache = true;
if (/["']use server["']/.test(text)) features.serverActions = true;
if (/from\s+["']next\/(legacy\/)?image["']/.test(text)) features.nextImage = true;
}
}
};
for (const d of SOURCE_DIRS) scan(path.join(root, d));
for (const name of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
const file = path.join(root, name);
if (existsSync(file) && /dangerouslyAllowSVG\s*:\s*true/.test(readFileSync(file, "utf8"))) features.svgAllowed = true;
}
return features;
}
export function check(root) {
const installed = findInstalled(root, ["next", "sharp", "react-server-dom-*"]);
const nextPkg = readJson(path.join(root, "node_modules", "next", "package.json"));
const next = nextPkg?.version ?? installed.find((p) => p.name === "next")?.version ?? null;
const sharp = [...new Set(installed.filter((p) => p.name === "sharp").map((p) => p.version))];
const rsd = installed.filter((p) => p.name.startsWith("react-server-dom-") && !p.path.includes(`${path.sep}next${path.sep}`));
const features = detectFeatures(root);
return { next, sharp, rsd, features, rows: assess({ next, sharp, rsd, features }) };
}
// --- CLI ---------------------------------------------------------------------
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
const root = path.resolve(process.argv[2] ?? ".");
const r = check(root);
if (!r.next) {
console.log(`No installed next package found under ${root}. Run npm install first.`);
process.exit(1);
}
const f = r.features;
console.log("Next.js security check (October 14, 2026 advisories)");
console.log(`next: ${r.next}`);
console.log(`sharp: ${r.sharp.length ? r.sharp.join(", ") : "not installed"}`);
console.log(`features: App Router ${f.appRouter ? "yes" : "no"}, "use cache" ${f.useCache ? "yes" : "no"}, Server Actions ${f.serverActions ? "yes" : "no"}, next/image ${f.nextImage ? "yes" : "no"}`);
console.log("");
let action = false;
for (const row of r.rows) {
action ||= row.affected;
console.log(`${row.affected ? "FIX " : "ok "} ${row.id.padEnd(20)} ${row.title}`);
const detail = row.inRange || row.id === "GHSA-xqc2-xf4c-x3h3" ? `${row.applies}${row.note ? `. ${row.note}` : ""}` : `next ${r.next} is not affected`;
console.log(` ${detail}`);
if (row.affected) console.log(` Fix: ${row.fixedBy}`);
}
console.log("");
console.log(action ? "Action needed: upgrade, reinstall, rebuild and redeploy." : "No action needed for these advisories.");
process.exit(action ? 1 : 0);
}# in the project root, after npm install / pnpm install / yarn
node next-security-check.mjs
echo "exit code: $?" # 0 = nothing to do, 1 = action neededOutput on a real Next.js 16.3.0 install with Server Actions and next/image:
Next.js security check (October 14, 2026 advisories)
next: 16.3.0
sharp: 0.35.3
features: App Router yes, "use cache" no, Server Actions yes, next/image yes
FIX CVE-2026-96889 Image Optimization RCE with SVG (critical)
your app uses next/image. sharp 0.35.3 installed; needs 0.35.5+.
Fix: next 16.4.1, or sharp 0.35.5+ for every copy
FIX GHSA-673q-mjm6-r5xr use cache RCE (critical, Next.js 16)
no "use cache" found; Vercel still says upgrade
Fix: next 16.4.1
FIX GHSA-p3m5-59mp-jv5r Server Actions denial of service (high)
your code has "use server"
Fix: next 16.4.1
ok GHSA-xqc2-xf4c-x3h3 React Server Components packages (critical)
not installed directly (Next.js bundles its own)
Action needed: upgrade, reinstall, rebuild and redeploy.Show the tests (8 cases, including pnpm and the sharp trap)
// node --test next-security-check.test.mjs
// Builds small fake projects on disk (npm and pnpm layouts) and checks them.
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, writeFileSync, symlinkSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { assess, check, compare } from "./next-security-check.mjs";
function project(files) {
const root = mkdtempSync(path.join(tmpdir(), "nextcheck-"));
for (const [file, content] of Object.entries(files)) {
const full = path.join(root, file);
mkdirSync(path.dirname(full), { recursive: true });
writeFileSync(full, typeof content === "string" ? content : JSON.stringify(content));
}
return root;
}
const pkg = (name, version) => ({ name, version });
const status = (r) => Object.fromEntries(r.rows.map((row) => [row.id, row.affected]));
test("version comparison", () => {
assert.ok(compare("16.4.1", "16.4.0") > 0);
assert.ok(compare("15.5.27", "15.5.28") < 0);
assert.ok(compare("16.4.0", "16.4.0-canary.3") > 0);
assert.ok(compare("0.35.10", "0.35.5") > 0);
});
test("unpatched Next.js 16 app with every feature: all three Next.js advisories", () => {
const root = project({
"node_modules/next/package.json": pkg("next", "16.3.0"),
"node_modules/sharp/package.json": pkg("sharp", "0.35.3"),
"app/page.tsx": 'import Image from "next/image";\nexport default function P() {}',
"app/actions.ts": '"use server";\nexport async function save() {}',
"app/data.ts": 'export async function get() { "use cache"; return new Uint8Array(1); }',
});
const r = check(root);
assert.deepEqual(status(r), {
"CVE-2026-96889": true,
"GHSA-673q-mjm6-r5xr": true,
"GHSA-p3m5-59mp-jv5r": true,
"GHSA-xqc2-xf4c-x3h3": false,
});
assert.deepEqual(r.features, { appRouter: true, useCache: true, serverActions: true, nextImage: true, svgAllowed: false });
rmSync(root, { recursive: true });
});
test("patched Next.js 16.4.1 with sharp 0.35.5: nothing to do", () => {
const root = project({
"node_modules/next/package.json": pkg("next", "16.4.1"),
"node_modules/sharp/package.json": pkg("sharp", "0.35.5"),
"app/actions.ts": '"use server";',
});
assert.ok(check(root).rows.every((row) => !row.affected));
rmSync(root, { recursive: true });
});
test("the 15.x lockfile trap: next 15.5.28 but an old nested sharp", () => {
const root = project({
"node_modules/next/package.json": pkg("next", "15.5.28"),
"node_modules/next/node_modules/sharp/package.json": pkg("sharp", "0.34.4"),
});
const r = check(root);
assert.deepEqual(r.sharp, ["0.34.4"]);
// next 15.5.28 accepts sharp ^0.34.3 and 0.34.x has no fix, so this is flagged.
assert.equal(status(r)["CVE-2026-96889"], true);
assert.ok(r.rows[0].note.includes("npm install sharp@^0.35.5"));
assert.equal(status(r)["GHSA-p3m5-59mp-jv5r"], false);
rmSync(root, { recursive: true });
});
test("sharp workaround on an unpatched 15.x: image RCE cleared, DoS remains", () => {
const root = project({
"node_modules/next/package.json": pkg("next", "15.4.6"),
"node_modules/sharp/package.json": pkg("sharp", "0.35.5"),
"app/form.tsx": "'use server'",
});
const s = status(check(root));
assert.equal(s["CVE-2026-96889"], false);
assert.equal(s["GHSA-673q-mjm6-r5xr"], false); // 16.x only
assert.equal(s["GHSA-p3m5-59mp-jv5r"], true);
rmSync(root, { recursive: true });
});
test("Pages Router app without Server Actions is not hit by the DoS", () => {
const root = project({
"node_modules/next/package.json": pkg("next", "14.2.30"),
"node_modules/sharp/package.json": pkg("sharp", "0.33.5"),
"pages/index.tsx": "export default function Home() {}",
});
const r = check(root);
assert.equal(r.features.appRouter, false);
assert.equal(status(r)["GHSA-p3m5-59mp-jv5r"], false);
assert.equal(status(r)["CVE-2026-96889"], true);
assert.match(r.rows[0].note, /no fixed release/);
rmSync(root, { recursive: true });
});
test("pnpm layout: finds packages inside node_modules/.pnpm, survives symlinks", () => {
const root = project({
"node_modules/.pnpm/next@16.2.2/node_modules/next/package.json": pkg("next", "16.2.2"),
"node_modules/.pnpm/sharp@0.35.1/node_modules/sharp/package.json": pkg("sharp", "0.35.1"),
"node_modules/.pnpm/react-server-dom-webpack@19.2.4/node_modules/react-server-dom-webpack/package.json": pkg("react-server-dom-webpack", "19.2.4"),
});
try {
symlinkSync(path.join(root, "node_modules/.pnpm/next@16.2.2/node_modules/next"), path.join(root, "node_modules/next"), "junction");
symlinkSync(path.join(root, "node_modules"), path.join(root, "node_modules/.pnpm/next@16.2.2/node_modules/next/node_modules"), "junction");
} catch {
// symlinks unavailable: the .pnpm walk alone must still work
}
const r = check(root);
assert.equal(r.next, "16.2.2");
assert.deepEqual(r.sharp, ["0.35.1"]);
assert.equal(status(r)["GHSA-xqc2-xf4c-x3h3"], true);
rmSync(root, { recursive: true });
});
test("assess flags old React Server Components packages per minor line", () => {
const rows = assess({
next: "16.4.1",
sharp: ["0.35.5"],
rsd: [{ name: "react-server-dom-webpack", version: "19.1.10" }, { name: "react-server-dom-parcel", version: "19.3.0" }],
features: { appRouter: true, useCache: false, serverActions: false, nextImage: false, svgAllowed: false },
});
const rsdRow = rows.find((row) => row.id === "GHSA-xqc2-xf4c-x3h3");
assert.equal(rsdRow.affected, true);
assert.equal(rsdRow.note, "react-server-dom-parcel@19.3.0");
});ok 1 - version comparison
ok 2 - unpatched Next.js 16 app with every feature: all three Next.js advisories
ok 3 - patched Next.js 16.4.1 with sharp 0.35.5: nothing to do
ok 4 - the 15.x lockfile trap: next 15.5.28 but an old nested sharp
ok 5 - sharp workaround on an unpatched 15.x: image RCE cleared, DoS remains
ok 6 - Pages Router app without Server Actions is not hit by the DoS
ok 7 - pnpm layout: finds packages inside node_modules/.pnpm, survives symlinks
ok 8 - assess flags old React Server Components packages per minor line
# pass 8
# fail 0The source scan is a guide, not proof: code generated at build time or living in another package will not show up. When in doubt, upgrade.
Patch it
- 1Upgrade. Keep
eslint-config-nexton the same version asnext.Terminal # Next.js 16 npm install next@16.4.1 eslint-config-next@16.4.1 # Next.js 15 npm install next@15.5.28 npm install sharp@^0.35.5 # only if sharp is in your own package.json # then confirm what actually got installed npm ls next sharp - 2Run the checker again and commit the lockfile, so CI and production install exactly what you tested.
- 3Build and test. Run a full production build and lint, and click through pages that use images, forms and cached data.
- 4Redeploy and confirm. Vercel and other platforms build from the new lockfile on deploy. Self-hosted Node servers and Docker images must be rebuilt and restarted. In the browser console of the live site,
window.next.versionshows the version that is actually running.
While you redeploy, check the Node.js runtime too. A week later, on 21 October, Node.js fixed a Brotli decoder flaw that can lead to remote code execution; see the Node.js Brotli vulnerability guide and the October 2026 security roundup for everything that shipped this month.
The sharp trap on Next.js 15
Next.js 16.4.1 requires sharp ^0.35.5, so upgrading pulls in the fixed version. Next.js 15.5.28 accepts ^0.34.3 || ^0.35.5, and the last 0.34 release (0.34.5) came out in November 2025, before the fix. If sharp is already in your own package.json (older Next.js docs recommended adding it for self-hosting) or pinned in the lockfile, the upgrade leaves it alone. I reproduced this with a real install:
$ npm install next@15.5.28
$ node next-security-check.mjs
next: 15.5.28
sharp: 0.34.5
FIX CVE-2026-96889 Image Optimization RCE with SVG (critical)
your app uses next/image. sharp 0.34.5 installed; needs 0.35.5+
(this Next.js still accepts it: run npm install sharp@^0.35.5).
$ npm install sharp@^0.35.5
$ node next-security-check.mjs
...
No action needed for these advisories.How I patched this site
codewares.in is a Next.js 16 App Router site using next/image, with no Server Actions or use cache. It was on Next.js 16.3.0 and sharp 0.35.3, so the image flaw applied. The upgrade:
npm install next@16.4.1 react@19.2.9 react-dom@19.2.9 sharp@^0.35.5
npm install -D eslint-config-next@16.4.1
npm ls sharp
# codewares@0.1.0
# +-- next@16.4.1
# | `-- sharp@0.35.5 deduped
# `-- sharp@0.35.5- React too. I moved
reactandreact-domto 19.2.9 so every React package is on a patched line, although Next.js bundles its own server components runtime. - npm changed exact pins to ranges.
npm installrewrote"16.3.0"as"^16.4.1"; I restored exact versions so builds stay reproducible. - npm audit then showed one production fix (
source-map-js, applied withnpm audit fix) and five development-only findings in the ESLint toolchain, where the suggested "fix" would downgradeeslint-config-nextto 14. I left those. - A full lint caught one old plain
<a>link to an internal page, now aLink. - Result: two clean production builds, every page, social image and the image optimizer checked locally, then deployed. The live site reports
window.next.version= 16.4.1.
Next.js 14 and older
There is no fixed 14.x or older release. The image flaw's documented workaround is sharp 0.35.5 or later, but older Next.js versions may not work with it, and the Server Actions issue reaches back to 13.0. The real fix is moving to 15.5.28 or 16.4.1. If that is a large jump, start with the official codemods and fix what the build reports.
Common questions
Which Next.js versions fix CVE-2026-96889?+
How do I check if my Next.js app is vulnerable?+
Is my app on Vercel safe without upgrading?+
I don't use "use cache" or Server Actions. Do I still need to upgrade?+
Why is sharp still 0.34 after upgrading to Next.js 15.5.28?+
Want your Next.js app patched?
I upgrade Next.js and React apps, fix what the upgrade breaks, and check the result before it goes live. See Next.js and React bug fixing. Running PHP as well? The PHP2Shell guide covers this month's PHP fixes.
Sources
Next steps
Keep reading
Need a hand with this?
Tell me what you are working on. Free scoping call, fixed quote within 48 hours.
Book a call