lingui/skills

swc-plugin-compatibility

Diagnose and fix Lingui SWC plugin compatibility errors with Next.js, Vite, Rspack, or other SWC runtimes.

First seen Jan 27, 2026

Installation

$ npx skills add lingui/skills --skill swc-plugin-compatibility

Summary

  • Diagnose and fix Lingui SWC plugin compatibility errors with Next.js, Vite, Rspack, or other SWC runtimes.
  • Use when seeing errors like "failed to invoke plugin", "failed to run Wasm plugin transform", "Failed to deserialize program received from host", "Failed to execute SWC plugin", "out of bounds memory access", or "LayoutError" during builds with @lingui/swc-plugin — or when macros are silently not transformed (build succeeds but Trans/t render as raw untranslated output).

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from lingui/skills.

npx skills add lingui/skills

Browse all from lingui/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 9
License LICENSE
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,218 B
  • docs SUMMARY.md 934 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 214 installs

SKILL.md

SWC Plugin Compatibility

If you see errors like these during your build:

failed to invoke plugin on 'Some("/app/src/file.ts")'
failed to run Wasm plugin transform. Please ensure the version of `swc_core` used by the plugin is compatible with the host runtime.
    swc_plugin_runner: <n>
: Failed to deserialize program received from host
Failed to execute SWC plugin                      (Next.js / Turbopack wording of the same failure)
RuntimeError: out of bounds memory access         (older hosts)
LayoutError called Result::unwrap()               (older hosts)

This is NOT a bug in your code or in Lingui. The installed @lingui/swc-plugin was built against an swc_core version your SWC host cannot run.

Why This Happens

Every @lingui/swc-plugin release is compiled against one pinned swccore version — the plugin's compatibility table lists them. The host (Next.js's bundled SWC, or the @swc/core that @vitejs/plugin-react-swc / Rspack resolves) accepts plugins from a range of swccore versions. Outside that range the Wasm plugin cannot deserialize the AST it is handed, and the build fails on every file that uses a macro.

