# Turbopack Ate My Webpack Plugin: Fix or Flee

> Turbopack default breaks webpack-only plugins at build. Find equivalents, bridge the gap, or opt out cleanly. All three paths inside.

Source: https://boringpush.com/blog/nextjs-16-turbopack-breaks-webpack-build

## The build died on someone else's plugin

Upgrade day. New major, Turbopack now default, build starts — and dies on a 2023-era SVG sprite plugin: "unknown loader," no equivalent, no migration path. A dependency chosen in 2023, maintained by a stranger, now vetoes the entire toolchain upgrade.

The shim tried (broken differently), the fork tried (abandoned twice) — and rewriting SVG handling by hand at midnight briefly tempted (the worst ideas arrive tired). The plugin wasn't critical — it optimized icons anyone could inline. But it stood between the app and the major like a toll booth with no attendant.

Turbopack breaks webpack builds exactly at these seams: custom loaders, exotic plugins, config hacks accumulated across majors. Speed costs compatibility; the bill arrives on upgrade day.

## Bundler breakage classes

| Breakage | Symptom | Fix shape |
|---|---|---|
| Custom loader, no equiv | "Unknown loader" at compile | Find equiv or inline the work |
| Webpack plugin API use | Silent wrong output | Replace with supported hook |
| Config hacks (`webpack:` fn) | Ignored silently | Re-express or drop |
| Performance assumptions | Slower dev (ironic) | Actually faster (most cases) |
| Build-only plugin | Prod fails, dev fine | Gate to webpack or replace |

Three things the toll booth taught me:

1. **Config hacks accrue like debt.** Each `webpack:` customization felt clever once and became a migration liability silently — five years of clever totals one blocked upgrade. Config surface is liability surface; every custom line needs an owner and an expiry review. Boring configs migrate; clever ones veto.
2. **Plugin authors owe you nothing.** Maintained-by-stranger means roadmap-by-volunteer — Turbopack support arrives when someone cares enough, which correlates poorly with your launch calendar. Depending on exotic plugins is depending on strangers' weekends. Audit dependencies for bus factor before majors, not during.
3. **Speed masks the real question.** Turbopack is genuinely faster (dev loops halved in measurement), which makes the plugin gap feel like ingratitude — complaining about free speed. Separate the ledgers: speed is Turbopack's gift, compatibility is your config's debt. Accept the gift, pay the debt deliberately, don't confuse the accounts.

The corridor post ordered the upgrade. This one pays the bundler toll.

## Turbopack breaks webpack resolution

Equivalent, bridge, or opt-out — in that order of preference, with honor intact.

**Step 1 — Find the Turbopack equivalent first.**

```bash
# inventory custom webpack usage (10 min, complete picture):
grep -rn "webpack" next.config.js; grep -rln "loader" next.config.js
# per item: check Turbopack docs for native support (most common loaders covered)
# SVG sprites → SVGR equiv or inline SVGs (better anyway: no runtime, no plugin)
```

Most gaps close here: common loaders (SVG, images, CSS extras) have native or documented equivalents, often better than the originals (inline SVG beats sprite plugins on every axis that matters). My sprite plugin died this way — replaced by inline SVGs in an afternoon, bundle smaller, zero plugins. Equivalents first because they're upgrades disguised as chores.

**Step 2 — Bridge what lacks equivalents (temporarily, dated).**

```js next.config.js
// ONLY for the gap with no equiv, with expiry date in comments:
// TODO(turbopack-equiv): remove after plugin X publishes support (tracked: link)
// prefer: move the work out of bundling (pre-build script > loader hack)
```

Bridges are dated technical debt with owners and links — not permanent hacks wearing temporary names. Pre-build scripts (generate sprites before `next build`, commit outputs) beat loader hacks structurally: bundler-independent, debuggable, portable. Bridge while tracking upstream; each release, re-check the equiv. Temporary with a calendar is planning; without one it's surrender.

**Step 3 — Opt out cleanly where principles cost too much.**

```js next.config.js
experimental: { turbo: false }   // legitimate, documented, honorable
// + calendar entry: re-evaluate quarterly (plugin landscape moves fast)
// + CI note: team knows WHY (linked issue), not just THAT
```

