# Next.js 16 Turbopack webpack Config Error: Don't Silence It

> Next.js 16 fails the build when it finds a webpack config, and the error suggests turbopack: {}. I tested it. The build passes — and the service worker silently disappears.

By Tahir Nazir · Published 2026-09-25 · Updated 2026-10-08
Canonical: https://www.its-tahir.com/blog/nextjs-16-turbopack-webpack-config-error

If `next build` just failed with this, you're on Next.js 16 and something in your config — usually a plugin — is using webpack. **Don't silence it with the `turbopack: {}` the error suggests until you know what it turns off:** that makes Turbopack ignore the webpack config silently, and `next build --webpack` is the fix that keeps the plugin working.

```text
⨯ ERROR: This build is using Turbopack, with a `webpack` config and no `turbopack` config.
   This may be a mistake.

   As of Next.js 16 Turbopack is enabled by default and
   custom webpack configurations may need to be migrated to Turbopack.

   NOTE: your `webpack` config may have been added by a configuration plugin.
   ...
   TIP: Many applications work fine under Turbopack with no configuration,
   if that is the case for you, you can silence this error by passing the
   `--turbopack` or `--webpack` flag explicitly or simply setting an
   empty turbopack config in your Next config file (e.g. `turbopack: {}`).
```

The quickest fix is the one the error hands you: add `turbopack: {}`. Here's what it actually turns off.

I reproduced this on this site, which runs Next.js 16.1.1 with `@serwist/next` 9.5.12 for offline support. With `turbopack: {}` added, the build went green — every page generated, exit code 0. And `public/sw.js`, the service worker, was never written. The site would have deployed looking perfect, with its offline mode quietly gone.

## Why you're seeing it when you never wrote a webpack config

Next.js 16 made Turbopack the default for both `next dev` and `next build`. The upgrade guide is explicit about what happens next:

> If your project has a custom `webpack` configuration and you run `next build` (which now uses Turbopack by default), the build will **fail** to prevent misconfiguration issues.

Most people hitting this never wrote a `webpack` function. A **plugin** wrapped their config and added one. The error even says so — *"your `webpack` config may have been added by a configuration plugin"* — and so does the upgrade guide.

`withSerwistInit` from `@serwist/next` is one. Payload CMS's `withPayload` is another, [tracked in its own issue](https://github.com/payloadcms/payload/issues/14354). Any `with…()` wrapper around your Next config is a suspect.

That's the important part. The failure isn't Next.js being fussy. It's Next.js telling you: *a plugin wants to do work in webpack, and Turbopack is about to skip that work.*

## What each escape hatch actually does

The error and the docs offer three ways out. They are not equivalent.

| Fix | Bundler used | Your webpack config |
| --- | --- | --- |
| `turbopack: {}` in next.config | Turbopack | **Ignored, silently** |
| `next build --turbopack` | Turbopack | **Ignored, silently** |
| `next build --webpack` | webpack | Runs as before |

The upgrade guide describes the `--turbopack` option in plain words: *"build using Turbopack and ignore your `webpack` config."* That's accurate. The problem is that "ignore" doesn't produce an error, a failed step or a missing page. It produces a smaller build.

## The test: green build, no service worker

Here's what I ran, on the same codebase, same commit.

**With `turbopack: {}` added to `next.config.ts`:**

```bash
$ npx next build
▲ Next.js 16.1.1 (Turbopack)
✓ Compiled successfully
✓ Generating static pages using 7 workers (127/127)

$ ls public/sw.js
ls: cannot access 'public/sw.js': No such file or directory
```

**With `next build --turbopack` and no config change:** identical result. Exit code 0, all 127 pages, no `sw.js`.

**With `next build --webpack`:** `public/sw.js` is generated as normal — about 45 KB.

There was exactly one clue in the Turbopack runs, printed at the very top of the log:

```text
[@serwist/next] WARNING: You are using '@serwist/next' with `next dev --turbopack`,
but it doesn't support Turbopack.
```