SWC has stabilised the plugin ABI over time, so mismatches are rarer than they were, but hosts still move past plugins and plugins past hosts (see lingui/swc-plugin #250 and #253 for one of each). The pairing is something to check, never assume.

How to Fix

Step 1: Identify the host and its resolved version

Signal in the project Host Read the version from
next in dependencies, no .babelrc Next.js's bundled SWC npm ls next
@vitejs/plugin-react-swc @swc/core npm ls @swc/core — the lockfile resolution, not the caret range the plugin declares
Rspack / @swc/core directly @swc/core npm ls @swc/core

Next.js embeds SWC and ignores any @swc/core in package.json; only the Next.js version decides its swc_core. On Vite, two projects with identical package.json can resolve different @swc/core versions — the lockfile is the source of truth.

Step 2: Choose the plugin version

  1. Read the host's accepted swccore range. The error message usually prints a https://plugins.swc.rs/versions/from-plugin-runner/<n> URL — open it in a browser; for Next.js, select runtime next and the resolved version on https://plugins.swc.rs. The site is client-rendered, so a plain fetch returns an empty page; the fallback is the host's own pin: for Next.js, swccore in the root Cargo.toml of the vercel/next.js tag (v<version>), for @swc/core, its GitHub release notes.
  2. In the plugin's compatibility table, take the newest @lingui/swc-plugin whose swc_core falls inside that range.
  3. Check its peer: npm view @lingui/swc-plugin@<version> peerDependencies. Each plugin major requires the matching @lingui/core major as a required peer (a newer plugin major may also admit the previous core major). A candidate whose peer excludes the installed @lingui/core is not a candidate — the install fails with ERESOLVE instead of the build failing. Pick the next-newest that passes, or go to "What If No Compatible Version Exists?".

The compatibility site has lagged behind releases before. When the table and the site disagree, or the host is newer than either lists, decide by building: try the newest plugin, then step back one swc_core row at a time until a macro-using file compiles.

Hosts drift independently, so two hosts in one repo (a Next.js app and a Vite app) can legitimately need two different plugin versions.

Step 3: Pin exactly and confirm

{
  "devDependencies": {
    "@lingui/swc-plugin": "<exact version>"
  }
}

Use an exact version (no ^ or ~) so a routine dependency bump cannot move it. Done when the build passes on a file that uses a macro and the rendered output shows translated text rather than raw macro source.

Keeping It Working

  1. Pin the plugin exactly and exclude it from Renovate/Dependabot auto-merge; bump it deliberately, after checking the compatibility table.
  2. Treat host upgrades as plugin upgrades. Hosts change swc_core in minor and patch releases, so a Next.js or @vitejs/plugin-react-swc bump needs the same check — put both packages in one update group.
  3. Build after any upgrade that touches SWC or the plugin. The build, not the changelog, is the confirmation.

@lingui/swc-plugin does not track the other @lingui/* versions; it releases on its own cadence, so its version number carries no information about which @lingui/core it pairs with beyond the peer field.

Example: Next.js Configuration

// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    swcPlugins: [
      ['@lingui/swc-plugin', {
        // Plugin options
      }],
    ],
  },
};

export default nextConfig;

Example: .swcrc Configuration

{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "experimental": {
      "plugins": [
        ["@lingui/swc-plugin", {}]
      ]
    }
  }
}

Example: Vite Configuration

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import { lingui } from "@lingui/vite-plugin";

export default defineConfig({
  plugins: [
    react({ plugins: [["@lingui/swc-plugin", {}]] }),
    lingui(),
  ],
});

Silent Failure: Wrong Plugin Entry Shape

Every SWC plugin entry must be a [name, options] tuple, even with empty options. Passing the plugin name as a bare string silently disables the macro transform: the build succeeds, but <Trans> never resolves and messages render as raw macro output.

// ❌ Silently does nothing
react({ plugins: ["@lingui/swc-plugin"] })

// ✅ Tuple with options object
react({ plugins: [["@lingui/swc-plugin", {}]] })

The same applies to Next.js swcPlugins and .swcrc plugins arrays.

What If No Compatible Version Exists?

When no plugin version that installs beside the project's @lingui/core fits the host, make an explicit choice rather than guessing:

  1. Move the host to a version whose swc_core range has a matching plugin (usually forward: newer hosts accept newer plugins)
  2. Switch to the Babel transform (@lingui/babel-plugin-lingui-macro) — see the caveats below
  3. Open an issue or PR at https://github.com/lingui/swc-plugin

Forcing the install (--legacy-peer-deps) or downgrading @lingui/core to satisfy an old plugin's peer are not on this list: the first hides the mismatch until the build, the second regresses the runtime to please a build tool.

Babel Fallback on Next.js

npm install -D @lingui/babel-plugin-lingui-macro babel-plugin-macros @babel/types
// .babelrc
{ "presets": ["next/babel"], "plugins": ["macros"] }

Remove the experimental.swcPlugins entry. Three things the Babel path needs that the SWC plugin did not:

  1. @babel/types installed explicitly. Next bundles its own Babel and does not expose it to plugins; without it the build fails with Cannot find package '@babel/types'.
  2. A lingui.config.ts (or .js/.mjs). The Babel macro reads it at compile time; the SWC plugin never did, so a project that got by without one breaks here.
  3. Acceptance that .babelrc turns off Next's SWC compiler for the whole project — Next prints Disabled SWC as replacement for Babel because of custom Babel configuration, and compiler options in next.config are ignored from then on. Builds get slower; prefer moving the host when that is an option.

Babel Fallback Caveat: @vitejs/plugin-react 6

@vitejs/plugin-react@6 dropped its internal Babel and removed the babel option entirely. On v6+, the classic form:

react({ babel: { plugins: ["@lingui/babel-plugin-lingui-macro"] } })

no longer works — a TypeScript config fails with TS2353 ("babel does not exist in type..."); a plain JS config is silently ignored and macros never get transformed.

Your options on Vite:

  • Stay on SWC: @vitejs/plugin-react-swc + an exactly pinned @lingui/swc-plugin chosen by the steps above (preferred)
  • Run the macro as a standalone Babel pass (@rolldown/plugin-babel on Vite 8, vite-plugin-babel on Vite ≤ 7) — the lingui-framework-setup skill has the wiring
  • Pin @vitejs/plugin-react@^5, which still supports the babel option