A bad week to ship 'next: latest'
The redesigned version of this site went live yesterday afternoon. About an hour later, the next deploy failed with a single line from Vercel:
Build Failed. Vulnerable version of Next.js detected, please update immediately.
Within twenty-four hours I'd patched two CVEs, reverted a too-aggressive framework upgrade, and spent a confusing thirty minutes wondering why a security scanner kept reporting a vulnerability I'd already fixed. None of it was hard. All of it was avoidable. This is what I learned.
What got flagged
The first hit was CVE-2025-66478, a remote code execution vulnerability in React Server Components. The advisory describes unauthenticated RCE via insecure deserialization in the React Flight protocol — the wire format React uses to ship server-rendered components to the client. The fix lived in patched versions of next, react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack.
I was on next@15.4.6. The patched line was 15.5.8. A small bump.
The second hit, a few hours later: next-mdx-remote@4.4.1 was flagged as vulnerable. I was using it to render MDX articles like this one. The patched line was the v6 series — a major version bump.
The almost-mistake
My first instinct on the Next.js advisory was to do npm install next@latest. That pulled next@16.2.6. Next.js 16 is a significant release: Turbopack as the default builder, the eslint block removed from next.config.js, automatic tsconfig modifications. My build immediately broke on a Turbopack/webpack config conflict.
When patching a CVE, the smallest possible patch is almost always the right answer. Major-version upgrades introduce migration work that has nothing to do with the security fix. If you're mid-incident, that's exactly the wrong moment to take on framework breaking changes.
Vercel's automated security bot had quietly opened a PR while I was busy compounding the problem. Their PR did exactly what I should have done: pinned next to the precise patched version (15.5.8) and updated the React Server DOM packages to their advisory-recommended versions. No major bump, no breaking changes, no migration. I reverted my Next.js 16 attempt, applied the bot's targeted patch, and the build went green.
There's a meta-lesson here: the people maintaining the framework usually know exactly which patch fixes a given CVE without breaking your code. Trust their guidance over your own “just go latest” instinct.
The other lesson hiding in the same incident
package.json had "next": "latest" — a string I inherited from a project template years ago and never thought about. Half the reason the upgrade went sideways is that I had no idea which Next.js version I was actually running until I checked. Production builds were silently consuming whatever was the latest tag at install time.
After the patch, the new pin reads "next": "15.5.8". Exact version, no caret, no tilde, no latest. If I want to upgrade later, that's a deliberate npm install next@15.6.x (or whatever) rather than a surprise.
For framework-level dependencies, pin to exact versions. For everything else, pin major and minor; allow patch ranges (^X.Y.Z is fine for libraries you don't fundamentally depend on for app structure). The discipline costs you almost nothing and removes an entire category of surprise.
The next-mdx-remote bump that wasn't breaking
next-mdx-remote is the library I use to render MDX article files into React components. The v4 to v6 jump was technically two major versions, which usually means migration work. I was bracing for it.
In practice, the API surface I touch is small: a default MDXRemote component, a MDXRemoteSerializeResult type, and a serialize function from a sub-export. All three names were stable across the version jump. The package shifted to ESM-only internally, which Next.js handled transparently. My build went green on the first try after npm install next-mdx-remote@6.
This is worth saying out loud because the “this is a major version, brace for migration” instinct can make people defer security patches longer than necessary. The actual cost of a major bump depends entirely on how much of the library's surface area you consume. A library you call from one place with three imports is going to be much cheaper to upgrade than one threaded through fifty files.
The lesson isn't “major bumps are always easy.” It's “measure your actual exposure before assuming you can't afford the bump.” A five-minute audit of your import sites tells you a lot.
The build cache that kept lying
After both patches were committed and pushed, Vercel kept failing the next deploy with the same error: Vulnerable version of next-mdx-remote detected (4.4.1). I checked the lockfile. Only v6 was present. I checked npm ls. Only v6. I checked the package.json. v6.
Vercel's build cache. The pre-build security scanner was reading the cached node_modules/ from a previous build — a build that ran before my patch landed. The cache was stale, the scanner was honest about what it saw, and there was no actual vulnerability in the codebase.
The fix was a redeploy with the “Use existing Build Cache” checkbox unchecked. Took thirty seconds. The hour I spent staring at it before figuring that out was less efficient.
Build caches and security scanners need to communicate. If the cache is read-through to the scanner, the scanner reports yesterday's state. If the scanner is supposed to be authoritative about “is this code safe to deploy,” cached install artifacts are the wrong input. Worth knowing what your platform actually does.
What I'm taking from this
A few rules I'm writing down for myself:
1. Don't use "latest" in package.json for anything load-bearing. Pin frameworks to exact versions; pin libraries to major.minor at minimum. Surprise upgrades are not a feature.
2. When a CVE drops, look for the vendor's targeted patch first. Their bot, their advisory, their recommended version. Major version jumps are migration work in disguise. Don't take on migration work in the middle of an incident.
3. Audit your actual API surface before fearing a major-version bump. “v4 to v6” sounds expensive. “Three imports across one file, all stable” is cheap.
4. Build caches lie when their inputs change. If a security scanner keeps flagging something you've fixed, suspect the cache before you suspect your fix.
5. Read the Vercel deploy log even when you're sure you fixed it. Half my confusion came from assuming the symptom would clear instantly.
None of these are novel. All of them are the kind of thing you only actually internalize once an incident makes you live them.
A note on what this site is
This is the first piece of writing on the new version of this site — or, more honestly, the first piece I didn't plan to write that wrote itself in twenty-four hours of cleanup. The site is positioned around cloud security, AI/ML, and the intersection of the two. A supply-chain CVE on a Next.js portfolio isn't exactly the AI-cloud frontier, but the underlying discipline (dependency hygiene, vendor advisory literacy, the cost of laziness in version management) is the same one I want to develop more rigor around. So this counts.
The next piece is the one I had originally planned: why I'm betting the next decade on the cloud and AI security intersection. That one's queued. This one came first because it was the one that demanded to be written.