Opt-outs are valid engineering when priced (slower dev loop) against principles (shipping this quarter). Document the why beside the flag — future-you deciding whether the toll is paid yet needs reasons, not just settings. Quarterly re-evaluation keeps opt-outs temporary by process instead of permanent by inertia. Retreats with calendars are advances scheduled.

That's what flat bills buy — fast bundlers, zero hostages.

## The tollbooth ledger

| Plugin gap | Path taken | Outcome |
|---|---|---|
| SVG sprites | Equivalent (inline) | Smaller bundle, no plugin |
| Legacy CSS plugin | Bridged (dated TODO) | Shipped, equiv tracked |
| Exotic analyzer | Opt-out briefly | Re-enabled next quarter |
| Total upgrade delay | — | 1 afternoon (not 1 quarter) |
| Config lines after | Fewer than before | (Upgrades get easier yearly) |

BoringPush upgrades pay tolls deliberately.

The honest bill: equivalents cost afternoons (rewriting working config that wasn't broken), bridges cost vigilance (tracking upstream, honoring expiries), opt-outs cost speed (the fast bundler waits). Priced against a pinned old major (security patches ending, features frozen, debt compounding): tolls paid deliberately beat taxes accrued silently. Pay at the booth, keep the receipt, drive fast after.

## FAQ

### Is turbopack breaks webpack permanent or transitional?

Transitional with a long tail: mainstream loaders gain equivalents steadily, exotic corners lag indefinitely (maintainer economics). The tail never fully closes — some 2021 plugin will always lack support. Strategy accordingly: equivalents for the mainstream (permanent fix), bridges/opt-outs for the tail (managed indefinitely). Transitional for most, permanent for edges — know which edge you're on.

### Should new projects start Turbopack-first?

Yes — defaults are defaults for a reason (speed, future-proofing, community momentum), and greenfield avoids tollbooths entirely (no legacy config to migrate). New projects inherit zero plugin debt by construction. Start default, add custom config only with expiry dates attached. Greenfield discipline prevents brownfield tolls.

### How do I audit plugin bus factor before it matters?

Quarterly dependency review: last-commit dates, issue responsiveness, maintainer count, Turbopack-compat statements. Single-maintainer-unresponsive-six-months means plan the exit now (equivalent research, bridge sketch) before it becomes urgent. Bus-factor audits are cheap insurance; tollbooth surprises are expensive education. Audit calm, migrate calmer.

### Can Turbopack and webpack coexist during transition?

Per-build only (one bundler per compile), but branch-stratified: main on webpack (stable), migration branch on Turbopack (proving), converge on green. Dual-track briefly, merge deliberately. Transitions are bridges with traffic: old span carries prod while new span load-tests. Never big-bang bundlers on launch weeks — boring migrations, exciting results.

### What about Rsbuild/Rolldown/other bundlers — switch instead?

Different tollbooths, same economics: every bundler taxes exotic configs at migration time. Switching bundlers to dodge one plugin gap usually trades known gaps for unknown ones plus ecosystem churn. Fix the gap (equivalent/bridge/opt-out) unless the new bundler wins on sustained merits (speed + compat + momentum). Tollbooth-hop rarely; commit deliberately.

### When is pinning the old major the right call?

Revenue weeks and deep plugin entanglement (three-plus irreplaceable gaps): pin with a calendar date (next quiet quarter, non-negotiable), security-patch awareness (know what pinning forgoes), and active equiv tracking (pinning is waiting with homework). Pinned-with-plan beats migrated-in-panic; pinned-forever is just debt with a version number. Pin deliberately, unpin scheduled.

## Keep reading

- [14 to 16 Upgrade: Caching Broke Everything](/blog/nextjs-14-to-16-upgrade-cache-pain) — the corridor this toll sits in
- [Next 16 Async Params Broke My Build: Fix](/blog/nextjs-16-async-params-breaking) — step zero of the same migration
- [Next.js Dev Takes 30 Seconds to Boot? Fix That](/blog/nextjs-dev-slow-30s-boot-hot-reload) — the speed Turbopack buys back
