blog·4 min lezen

Next.js 16: wat stilletjes breekt na de upgrade

Geen foutmelding, ander gedrag: lint, scrollen, afbeeldingen en een React-regel die nu een error is. Wat wij tegenkwamen bij Next.js 16 en de oplossing.

Deze site draait op Next.js 16, net als de sites die we voor klanten bouwen. De meeste wijzigingen in versie 16 melden zich vanzelf: de build faalt en je weet waar je moet kijken. Een paar doen dat niet. Alles lijkt goed, tot iemand merkt dat iets anders werkt dan vorige week. Dit zijn de wijzigingen die ons tijd kostten, en een paar uit de upgrade-notities die op dezelfde manier verrassen.

1. next build controleert je code niet meer

In Next.js 15 draaide next build de linter mee. In 16 is next lint verwijderd en doet de build dat niet meer. Een lintfout die vroeg de build stopte, gaat nu ongemerkt naar productie.

De oplossing is kort: draai de linter zelf, vóór de build.

"scripts": {
  "lint": "eslint",
  "build": "next build"
}
npm run lint && npm run build

Next levert een codemod die de oude opzet omzet: npx @next/codemod@canary next-lint-to-eslint-cli .

2. setState in een effect is nu een error

Met de ESLint-configuratie van Next.js 16 (en eslint-plugin-react-hooks 7) is de regel react-hooks/set-state-in-effect een error. Dit patroon, dat jaren gewoon was, faalt nu de lint:

useEffect(() => {
  if (window.matchMedia("(max-width: 640px)").matches) setDevice("mobiel");
}, []);

We liepen er deze week nog tegenaan, bij een fotogalerij die op een telefoon standaard de mobiele weergave moest tonen. Voor een waarde die alleen in de browser bestaat, zoals een mediaquery, is useSyncExternalStore het juiste gereedschap:

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 ? "mobiel" : "desktop");

De derde functie geeft de waarde voor de server: daar bestaat geen scherm. Kiest de bezoeker zelf, dan wint die keuze. Voor puur visuele dingen, zoals een teller of een animatie bij het scrollen, is de DOM direct aanpassen via een ref vaak beter dan state.

3. Soepel scrollen maakt elke paginawissel traag

Staat scroll-behavior: smooth in je CSS op <html>? Vroeger zette Next dat tijdens een navigatie even uit, zodat een nieuwe pagina direct bovenaan begon. In 16 doet Next dat niet meer. Elke klik op een link wordt dan een langzame scroll naar boven.

Wil je het oude gedrag terug, zet dan een attribuut op <html>:

<html lang="nl" data-scroll-behavior="smooth">

4. priority op een afbeelding is verouderd

<Image priority> werkt nog, maar is verouderd ten gunste van preload. De documentatie zegt er meteen bij dat je in de meeste gevallen beter loading="eager" of fetchPriority="high" gebruikt. Bij een gewone <img> is fetchPriority="high" hoe dan ook de manier om de grote afbeelding bovenaan voorrang te geven.

Ook goed om te weten

Deze drie kwamen we niet zelf tegen, maar ze staan in de upgrade-notities en je merkt ze pas als je goed kijkt:

  • Afbeeldingskwaliteit. images.qualities staat standaard op alleen [75]. Een quality={90} op een afbeelding wordt dan stil 75, de dichtstbijzijnde toegestane waarde. Wil je meer, zet de waarden dan in next.config.
  • Afbeeldingen langer in de cache. images.minimumCacheTTL ging van 60 seconden naar 4 uur. Verander je een afbeelding op de bron zonder de naam te wijzigen, dan kan de oude versie nog uren blijven staan.
  • middleware heet nu proxy. Het oude bestand werkt nog, maar is verouderd. Hernoem middleware.ts naar proxy.ts en de functie naar proxy. Let op: proxy draait altijd op Node.js. Heb je de edge-runtime nodig, dan blijf je voorlopig bij middleware.

Laat je assistent eerst de meegeleverde documentatie lezen

Next.js 16 zet de documentatie in het pakket zelf, in node_modules/next/dist/docs/. De lijst met wijzigingen staat in 01-app/02-guides/upgrading/version-16.md. In onze Next-projecten staat in AGENTS.md dat Claude of Codex die docs leest voordat er code geschreven wordt. Een model kent vaak nog de oude versie van Next, en dan schrijft het met veel zelfvertrouwen code voor de vorige versie.

Een opdracht die daarbij helpt:

Lees node_modules/next/dist/docs/01-app/02-guides/upgrading/version-16.md en loop onze code na op alles wat daarin staat. Draai daarna npm run lint en npm run build en meld wat je aanpast.

Veelgemaakte fouten

Alleen op de build vertrouwen. Een groene build zegt in 16 niets meer over lintfouten.

De linter uitzetten omdat de nieuwe regel lastig is. De regel wijst meestal op een render te veel. useSyncExternalStore of een ref is bijna altijd de nette oplossing.

Een model laten upgraden zonder de docs. Dan krijg je code die in versie 15 klopte.

Bouw je sites met Claude of Codex en wil je ze via git live zetten? Lees van git push naar live of bekijk de webhosting.

MK
Maarten Keizer

Oprichter van Invoker. Ruim twintig jaar hosting, systeembeheer en webdevelopment; bouwt de Claude-hosting zelf en test alles eerst op de eigen servers.

over maarten