Read that again: it says **`next dev`**, during a **production build**. In a CI log that scrolls past in a second, a warning about the dev server is exactly the kind of line you learn to ignore. The warning also tells you how to suppress it with an environment variable — which would remove the only signal left.

### Why nothing breaks in the browser either

You might expect the missing file to surface as an error once deployed. It usually won't.

The standard registration pattern — and the one this site uses — catches failures so a service worker problem never breaks the page:

```ts
navigator.serviceWorker.register("/sw.js", { scope: "/" }).catch(() => {
  // Offline support is a progressive enhancement; never break the page for it.
});
```

That's the right call for users. It also means a 404 on `/sw.js` produces no visible error at all. **The page loads, the app works online, and the only symptom is that offline doesn't** — which nobody tests on a normal deploy.

It gets worse for returning visitors. When a browser's periodic update check for an existing service worker gets a 404, the update fails and the **previously installed worker stays in place**. Users who installed your app before the change keep running the old worker and its old precache. New visitors get none. Two populations, and no error in either.

## Why Turbopack can't just run the plugin

Webpack configuration is JavaScript that runs *inside* webpack — plugins hook into the compilation and emit extra files. Service worker generation is exactly that: Serwist compiles `sw.ts`, injects the precache manifest, and writes `sw.js`.

Turbopack is a different bundler written in Rust. It supports a subset of webpack **loaders** through `turbopack.rules`, but not webpack **plugins**. Even for loaders, the Next.js docs list `emitFile` — the API for writing extra output files — as *"No support"*. Being a separate Rust bundler shows up elsewhere too: behind a company proxy, Turbopack downloads fonts with its own HTTP client, which is why [Next.js fails to fetch Google Fonts under Turbopack](/blog/nextjs-failed-to-fetch-google-fonts-corporate-proxy) when the same certificate setup works under webpack.

So there's no config you can add to make `@serwist/next` work under Turbopack. The plugin's work needs a different mechanism entirely.

## Fix 1: build production with webpack (what I did)

This is the smallest change and the one the upgrade guide documents directly:

```json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build --webpack",
    "start": "next start"
  }
}
```

Turbopack stays on for development, webpack runs the production build, and the plugin works exactly as it did on Next.js 15.

The dev side is fine because Serwist is normally disabled outside production anyway:

```ts
const withSerwist = withSerwistInit({
  swSrc: "src/app/sw.ts",
  swDest: "public/sw.js",
  disable: process.env.NODE_ENV !== "production",
});
```

This site passes `--webpack` to `next dev` as well, for parity between dev and production. You lose Turbopack's dev-server speed, and whether that trade is worth it depends on how big your app is.

The cost of Fix 1 is that you're opting out of the default. The docs say plainly: *"We recommend using Turbopack for development and production."* Treat `--webpack` as a stable place to stand while you plan Fix 2, not as a permanent answer.

## Fix 2: migrate to @serwist/turbopack

Serwist's maintainers resolved the Turbopack request with a **separate package** rather than changing `@serwist/next`. It doesn't hook into the bundler at all — it compiles the service worker with esbuild through a Next.js route handler.

The shape of the migration, from the Serwist docs:

```bash
npm i -D @serwist/turbopack esbuild serwist
```

- Wrap your config with `withSerwist` from `@serwist/turbopack`.
- Add a route handler at `app/serwist/[path]/route.ts` using `createSerwistRoute()`, pointing `swSrc` at your worker.
- In `sw.ts`, import from `@serwist/turbopack/worker` instead of `@serwist/next/worker`.
- Register with `SerwistProvider` from `@serwist/turbopack/react`, using `swUrl="/serwist/sw.js"`.

**Watch the URL change.** The worker moves from `/sw.js` to `/serwist/sw.js`. Anyone who installed the old worker keeps it until you deal with it — the update-404 behaviour above applies here too. Either keep serving something at the old URL, or have the new registration code unregister the old worker explicitly. And if you have a proxy (the Next.js 16 name for middleware) that excludes `/sw.js`, update it to skip `/serwist/` instead.

## Fix 3, whichever you pick: make the build fail loudly

The real lesson isn't about Serwist. It's that **a green build proved nothing**, because the failure mode was "less output", and nothing was checking the output.

