Production elimination
On this page
The panel is gated on a literal process.env.NODE_ENV === "development"
check that survives verbatim into the published dist/. Your bundler
substitutes it at your build time, folds the condition to false, and drops
the dynamic import() of the panel along with the whole branch — so no panel
code reaches your production bundle, and no chunk is emitted for it.
This is why the check is written inline rather than imported as a boolean
constant: cross-module constant propagation is not guaranteed by every
bundler, but a literal process.env.NODE_ENV comparison in the same file is
handled by all of them.
The capture layer is gated on the same condition plus a
typeof window !== "undefined" check, so attachHttpMonitor and friends
become no-ops in production even though their call sites remain — and also
during SSR, in the same dev build where they are live in the browser.
That second half is not a bundling concern but a correctness one. The capture module is reachable from your HTTP-client module, which typically also runs on the server, and the monitor's buffer is a module-level singleton: on a long-lived Node process it would otherwise accumulate every user's request payloads for the lifetime of the server. Keeping the server-side singleton permanently empty is the point.
Practically: a captureEncrypted call that runs during SSR does nothing, and
<Blix /> renders nothing there. It also renders nothing during hydration, so
its first client render matches the server's HTML; the panel mounts on the
render right after. (Before 0.9.0 it branched on typeof window instead,
which Next.js reported as a hydration mismatch on every load.)
If the panel disappears in development, check the console for
[blix] the panel crashed. Blix renders inside an error boundary of its own,
so a failure in the panel removes the panel until the next reload and leaves
your app running — the logged error is the thing to report.
Enabling Blix in a production build (staging)
A staging or test server usually runs a production build, where the panel is
gone by design. Setting NODE_ENV=development for that build does not bring
it back on Next.js 16: next build runs on Turbopack and inlines NODE_ENV
as "production" whatever the environment says.
Opt in explicitly instead, in that server's env file:
NEXT_PUBLIC_BLIX=trueThe panel and the capture layer both check it next to NODE_ENV, as the same
kind of literal comparison, so Next.js inlines it at build time.
Never set NEXT_PUBLIC_BLIX=true for a build that real users get. It
ships the monitor to them, and with it everything it captures — see
Security.
Your own guards need the variable too. A mount component that returns
null outside development (as in Mounting the panel),
or a call-site guard around attachHttpMonitor (below), keeps Blix off on
staging regardless of the variable. Add it to each:
if ( (process.env.NODE_ENV === "development" || process.env.NEXT_PUBLIC_BLIX === "true") && typeof window !== "undefined") { attachHttpMonitor(apiClient);}The variable is read under its NEXT_PUBLIC_ name, which is a Next.js
convention. The opt-in has been verified with Next.js 16 (Turbopack) only;
other bundlers do not inline that name by default.
So NEXT_PUBLIC_BLIX is honoured only where the bundler inlines it. On Vite,
esbuild, Rollup and the like it is ignored, safely: the read is guarded, so it
does not depend on a process global existing in the browser, and Blix stays
off in production.
Guard your call sites
No-op is not the same as eliminated. The argument above works for the panel because the literal check lives in the file that makes the dynamic import. It does not carry over to the capture layer, and the reason is the same one that motivated writing the check inline in the first place.
Every capture function tests a single internal constant inside its own body,
and that constant lives in Blix's module, not yours. Your bundler folds the
constant to false — but it keeps the function bodies that reference it,
because your call site still imports them. The result is tens of kilobytes of
capture runtime in a production bundle where every entry point is dead.
To drop it, write the same literal check in the file that makes the call, exactly as Blix does internally:
// ✅ folded away in production — the whole capture chunk is droppedexport const apiClient = process.env.NODE_ENV === "development" ? withInitiatorCapture(createClient()) : createClient(); if (process.env.NODE_ENV === "development" && typeof window !== "undefined") { attachHttpMonitor(apiClient);}// ❌ works, but keeps the capture runtime in your production bundleexport const apiClient = withInitiatorCapture(createClient());attachHttpMonitor(apiClient);Two rules for writing the guard:
- Write the condition out literally, in the file that makes the call.
Hoisting it into a shared
const IS_DEVdefeats the folding, for the same reason Blix writes it inline in its own source. - Include
typeof window !== "undefined"when the module also evaluates on the server, which in the Next.js App Router it generally does. It is redundant with Blix's internal guard, but it keeps the folded branch unambiguous for the bundler and matches the condition Blix uses internally.
createReduxMonitorMiddleware needs the middleware callback restructured
rather than a one-line guard — see the Redux
example above, which is written in the guarded form.