This site runs on Next.js 16, as do the sites we build for customers. Most changes in version 16 announce themselves: the build fails and you know where to look. A few do not. Everything looks fine, until someone notices something works differently than last week. These are the changes that cost us time, plus a few from the upgrade notes that surprise you in the same way.
1. next build no longer checks your code
In Next.js 15, next build ran the linter as part of the build. In 16, next lint is gone and the build no longer lints. A lint error that used to stop the build now goes to production unnoticed.
The fix is short: run the linter yourself, before the build.
"scripts": {
"lint": "eslint",
"build": "next build"
}
npm run lint && npm run build
Next ships a codemod that converts the old setup: npx @next/codemod@canary next-lint-to-eslint-cli .
2. setState in an effect is now an error
With the Next.js 16 ESLint config (and eslint-plugin-react-hooks 7), the rule react-hooks/set-state-in-effect is an error. This pattern, normal for years, now fails the lint:
useEffect(() => {
if (window.matchMedia("(max-width: 640px)").matches) setDevice("mobile");
}, []);
We ran into it again this week, in a screenshot gallery that had to default to the mobile view on a phone. For a value that only exists in the browser, such as a media query, useSyncExternalStore is the right tool:
const NARROW = "(max-width: 640px)";
const onChange = (cb: () => void) => {
const m = window.matchMedia(NARROW);
m.addEventListener("change", cb);
return () => m.removeEventListener("change", cb);
};
const [chosen, setDevice] = useState<Device | null>(null);
const narrow = useSyncExternalStore(onChange, () => window.matchMedia(NARROW).matches, () => false);
const device = chosen ?? (narrow ? "mobile" : "desktop");
The third function supplies the value on the server, where there is no screen. If the visitor picks a view, that choice wins. For purely visual things, like a counter or a scroll animation, updating the DOM directly through a ref is often better than state.
3. Smooth scrolling makes every page change slow
Do you have scroll-behavior: smooth in your CSS on <html>? Next used to switch it off briefly during navigation, so a new page started at the top straight away. In 16 Next no longer does this. Every link click becomes a slow scroll to the top.
To get the old behaviour back, put an attribute on <html>:
<html lang="en" data-scroll-behavior="smooth">
4. priority on an image is deprecated
<Image priority> still works, but is deprecated in favour of preload. The documentation immediately adds that in most cases you are better off with loading="eager" or fetchPriority="high". On a plain <img>, fetchPriority="high" is the way to give the large image at the top priority anyway.
Also worth knowing
We did not hit these three ourselves, but they are in the upgrade notes and you only notice them when you look closely:
- Image quality.
images.qualitiesnow defaults to[75]only. Aquality={90}on an image silently becomes 75, the closest allowed value. If you want more, list the values innext.config. - Images cached longer.
images.minimumCacheTTLwent from 60 seconds to 4 hours. If you change an image at the source without renaming it, the old version can stay around for hours. middlewareis now calledproxy. The old file still works but is deprecated. Renamemiddleware.tstoproxy.tsand the function toproxy. Note:proxyalways runs on Node.js. If you need the edge runtime, stay onmiddlewarefor now.
Let your assistant read the bundled docs first
Next.js 16 ships its documentation inside the package, in node_modules/next/dist/docs/. The list of changes is in 01-app/02-guides/upgrading/version-16.md. In our Next projects, AGENTS.md tells Claude or Codex to read those docs before writing any code. A model often still knows the old version of Next, and then writes code for the previous version with great confidence.
A prompt that helps:
Read node_modules/next/dist/docs/01-app/02-guides/upgrading/version-16.md and check our code for everything listed there. Then run npm run lint and npm run build and tell me what you change.
Common mistakes
Trusting the build alone. In 16 a green build says nothing about lint errors.
Turning the linter off because the new rule is annoying. The rule usually points at one render too many. useSyncExternalStore or a ref is almost always the clean fix.
Letting a model upgrade without the docs. You get code that was right in version 15.
Building sites with Claude or Codex and want to deploy them with git? Read from git push to live or see our web hosting.