@mrzr/api-client

Troubleshooting

Fix common @mrzr/api-client problems: error messages explained, requests hitting the wrong host, logouts on reload, workers not starting and CSRF issues.

Error messages

MessageCause and fix
addToUrl contains a falsy segment at index 0An ID was null, undefined or "". Guard it, or use addTemplateToUrl
Failed to fetch, Network request failed (0)Never reached the server; the text is the runtime's own error: CORS (check the console for the real reason), offline, a wrong baseUrl, or an http: API from an https: page
Request timed out (408)The per-attempt timeout (default 30 s) ran out. Raise it, or pass timeout: 0
Request aborted / Request canceled: … (0)A deliberate cancellation. Check res.canceled
A ReadableStream body cannot be sent through a Web WorkerSend a Blob or File, or use a client with worker: false
Access token expired during a streamed upload…The token was refreshed, but a stream can't be re-sent. Retry with a fresh stream, and use uploadSkewMs
No base URL for "/users"…On the server there's no page origin to fall back to. Set baseUrl
Client destroyedThe client was used after destroy(), or destroyed with requests in flight. Don't destroy a shared client from a component
Plugin "x" failed …A plugin hook threw. Only that call failed

Requests go to my own app instead of the API

No baseUrl was found, so the browser client used the page's origin. Check what detection sees:

import { detectBaseUrl } from "@mrzr/api-client";
console.log(JSON.stringify(detectBaseUrl()));   // "" means nothing was found

The usual cause is a variable the bundler doesn't expose to the browser (API_URL instead of NEXT_PUBLIC_API_URL or VITE_API_URL), or a dev server not restarted after editing .env. Passing baseUrl explicitly avoids all of it. See environment variables.

Logged out after every reload

  • The default storage: "memory" is designed not to survive a reload. Use cookie mode, or accept storage: "local".
  • In cookie mode, isAuthenticated starts false on every load because JavaScript can't see httpOnly cookies. Call api.restoreSession("/auth/me") on startup.
  • Two clients with different storageKeys don't share a session.
  • In Nuxt or another SSR framework, create the client in client-only code, once. A new client per render starts with no session.

Logged out right after logging in

The client found no tokens in the login response. Look at what the server sends:

const res = await api.post("/auth/login", creds, { skipAuth: true, fullData: true });
console.log(JSON.stringify(res.data, null, 2));

Then describe its shape with extractTokens.

api.isWorker is false in the browser

  1. worker: false is set.
  2. extractTokens or buildRefreshBody is a function. Use the object forms.
  3. The CSP blocks blob: workers. Add worker-src 'self' blob:. Browsers often report this late, so isWorker can switch to false after the first request.

cancel() returns 0

  1. Cancellation isn't on: pass cancel: true, or use a cancelScope.
  2. It's a write. Only GET is covered unless you set cancelable: true or cancel: { methods: "all" }.
  3. The pattern doesn't match. Patterns match whole segments of the path, without origin or query. Compare with api.pending().map((r) => r.path).

Tabs don't sync

multiTab: false is set, the tabs use different storageKeys or origins (localhost:3000 and :3001 are different), or storage is "memory", where only logout is shared. See what syncs.

The CSRF header isn't sent

It's only sent on POST, PUT, PATCH, DELETE and refresh, only to trusted origins, and only if xsrfCookieName or getCsrfToken finds a value. The CSRF cookie must not be httpOnly, or JavaScript can't read it.

Cookies aren't sent cross-origin

Cookie mode sends credentials: "include". The server also needs Access-Control-Allow-Credentials: true, an explicit Access-Control-Allow-Origin (never *), and cookies with SameSite=None; Secure, which requires HTTPS, locally too.

res.data isn't what I expect

  • It's the whole wrapper: the body has no top-level data key to unwrap. Unwrap it yourself with afterFunc: (d) => d.payload.
  • It's the payload but you need meta or links: read res.body, or pass fullData: true.
  • It's undefined on success: a 204, an empty body, or a body that isn't JSON. Check res.headers?.["content-type"].
  • Validation errors are missing: errors must be at the top level of the body. Otherwise read them from e.data.

A Node process or test runner won't exit

Pass worker: false, multiTab: false in tests and scripts, and call api.destroy() when done.

Still stuck? Open an issue with the package version, your createClient options (no secrets), the full IRes or ApiError, and api.isWorker.

On this page