> For the complete documentation index, see [llms.txt](https://icure.gitbook.io/icure/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://icure.gitbook.io/icure/support/cardinal-sdk-web-bundling.md).

# Troubleshooting: login hangs in a minified web bundle

## Symptom

A web application that embeds the **Cardinal SDK** (`@icure/cardinal-sdk`) works perfectly in development and in unminified production builds, but as soon as the JavaScript bundle is **minified** the user can no longer log in:

* the login request `POST /rest/v2/auth/login` is sent and returns **`200`** (authentication itself succeeds);
* the UI then freezes — the login spinner spins forever and the page never reaches the dashboard;
* the browser tab becomes unresponsive (one CPU core pegged at 100%), so much so that a CPU profile often cannot even be captured.

There is **no error in the console**. (If you enable "break on caught exceptions" you may see a caught `process is not defined` coming from the SDK's `getKtorLogLevel()` — this is harmless, it is swallowed by a `try/catch`, and it happens in the working unminified build too. It is **not** the cause of the hang.)

## Root cause

The Cardinal SDK is compiled from **Kotlin Multiplatform to JavaScript (Kotlin/JS, on top of ktor)**. Every `suspend` function (coroutine) is emitted as a state machine of the form:

```js
while (true) {
  switch (state) {
    case 0: /* ... */ state = 1; continue
    case 1: /* ... */ return result
  }
}
```

A JavaScript minifier's **compressor** (the `compress` pass of SWC/terser — variable reduction, constant evaluation, loop optimisation, etc.) can **miscompile these state machines**: the variable that advances `state` is treated as if it were invariant, so after a successful login the response coroutine never leaves the loop. The result is an **infinite loop on the main thread** — hence the pegged CPU and the spinner that never resolves.

Identifier **mangling** (renaming variables to short names) is safe; only the **compressor** breaks the coroutines. Unminified builds keep the state machines intact, which is exactly why login works there and only the minified bundle is affected.

## Fix

Keep minification, but **disable the compressor and keep only mangling**. This resolves the infinite loop at a negligible size cost (\~1 % gzipped — mangling provides the bulk of the savings).

### Rsbuild / Rspack (SWC minifier)

```ts
// rsbuild.config.ts
export default defineConfig({
  output: {
    minify: {
      js: true,
      jsOptions: {
        minimizerOptions: {
          // SWC `compress` miscompiles the Cardinal SDK's Kotlin/JS coroutine
          // state machines, causing an infinite loop after a successful login.
          // Keep `mangle` on, turn `compress` off.
          compress: false,
        },
      },
    },
  },
})
```

### webpack (Terser)

```js
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: false, // keep mangle, disable the compressor
          mangle: true,
        },
      }),
    ],
  },
}
```

### Other bundlers

The same principle applies to any toolchain: **disable the compressor / "optimize" pass and keep identifier mangling**. If you prefer to keep compression on, an alternative is to exclude `@icure/cardinal-sdk` (and its `ktor-*` / `kotlin*` chunks) from the compressor while still compressing your own code.

## How to confirm you are hitting this

1. Open the network tab: `POST /rest/v2/auth/login` returns `200`, yet the UI stays on the login screen with the spinner active.
2. The tab is unresponsive and one CPU core is at 100 % (an infinite loop, not a pending request).
3. Rebuild with the compressor disabled (see above) — login works again.

If disabling the compressor fixes it, you have confirmed the miscompilation. Re-enable the compressor only once the underlying SWC/SDK issue has been resolved.
