Recipe builder
Pick a framework and the extensions you want; copy the files. Every combination this page can produce — every extension alone and all of them together, eager and lazy, for both frameworks — is typechecked against the package source in CI.
Extensions are independent of each other: any subset works, in any order. Only ext/a11y needs another package, the optional axe-core peer. Some extensions show nothing useful until you hand them your own data — those come out with placeholders and a note below the code.
npm install @nejcm/dev-toolbarimport { DevToolbar } from "@nejcm/dev-toolbar";
import { environment } from "@nejcm/dev-toolbar/ext/environment";
import { metrics } from "@nejcm/dev-toolbar/ext/metrics";
import { commandMenu } from "@nejcm/dev-toolbar/ext/command-menu";
import { diagnostics } from "@nejcm/dev-toolbar/ext/diagnostics";
// Built once, at module scope — never inside render.
const extensions = [
environment({
context: {
environment: import.meta.env.VITE_APP_ENV,
release: import.meta.env.VITE_RELEASE,
commit: import.meta.env.VITE_COMMIT,
},
}),
metrics(),
commandMenu(),
diagnostics(),
];
export default function DevTools() {
return <DevToolbar extensions={extensions} />;
}
import { StrictMode, Suspense, lazy } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
// Off in every production build, staging included, unless built with VITE_DEV_TOOLBAR=true.
const DevTools =
import.meta.env.DEV || import.meta.env.VITE_DEV_TOOLBAR === "true"
? lazy(() => import("./DevTools"))
: null;
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
{DevTools && (
<Suspense fallback={null}>
<DevTools />
</Suspense>
)}
</StrictMode>,
);
- Environment: Shows only what you pass. Wire the env variables your build already sets; unset ones read as unknown.
- Diagnostics: Lazy-loaded, it starts after the app's first render, so console output before then is not in the tail.
The lazy setup loads the toolbar on the dev server and leaves it out of every production build unless you opt that build in at build time (VITE_DEV_TOOLBAR=true or NEXT_PUBLIC_DEV_TOOLBAR=true) — a staging build is a production build, so it needs the flag too. Unset means off. Its caveat: the chunk loads after the app's first render, so nothing in it runs at boot. See SSR for why the Next.js extension array lives in a "use client" module.