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
| Message | Cause and fix |
|---|---|
addToUrl contains a falsy segment at index 0 | An 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 Worker | Send 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 destroyed | The 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 foundThe 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 acceptstorage: "local". - In cookie mode,
isAuthenticatedstartsfalseon every load because JavaScript can't see httpOnly cookies. Callapi.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
worker: falseis set.extractTokensorbuildRefreshBodyis a function. Use the object forms.- The CSP blocks
blob:workers. Addworker-src 'self' blob:. Browsers often report this late, soisWorkercan switch tofalseafter the first request.
cancel() returns 0
- Cancellation isn't on: pass
cancel: true, or use acancelScope. - It's a write. Only
GETis covered unless you setcancelable: trueorcancel: { methods: "all" }. - 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
datakey to unwrap. Unwrap it yourself withafterFunc: (d) => d.payload. - It's the payload but you need
metaorlinks: readres.body, or passfullData: true. - It's
undefinedon success: a204, an empty body, or a body that isn't JSON. Checkres.headers?.["content-type"]. - Validation errors are missing:
errorsmust be at the top level of the body. Otherwise read them frome.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.
Security
What @mrzr/api-client protects against and what it doesn't: token isolation, trusted origins, storage trade-offs, recommended setups and a CSP.
Client Options
Every createClient and per-request option in @mrzr/api-client with its default: baseUrl, timeout, auth mode, token storage, CSRF and cancellation.