Your app's traffic, inside your app.
Blix is an open-source devtools panel that renders inside your React app. Network requests from axios or fetch, Redux actions, TanStack Query and realtime messages, in one style-isolated overlay that links them together.
pnpm add -D @hakam-aldeen-kh/blixThis is Blix itself, running on a demo app. Click rows, switch tabs, press Ctrl K.Tap a row to open it.
React 18 & 19Next.js App RouteraxiosfetchRedux ToolkitTanStack QueryRealtime adapters
The problem
One click, spread across three devtools.
The request is in the browser's Network tab. The action is in the Redux extension. The cache entry is in Query Devtools. None of them know about each other, and none of them sit where you click.
01 / 04 · Network
Every request, from axios or fetch.
Filter with a query syntax, then act on a request without leaving the row. Encrypted payloads sit next to their decrypted form.
method:postis:errorurl:orders-status:2xx- Replay
- Send the request again through your own client.
- cURL
- Copy it as cURL, or as fetch from the context menu.
- JWT
- Bearer tokens decoded in place: subject, issuer, time to expiry.
- Encrypted
- The ciphertext your interceptors produced, next to the plaintext.
02 / 04 · Realtime
Sockets, frame by frame.
Each connection is one row. Its Messages tab lists every frame with its direction, event and size, from connect to disconnect.
- ↓ in
- Frames the server pushed.
- ↑ out
- Subscriptions your app sent.
- system
- Connect, presence and disconnect.
03 / 04 · Redux
Every action, and what it changed.
The Diff tab is a unified diff: each changed path with its before and after. State shows the live store, scoped by slice.
- Diff
- Changed paths, line by line, with +n −n counts.
- State
- The live store, with the slices this action touched marked.
- Re-dispatch
- Run the action again against the current store.
04 / 04 · Query
The cache, with its history.
One row per query key, for the whole session. The Timeline shows every fetch, success, error and invalidation, and the header counts its observers.
- Invalidate
- Mark a query stale and refetch it.
- Timeline
- Every state change for one query key.
- Linked
- Jump to the request its queryFn made.
Linked
A query knows which request fed it.
When Blix sees the request behind a TanStack Query fetch, it records the link. Follow it and the panel jumps to that request, selected, with its payload and response. Separate devtools can't do this because they never see both sides.
Click something. Watch it land.
A small demo app on the left, the real Blix on the right. Each button makes a real axios, fetch, Redux, Query or socket call, and the row appears in its source as it happens.
Demo store
A fake app. Responses are simulated.
Keyboard
Built for the keyboard.
Every action in the panel is a command, and the palette shows each one's shortcut where it has one. The panel on the left is live: open the palette, type to filter, arrow to select, enter to run.
- CtrlK
- Open the command palette
- ↑↓
- Move through rows
- 14
- Jump between sources
- ?
- Every shortcut, on one sheet
- Ctrl`
- Show or hide the panel
Install
Four steps in the Next.js App Router.
Install the package, attach capture where your client is built, wrap the panel in a client component, then render that wrapper in your root layout. Using fetch instead of axios? Call attachFetchMonitor() once on the client.
Step 1: Install
As a dev dependency. It has to be present at build time; nothing from it reaches production output.
terminalshpnpm add -D @hakam-aldeen-kh/blixStep 2: Attach capture at module scope
Where the client is constructed, after your own interceptors. Import capture from /capture, which carries no client boundary.
src/network/axios.tstsimport axios from "axios";import { attachHttpMonitor } from "@hakam-aldeen-kh/blix/capture";export const apiClient = axios.create({ baseURL: "/api" });apiClient.interceptors.request.use(addAuthHeader);// After your own interceptors, at module scope.if (process.env.NODE_ENV === "development" && typeof window !== "undefined") {attachHttpMonitor(apiClient);}Step 3: Create a client wrapper
BlixMount.tsx starts with "use client". The panel runs in the browser, so this boundary is required.
app/BlixMount.tsxtsx"use client";import { Blix } from "@hakam-aldeen-kh/blix";import { apiClient } from "@/src/network/axios";import { store } from "@/src/store";export default function BlixMount() {if (process.env.NODE_ENV !== "development" &&process.env.NEXT_PUBLIC_BLIX !== "true")return null;return <Blix store={store} apiClient={apiClient} dbName="my-app" />;}Step 4: Render it in the root layout
layout.tsx is a server component. It renders the wrapper and never imports Blix directly.
app/layout.tsxtsximport BlixMount from "./BlixMount";export default function RootLayout({ children }) {return (<html lang="en"><body>{children}<BlixMount /></body></html>);}
Production
Zero cost in production.
The NODE_ENV guards in Blix survive the build into dist/, so your bundler sees them and drops the panel from production output. Nothing to configure, nothing to strip. Need it on a staging server? Build that one with NEXT_PUBLIC_BLIX=true.
// excerptif ( mounted && (process.env.NODE_ENV === "development" || (typeof process !== "undefined" && !!process.env && process.env.NEXT_PUBLIC_BLIX === "true"))) { panelRef.current ??= lazy(() => import("./DevToolsPanel-JUWNT5QO.js"));}Blix, per build
- Production cost
- Guards are kept in the published build and tree-shaken by your bundler. Production users download none of it.
- Style isolation
- The panel portals outside your stacking contexts and paints only from its own themes. Your app's CSS never reaches it.
- Footprint
- No UI libraries, and one wrapper component to remove if you ever want to.
Does Blix ship to my users?
Which React version do I need?
What does it capture?
Is anything written to disk?
Will its styles clash with my app?
Is it free?
Put it in the app you're debugging.
pnpm add -D @hakam-aldeen-kh/blix