Skip to content
v0.12.0

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:

terminalbash
NEXT_PUBLIC_BLIX=true

The 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:

typescriptts
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:

typescriptts
// ✅ folded away in production — the whole capture chunk is dropped
export const apiClient =
process.env.NODE_ENV === "development"
? withInitiatorCapture(createClient())
: createClient();
if (process.env.NODE_ENV === "development" && typeof window !== "undefined") {
attachHttpMonitor(apiClient);
}
typescriptts
// ❌ works, but keeps the capture runtime in your production bundle
export 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_DEV defeats 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.

Edit this page on GitHubLast updated October 16, 2026
Docs