A one-line guard closes that:

```json
{
  "scripts": {
    "build": "next build --webpack",
    "postbuild": "node -e \"require('fs').statSync('public/sw.js')\""
  }
}
```

`statSync` throws if the file is missing, which fails the step and fails the deploy. It takes a second to run.

The same idea generalises to any plugin you're unsure about. Run the build **both ways** and compare what comes out:

```bash
npx next build --webpack   && find public .next/static -type f | sort > /tmp/webpack.txt
npx next build --turbopack && find public .next/static -type f | sort > /tmp/turbo.txt
diff <(sed 's/[a-f0-9]\{8,\}//g' /tmp/webpack.txt) <(sed 's/[a-f0-9]\{8,\}//g' /tmp/turbo.txt)
```

Hashed chunk names will differ between bundlers, which is why the hashes are stripped. What you're looking for is a file that exists on one side only — like `public/sw.js`.

## The short version

The error is Next.js doing you a favour. It noticed that a plugin wanted to do work that Turbopack won't do, and it stopped rather than guess. The suggested one-line fix removes that protection, and for any plugin that *generates files*, the result is a build that succeeds with something missing.

Choose `--webpack` if you need the plugin working today. Migrate to the plugin's Turbopack-native version when you have time. And either way, check for the output, not just the exit code.

**Key takeaways**

- Next.js 16 builds with Turbopack by default and fails the build when a webpack config exists — usually one injected by a plugin you installed, not one you wrote.
- turbopack: {} and next build --turbopack both make Turbopack ignore the webpack config silently. With @serwist/next, the build passes and public/sw.js is never generated.
- The only signal was a Serwist warning that mentions next dev during a production build — easy to miss, and suppressible.
- next build --webpack keeps plugins working; @serwist/turbopack is the Turbopack-native replacement, but it moves the worker from /sw.js to /serwist/sw.js.
- Add a postbuild check that the files you depend on exist. A green build only proves the build finished, not that it produced everything.

This came out of the same Next.js codebase as my [indexing case study](/blog/nextjs-site-not-indexed-by-google). If you're planning a Next.js 16 upgrade and want it done without silent regressions, [see my work](/work).

## FAQ

### What does 'This build is using Turbopack, with a webpack config and no turbopack config' mean?

Next.js 16 builds with Turbopack by default. When it finds a webpack function in your Next config — usually added by a plugin such as @serwist/next rather than written by you — it stops the build, because Turbopack does not run webpack configuration and whatever that config does would otherwise be skipped.

### Is it safe to add turbopack: {} to silence the error?

Only if you have confirmed the webpack config does nothing you need. An empty turbopack config makes Next.js build with Turbopack and ignore the webpack config entirely. With @serwist/next, the build succeeds and the service worker file is simply never generated. Compare the build output with and without the change before shipping it.

### Does Serwist work with Turbopack in Next.js 16?

@serwist/next does not — it generates the service worker inside a webpack plugin, so it needs next build --webpack. Serwist ships a separate package, @serwist/turbopack, that builds the worker through a Next.js route handler with esbuild instead, and serves it from /serwist/sw.js rather than /sw.js.

### How do I keep Turbopack for next dev but webpack for production builds?

Pass the flag per script: keep "dev": "next dev" and set "build": "next build --webpack". The Next.js 16 upgrade guide documents exactly this split. If the plugin is disabled outside production, as Serwist's is by default, development loses nothing.

## Sources

- [How to upgrade to version 16 — Next.js docs](https://nextjs.org/docs/app/guides/upgrading/version-16)
- [next.config.js: turbopack — Next.js docs](https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack)
- [Turbopack — Serwist docs](https://serwist.pages.dev/docs/next/turbo)
- [[Feature request]: next dev --turbo support (serwist/serwist #54)](https://github.com/serwist/serwist/issues/54)
- [withPayload unconditionally injects webpack config (payloadcms/payload #14354)](https://github.com/payloadcms/payload/issues/14354)
- [The service worker lifecycle — web.dev](https://web.dev/articles/service-worker-lifecycle)
