How to Fix: loading the next.config.ts in section experimental.swcPlugins panics

6 min read

Next.js panic when using experimental.swcPlugins from next.config.ts: cause, fix, and safe workarounds

If Next.js crashes or panics as soon as you define experimental.swcPlugins inside next.config.ts, the problem is usually not your plugin logic. The failure happens earlier, during how Next.js loads and serializes config for the Rust-powered SWC pipeline. In practice, the issue appears when a TypeScript-based Next config is used together with SWC plugin configuration that Next expects in a plain, stable runtime shape.

The reproduction linked in the issue demonstrates that simply installing plugins such as @swc/plugin-noop or swc-plugin-pre-paths and referencing them in experimental.swcPlugins can trigger a panic when the config file is next.config.ts. This tutorial explains why it happens, how to fix it reliably, and which edge cases to check if the first fix does not resolve it.

Symptoms

You are likely hitting this exact bug if all or most of these are true:

  • Your project uses Next.js with a config file named next.config.ts.
  • You define experimental.swcPlugins with one or more SWC plugins.
  • Starting the dev server or build process causes a panic, crash, or internal error instead of a normal validation message.
  • The same plugin setup may behave differently or stop crashing when moved to next.config.js or next.config.mjs.

Reference reproduction: sample repository from the GitHub issue.

Understanding the Root Cause

This bug is caused by an interaction between Next.js config loading, TypeScript config transpilation, and the experimental handoff of SWC plugin definitions into Next’s internal Rust tooling.

At a high level, experimental.swcPlugins is not just another JavaScript option. It is consumed by an internal compilation pipeline that expects a very specific, fully serializable structure, typically an array shaped like this:

experimental: {
  swcPlugins: [
    ['plugin-name', { /* plugin options */ }]
  ]
}

When the config lives in next.config.ts, Next must first compile and evaluate that TypeScript file before it can pass the config onward. That extra config-loading layer can alter timing, module format, object shape, or serialization assumptions in ways that the experimental SWC plugin loader does not handle robustly. Instead of gracefully rejecting the config, the lower-level Rust side may panic because it receives something unexpected or unsupported.

Important details:

  • experimental.swcPlugins is still experimental, so its error handling is less defensive than stable config options.
  • next.config.ts adds a transpilation step before runtime evaluation.
  • SWC plugin loading often depends on stable module resolution, plugin binary compatibility, and predictable JSON-like config values.
  • A panic usually indicates an internal assumption failed in native code, not necessarily that your plugin options are semantically wrong.

In short: the root problem is the combination of TypeScript-based Next config and experimental SWC plugin config loading, not merely the plugin package installation itself.

Step-by-Step Solution

The safest and most reliable fix is to move your Next configuration from next.config.ts to a JavaScript-based config file such as next.config.js or next.config.mjs.

1. Rename the config file

Change this:

next.config.ts

To one of these:

next.config.js

or

next.config.mjs

2. Export a plain config object

If you use CommonJS:

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    swcPlugins: [
      ['@swc/plugin-noop', {}]
    ]
  }
}

module.exports = nextConfig

If you use ESM:

const nextConfig = {
  experimental: {
    swcPlugins: [
      ['@swc/plugin-noop', {}]
    ]
  }
}

export default nextConfig

3. Test with the smallest possible plugin config

Before adding your real plugin options, verify that the panic disappears with a minimal config:

const nextConfig = {
  experimental: {
    swcPlugins: [
      ['@swc/plugin-noop', {}]
    ]
  }
}

module.exports = nextConfig

If this works, the crash was caused by the config loading path, not the plugin package itself.

4. Add your real plugin carefully

For example:

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    swcPlugins: [
      ['swc-plugin-pre-paths', {
        basePath: './src'
      }]
    ]
  }
}

module.exports = nextConfig

Keep plugin options strictly JSON-serializable. Avoid functions, class instances, imported runtime objects, or environment-dependent computed values until you confirm a stable baseline.

5. Clear cache and reinstall if needed

After changing config file type, remove build artifacts and reinstall dependencies to avoid stale state:

rm -rf .next node_modules
rm -f yarn.lock package-lock.json pnpm-lock.yaml
yarn install
yarn dev

Use your package manager consistently. If you use npm or pnpm, replace the install commands accordingly.

6. Pin compatible versions if the problem persists

Because SWC plugins are tightly coupled to compiler internals, version mismatches can look similar to this bug. Check your versions of:

  • next
  • @swc/core if directly installed
  • the SWC plugin package itself

Then test after aligning them. If possible, start from the exact versions used in a known-working example.

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    swcPlugins: [
      ['@swc/plugin-noop', {}]
    ]
  }
}

module.exports = nextConfig

This is the most stable workaround until Next.js fully supports this path with next.config.ts for SWC plugin loading.

Common Edge Cases

1. The plugin name resolves, but the binary is incompatible

Some SWC plugins depend on a specific ABI or compiler version. Even after moving to next.config.js, you may still see crashes if the plugin was built against a different toolchain version.

What to do:

  • Upgrade or downgrade the plugin package.
  • Align Next.js and plugin versions.
  • Test first with @swc/plugin-noop to isolate whether the issue is config-related or plugin-specific.

2. Plugin options are not plain objects

If you pass values derived from code execution, imported helpers, or non-serializable structures, the config may still fail.

Bad pattern:

const options = new Map()

module.exports = {
  experimental: {
    swcPlugins: [['some-plugin', options]]
  }
}

Safer pattern:

module.exports = {
  experimental: {
    swcPlugins: [['some-plugin', { enabled: true }]]
  }
}

3. Mixed ESM and CommonJS confusion

If your project uses ESM globally, but your config export style does not match the file extension, Next may fail before plugin loading even begins.

Examples:

  • Use module.exports in next.config.js for CommonJS setups.
  • Use export default in next.config.mjs for ESM setups.

4. Monorepo resolution issues

In workspaces or monorepos, the SWC plugin may be installed in a location that Next’s resolver does not pick up consistently.

Check that:

  • the plugin is installed in the correct package,
  • the app running Next has access to it,
  • hoisting is not masking a dependency problem.

5. Assuming this is a normal TypeScript error

It is easy to misread this as a typing problem because the config file ends with .ts. But the real issue is usually in the runtime handoff to the experimental SWC loader, not TypeScript type checking.

FAQ

Can I keep using next.config.ts with experimental.swcPlugins?

At the time of this issue, it is not the safest choice. The practical workaround is to switch to next.config.js or next.config.mjs when using experimental.swcPlugins.

Why does Next.js panic instead of showing a normal config validation error?

Because the failure often occurs inside the lower-level Rust/SWC integration layer. Experimental features may not always convert invalid or unsupported states into friendly JavaScript errors.

Does this mean the SWC plugin itself is broken?

Not necessarily. A plugin can be perfectly valid and still trigger this crash if the surrounding Next config loading path is unsupported or unstable. Test the same plugin from next.config.js first before blaming the plugin package.

Conclusion

The core fix is simple: if experimental.swcPlugins causes a panic from next.config.ts, move the config to next.config.js or next.config.mjs and keep the SWC plugin entries as plain serializable values. This avoids the unstable TypeScript config-loading path and matches the runtime expectations of Next’s experimental SWC integration much more closely.

If you are debugging the exact GitHub issue, start with the linked reproduction repository, convert the config file format, retest with @swc/plugin-noop, and then reintroduce your real plugin options one step at a time.

Leave a Reply

Your email address will not be published. Required fields are marked *