Next.js has published the Scheduled Security Release it announced earlier this week, covering the 16.3.x and 15.5.x lines and fixing a critical severity vulnerability. The specifics stayed under wraps until the patch itself dropped — standard practice for coordinated disclosure — but the patched version ranges are now confirmed.
For most teams, the real work starts here. Running next upgrade is not the finish line. A security release is only "handled" once you've verified the patch actually landed everywhere it needs to, rolled it out safely, and tightened the process so the next one goes faster. This post focuses on that back half.
Verify the patch mechanically, not visually
Eyeballing package.json is not reliable in a monorepo or micro-frontend setup — a caret range can silently resolve to an unpatched minor, or a stale lockfile can mask the real installed version.
# Check the actually resolved version, not the declared range
npm ls next --all
# Pull just the next.js version out of the lockfile
node -e "
const lock = require('./package-lock.json');
for (const [key, pkg] of Object.entries(lock.packages)) {
if (key.endsWith('node_modules/next')) {
console.log(key, pkg.version);
}
}
"If you run multiple workspaces, wrap this in a script that walks every workspace — you'll reuse it for the next release.
Add a permanent version gate to CI
Rather than relying on someone remembering to check before deploy, block vulnerable version ranges directly in CI:
# .github/workflows/security-gate.yml
name: Next.js Version Gate
on: [pull_request]
jobs:
check-next-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Fail if next version is below patched range
run: |
RESOLVED=$(npm ls next --json | node -pe 'JSON.parse(require("fs").readFileSync(0,"utf8")).dependencies.next.version')
echo "Resolved next version: $RESOLVED"
npx semver "$RESOLVED" -r ">=16.3.3 <17.0.0 || >=15.5.24 <16.0.0" || {
echo "::error::next version does not satisfy the patched range"
exit 1
}Once this exists, the only maintenance for future releases is updating the version range — you're not rebuilding the check from scratch every time.
Watch logs and error rates closer than usual
Framework-level patches can shift internal behavior in middleware or the App Router in subtle ways. For the first few hours after deploying, it's worth watching more closely than a routine release:
- Sudden shifts in 5xx rates
- Latency regressions on requests that pass through middleware
- New request patterns getting blocked at the WAF or reverse proxy layer
On our own engagements, we keep dashboards pinned in a visible spot for the first few hours after a critical patch so a rollback decision can happen fast if something looks off.
Standardize a staged rollout
The instinct with a "critical" label is to push to every environment immediately. In practice, going through a canary step is usually faster overall, because it catches regressions before they hit 100% of traffic.
# Example: canary rollout on Vercel
vercel deploy --prod --skip-domain # verify on the preview URL first
# once stable, alias the preview deployment to the production domain
vercel alias set <preview-url> your-domain.comDocumenting this as a standing procedure — not something you improvise under pressure — removes a decision point from the timeline the next time a critical release lands.
Takeaways
- Verify patch application in CI, not by reading
package.json - Keep a permanent CI gate that blocks known-vulnerable version ranges
- Monitor at higher resolution than usual for the first few hours post-deploy
- Write down the canary rollout steps before you need them
Next.js has been shipping monthly security releases through 2026, and critical-severity ones like this will happen again. The value isn't in reacting well once — it's in turning today's response into a repeatable process for the next release.