Skip to content
DERKONLINE

Fix Next.js 15 Pages That Render Unstyled Only in Safari

A Turbopack and Tailwind v4 dev-bundler bug drops your CSS in Safari; default dev to webpack and WebKit renders correctly again.

Derrick S. K. Siawor7 min read

You finish a feature, it looks perfect in Chrome, and then someone on the team opens it in Safari and sends a screenshot that looks like 1999. The HTML is all there. The fonts loaded. But none of your Tailwind classes apply, so the page renders as raw browser-default chrome, unstyled, structurally intact, and visually broken. Same code, same dev server, only Safari is wrong.

Your Next.js 15 page renders unstyled only in Safari because Turbopack's dev bundler emits Tailwind v4 CSS that WebKit fails to apply, while Chrome and Firefox apply it fine. The fix is to default your dev script to the webpack bundler instead of Turbopack, keeping Turbopack as an opt-in dev:turbo script for the speed when you are not testing in Safari.

The first instinct is to hunt for a CSS bug in your own code, and you can lose an afternoon there, because there is no bug in your code. This is an upstream issue in how Next.js 15's Turbopack dev bundler emits CSS for Tailwind v4, and it manifests only in Safari. The fix is one line in your dev script, and once you know it the problem evaporates. The trap is recognizing it, because every signal points at your stylesheet when the cause is the bundler.

The exact symptom, so you do not chase the wrong thing

Before the fix, learn the fingerprint, because the value of this is recognizing it in two minutes instead of two hours.

  • The page loads fully in Safari, including fonts and content. It is not a blank screen or a failed request. The structure is there.
  • Tailwind utility classes silently do not apply. The page looks unstyled, like the CSS never loaded, even though the markup references the classes.
  • It works perfectly in Chrome and other browsers with the exact same setup. This is Safari-only and WebKit-only.
  • You are on Next.js 15 with Tailwind v4 and running the Turbopack dev bundler.

There is one more confirming detail that rules out the obvious explanation. If you fetch the served CSS chunk directly, the Tailwind utilities are present in the file. The CSS is correct and fully generated. Safari is being served the right stylesheet and simply does not apply it. That is the tell that this is not a missing-CSS problem or a build problem, it is a compatibility problem in how the CSS was emitted, and Safari is the strict browser that refuses what Chrome tolerates.

What is actually going wrong

This is a known upstream issue, tracked as Next.js issue #71923, and it is confirmed, not a quirk of your project. The mechanism: Turbopack, the new dev bundler in Next.js 15, does not compile Tailwind v4's CSS output correctly for Safari and WebKit.

Tailwind v4 leans heavily on modern CSS features, custom properties, @property declarations, and @layer ordering, to produce its utility system. Turbopack's emission of those features has a subtle Safari-only compatibility bug. Chrome and Firefox apply the emitted CSS fine. Safari does not, because Safari is stricter about exactly the constructs Tailwind v4 depends on, and Turbopack's handling of them does not satisfy that strictness. The CSS is all there in the served file, it is just emitted in a way WebKit will not apply.

The most useful thing to know is what is not affected. Your production build is fine. In Next.js 15, next build uses webpack rather than Turbopack, and the webpack-emitted CSS works correctly in Safari. So this is strictly a Turbopack-the-dev-bundler plus Safari-the-target problem. It does not reach production, it does not affect Chrome, and it is not your code. It is a dev-only, Safari-only, Turbopack-only bug, which is exactly the kind of narrow intersection that makes it so hard to diagnose from the symptom alone.

The fix: default dev to webpack

Because the production build already uses webpack and works, the fix is to make your development server use webpack too, and keep Turbopack as an opt-in for contributors who only test in Chrome and want the faster compile.

In your package.json, drop the Turbopack flag from the default dev script and add a separate opt-in script for those who want it:

{
  "scripts": {
    "dev": "next dev -p 3040",
    "dev:turbo": "next dev -p 3040 --turbopack"
  }
}

Now pnpm dev runs the webpack dev bundler, which emits CSS that Safari applies correctly, and pnpm dev:turbo is there for the roughly twice-as-fast Turbopack compile when someone is working in Chrome and does not need Safari to render. Production is unaffected either way, because next build was already webpack-only. The whole fix is changing which bundler your default dev script uses, and Safari renders correctly again immediately. While you are in that dev script, it is worth making it bring up your whole stack in one command so a new contributor gets a correct, Safari-safe environment from a single pnpm dev.

This is a workaround for an upstream bug, not a permanent architectural choice, so it is worth revisiting when the Turbopack team ships a fix for issue #71923. Until then, defaulting Next.js 15 plus Tailwind v4 projects to webpack dev is the reliable choice, and we bake it into the scripts that are the source of truth on any project with that stack so the bug never reaches a developer who uses Safari. The web applications we ship default dev to webpack from the first commit.

Why this is the second Safari-only trap, and how to think about them

This bug rarely travels alone. Safari is consistently the strict browser, the one that honors specifications and behaviors that Chrome and Firefox treat as no-ops, so it is the browser that exposes a whole family of "works in Chrome, broken in Safari" issues. When you hit a Safari-only rendering problem on a local dev server, there are usually two distinct causes to check, and they have nothing to do with each other.

The first is this one: the Turbopack-versus-webpack CSS emission bug, where the page loads, fonts work, but utility classes silently do not apply. The fix is the dev-bundler switch above.

The second is a separate trap where the page renders genuinely broken because of security headers that Safari alone enforces on localhost, covered in full in why your dev site breaks in Safari but not Chrome on localhost. Safari honors both the CSP upgrade-insecure-requests directive and Strict-Transport-Security on loopback, where Chrome treats them as no-ops, so if either of those headers is set in development, Safari upgrades your http://localhost asset requests to https://, finds no TLS on the dev server, and the assets fail to load. That one persists even after you fix the code, because Safari caches the HSTS entry, and clearing it requires the operator to remove the localhost entry from Safari's website data. Different mechanism, different fix, same "Safari is the strict one" pattern.

The practical move when Chrome works and Safari does not on a dev server is to check both. Look at whether you are running Turbopack with Tailwind v4 (this article's bug), and curl the dev server's headers to see whether strict-transport-security or a CSP upgrade-insecure-requests is present in development (the other one). Knowing both exist turns a Safari mystery into a two-item checklist, and once a site is in production the same browser is behind Safari 520 errors when Chrome works on your nginx server, the Safari-only 520 that large auth cookies quietly cause, and why your site returns 520 in Safari but works in Chrome.

The recognition, condensed

Page loads in Safari, fonts and content present, but Tailwind classes silently do not apply, and Chrome is fine, on Next.js 15 with Tailwind v4 and Turbopack. That fingerprint means Turbopack emitted Tailwind v4's CSS in a way WebKit will not apply, confirmed upstream as issue #71923. The served CSS chunk has all the utilities, so it is an emission bug, not a missing-stylesheet bug. Default your dev script to webpack, keep Turbopack as an opt-in, and Safari renders correctly, while production was never affected because next build already uses webpack.

The afternoon you save is the one you would otherwise spend convinced there is a bug in your own CSS. There is not. It is the bundler, it is only Safari, and it is one line in your dev script.