Hosting·4 min read

Next.js 16 on an older iPhone: the page does nothing

Next.js 16 supports Safari 16.4 and up. On an iPhone running iOS 15 the script stops with a syntax error and the page never becomes interactive. Here is how to measure whether it affects you, and how to fix it.

Look at the analytics for this website over the past three months. Which
operating systems and browser versions do my visitors use, and how many
sessions came from iOS 15 or older?

You put a new Next.js site live, everything works, and then someone with an older iPhone calls. The page loads, but nothing happens. Menus do not open, forms do not submit, buttons do not respond. On every other phone in the office it works fine.

That is almost always the same problem, and it is not a mistake in your own code.

What happens

Next.js 16 assumes your visitor has Safari 16.4 or newer, which is Safari from the spring of 2023. The code that Next itself ships contains a piece of modern JavaScript that older Safari versions do not know: a class static block.

A browser reads a script file from start to finish before it does anything with it. If it hits something it cannot read, it drops that entire file. On iOS 15 the console of that phone shows this:

SyntaxError: Unexpected token '{'

That takes out the code that is supposed to take over the page in the browser. The HTML from the server is still there, so you see text and images, but React never takes over. That takeover is called hydration, and without hydration your page is a picture of a website.

Note the difference with React errors 418, 423 and 425. Those are about hydration that does happen and repairs itself. This is one step earlier: the script is never executed at all.

Measure first, then fix

Before you change anything, find out how many visitors this affects. Your own analytics will tell you, broken down by operating system and version. On a site we monitor that was 0 out of 515 sessions over 90 days on iOS 15 or older.

Zero is a pleasant number, because then the right decision is to do nothing and write it down so you remember it when someone calls a year from now. If you do see a few percent, for instance with an older audience or a site that gets shared a lot on older devices, it is worth the work.

What you should not do is decide this on instinct. "Nobody uses iOS 15 any more" is an assumption, and if you are wrong it is a silent outage: those visitors report nothing, they leave.

The fix

In package.json you add a browserslist with the oldest browsers you want to serve:

"browserslist": [
  "chrome 111",
  "edge 111",
  "firefox 111",
  "safari 15"
]

This works because Turbopack applies that list to Next's own code as well, not just to your files. The class static block is then rewritten into something Safari 15 can read. We tested it and the syntax error goes away.

Two things to know alongside it. You are stepping outside what Next.js officially supports, so test your site on such a device or simulator before you go live. And you are serving slightly older, slightly larger JavaScript to everyone. That difference is small, but it is real, which is why the measurement in the previous section is not a formality.

How you find it in practice

This problem rarely lands neatly in an error log. It is not a React message with a number and it is not a server error: it happens in one visitor's browser before your own error reporter has loaded. In practice you find it in three ways.

  • A report from a visitor who says the site "does not respond" rather than that there is an error message.
  • The console of the device itself, if you can borrow one or open a simulator.
  • Your analytics, where a whole group of devices shows a strikingly high share of single page visits and almost no follow-up steps.

The third is the most useful, because it works without the device in your hand.

Common mistakes

"I will clear the cache, that should fix it." This is not in the cache. The same file fails on that device every time, and works fine on every other device.

"I see a blank screen, so the server is broken." First check whether the HTML is there. If text arrives but nothing works, the problem is in the browser and not on the server. If nothing arrives, it is something else.

"I add a browserslist and I am done." Not without a test. After the change, check that the site really works on the old device, and that it has not become something different on a new phone.

"I will solve it with a message telling the visitor to update." You can, but keep in mind that the script that would show that message is often in the same file that fails to load. A warning you put in the server HTML does arrive.

"This only applies to Next.js." The pattern applies to any built frontend project: the floor set by your build tooling quietly decides who can use your site. Look that floor up before you upgrade, not after someone calls. For the rest of what changed in this version: Next.js 16, what breaks silently.

MK
Maarten Keizer

Founder of Invoker. Over twenty years in hosting, system administration and web development; builds the Claude hosting himself and tests everything on our own servers first.

about maarten