com um clique
devtools-production
// Handle devtools in production vs development. removeDevtoolsOnBuild, devDependency vs regular dependency, conditional imports, NoOp plugin variants for tree-shaking, non-Vite production exclusion patterns.
// Handle devtools in production vs development. removeDevtoolsOnBuild, devDependency vs regular dependency, conditional imports, NoOp plugin variants for tree-shaking, non-Vite production exclusion patterns.
Publish plugin to npm and submit to TanStack Devtools Marketplace. PluginMetadata registry format, plugin-registry.ts, pluginImport (importName, type), requires (packageName, minVersion), framework tagging, multi-framework submissions, featured plugins.
Configure @tanstack/devtools-vite for source inspection (data-tsd-source, inspectHotkey, ignore patterns), console piping (client-to-server, server-to-client, levels), enhanced logging, server event bus (port, host, HTTPS), production stripping (removeDevtoolsOnBuild), editor integration (launch-editor, custom editor.open). Must be FIRST plugin in Vite config. Vite ^6 || ^7 only.
Install TanStack Devtools, pick framework adapter (React/Vue/Solid/Preact), register plugins via plugins prop, configure shell (position, hotkeys, theme, hideUntilHover, requireUrlFlag, eventBusConfig). TanStackDevtools component, defaultOpen, localStorage persistence.
Build devtools panel components that display emitted event data. Listen via EventClient.on(), handle theme (light/dark), use @tanstack/devtools-ui components. Plugin registration (name, render, id, defaultOpen), lifecycle (mount, activate, destroy), max 3 active plugins. Two paths: Solid.js core with devtools-ui for multi-framework support, or framework-specific panels.
Use devtools-utils factory functions to create per-framework plugin adapters. createReactPlugin/createSolidPlugin/createVuePlugin/createPreactPlugin, createReactPanel/createSolidPanel/createVuePanel/createPreactPanel. [Plugin, NoOpPlugin] tuple for tree-shaking. DevtoolsPanelProps (theme). Vue uses (name, component) not options object. Solid render must be function.
Two-way event patterns between devtools panel and application. App-to-devtools observation, devtools-to-app commands, time-travel debugging with snapshots and revert. structuredClone for snapshot safety, distinct event suffixes for observation vs commands, serializable payloads only.
| name | devtools-production |
| description | Handle devtools in production vs development. removeDevtoolsOnBuild, devDependency vs regular dependency, conditional imports, NoOp plugin variants for tree-shaking, non-Vite production exclusion patterns. |
| type | lifecycle |
| library | @tanstack/devtools |
| library_version | 0.10.12 |
| requires | devtools-app-setup |
| sources | ["docs/production.md","docs/vite-plugin.md","packages/devtools-vite/src/plugin.ts","packages/devtools-vite/src/remove-devtools.ts","packages/devtools/package.json","packages/devtools/tsup.config.ts","packages/devtools-utils/src/react/plugin.tsx","packages/devtools-utils/src/react/panel.tsx"] |
Prerequisite: Read the devtools-app-setup skill first. The initial setup decisions (framework adapter, Vite plugin, dependency type) directly determine which production strategy applies.
TanStack Devtools has two independent mechanisms for keeping devtools out of production bundles. Understanding both is essential because they serve different project types.
The @tanstack/devtools-vite plugin includes a sub-plugin named @tanstack/devtools:remove-devtools-on-build. When removeDevtoolsOnBuild is true (the default), this plugin runs during vite build and any non-serve command where the mode is production.
It uses oxc-parser to parse every source file, find imports from these packages, and remove them along with any JSX elements they produce:
@tanstack/react-devtools@tanstack/preact-devtools@tanstack/solid-devtools@tanstack/vue-devtools@tanstack/svelte-devtools@tanstack/angular-devtools@tanstack/devtoolsThe stripping is AST-based. It removes the import declaration, then finds and removes any JSX elements whose tag name matches one of the imported identifiers. It also traces plugin references inside the plugins prop array and removes their imports if they become unused.
Source: packages/devtools-vite/src/remove-devtools.ts
This means for a standard Vite project, the default setup from devtools-app-setup already handles production correctly with zero additional configuration:
// This import and JSX element are completely removed from the production build
import { TanStackDevtools } from '@tanstack/react-devtools'
function App() {
return (
<>
<YourApp />
<TanStackDevtools
plugins={
[
/* ... */
]
}
/>
</>
)
}
The @tanstack/devtools core package uses Node.js conditional exports to serve different bundles based on the environment:
{
"exports": {
"workerd": { "import": "./dist/server.js" },
"browser": {
"development": { "import": "./dist/dev.js" },
"import": "./dist/index.js"
},
"node": { "import": "./dist/server.js" }
}
}
Key points:
browser + development condition resolves to dev.js (dev-only extras).browser without development resolves to index.js (production build).node and workerd resolve to server.js (server-safe, no DOM).These are built via tsup-preset-solid with dev_entry: true and server_entry: true in packages/devtools/tsup.config.ts.
This is the standard path from devtools-app-setup. Devtools are present during vite dev and stripped automatically on vite build.
Install as dev dependencies:
npm install -D @tanstack/react-devtools @tanstack/devtools-vite
Vite config -- default behavior:
import { devtools } from '@tanstack/devtools-vite'
import react from '@vitejs/plugin-react'
export default {
plugins: [
devtools(), // removeDevtoolsOnBuild defaults to true
react(),
],
}
Application code -- no guards needed:
import { TanStackDevtools } from '@tanstack/react-devtools'
function App() {
return (
<>
<YourApp />
<TanStackDevtools
plugins={
[
/* ... */
]
}
/>
</>
)
}
The Vite plugin handles everything. The import and JSX are removed from the production build. Since the packages are dev dependencies, they are not even available in a production node_modules after npm install --production.
When you deliberately want devtools accessible in a deployed application. This requires three changes from the default setup.
1. Install as regular dependencies (not -D):
npm install @tanstack/react-devtools @tanstack/devtools-vite
This ensures the packages are available in production node_modules.
2. Disable auto-stripping in the Vite config:
import { devtools } from '@tanstack/devtools-vite'
import react from '@vitejs/plugin-react'
export default {
plugins: [
devtools({
removeDevtoolsOnBuild: false,
}),
react(),
],
}
3. Application code remains the same:
import { TanStackDevtools } from '@tanstack/react-devtools'
function App() {
return (
<>
<YourApp />
<TanStackDevtools
plugins={
[
/* ... */
]
}
/>
</>
)
}
With removeDevtoolsOnBuild: false, the Vite build plugin skips the AST stripping pass entirely, so all devtools code ships to production.
You can combine this with requireUrlFlag from the shell config to hide the devtools UI unless a URL parameter is present:
<TanStackDevtools
config={{
requireUrlFlag: true,
urlFlag: 'debug', // visit ?debug to show devtools
}}
plugins={
[
/* ... */
]
}
/>
Without the Vite plugin, there is no automatic stripping. You must manually prevent devtools from entering production bundles using one of these strategies.
Create a separate file for devtools setup, then conditionally import it:
// devtools-setup.tsx
import { TanStackDevtools } from '@tanstack/react-devtools'
export default function Devtools() {
return (
<TanStackDevtools
plugins={
[
// your plugins
]
}
/>
)
}
// App.tsx
const Devtools =
process.env.NODE_ENV === 'development'
? (await import('./devtools-setup')).default
: () => null
function App() {
return (
<>
<YourApp />
<Devtools />
</>
)
}
When NODE_ENV is 'production', bundlers eliminate the dead import() path. The devtools-setup module and all its transitive dependencies are never included in the bundle.
For bundlers that support define/replace plugins (webpack DefinePlugin, esbuild define, Rollup @rollup/plugin-replace), wrap the import in a condition that the bundler can statically evaluate:
// webpack example with DefinePlugin
let DevtoolsComponent: React.ComponentType = () => null
if (__DEV__) {
const { TanStackDevtools } = await import('@tanstack/react-devtools')
DevtoolsComponent = () => (
<TanStackDevtools
plugins={
[
/* ... */
]
}
/>
)
}
function App() {
return (
<>
<YourApp />
<DevtoolsComponent />
</>
)
}
The key requirement is that the condition must be statically resolvable by the bundler. process.env.NODE_ENV === 'development' works for most bundlers. Framework-specific globals like __DEV__ also work.
When building reusable plugin packages with @tanstack/devtools-utils, the factory functions return a [Plugin, NoOpPlugin] tuple. The NoOpPlugin renders an empty fragment and carries no real dependencies. This is the primary mechanism for library authors to make their plugins tree-shakable.
import { createReactPlugin } from '@tanstack/devtools-utils/react'
const [QueryPlugin, QueryNoOpPlugin] = createReactPlugin({
name: 'TanStack Query',
Component: ({ theme }) => <QueryDevtoolsPanel theme={theme} />,
})
// The library exports both, and consumers choose:
export { QueryPlugin, QueryNoOpPlugin }
Consumer code uses the NoOp variant in production:
import { QueryPlugin, QueryNoOpPlugin } from '@tanstack/query-devtools'
const ActivePlugin =
process.env.NODE_ENV === 'development' ? QueryPlugin : QueryNoOpPlugin
function App() {
return <TanStackDevtools plugins={[ActivePlugin()]} />
}
The NoOp pattern exists for every framework adapter:
| Framework | Factory | Source |
|---|---|---|
| React | createReactPlugin | packages/devtools-utils/src/react/plugin.tsx |
| React (panel) | createReactPanel | packages/devtools-utils/src/react/panel.tsx |
| Preact | createPreactPlugin | packages/devtools-utils/src/preact/plugin.tsx |
| Solid | createSolidPlugin | packages/devtools-utils/src/solid/plugin.tsx |
| Vue | createVuePlugin | packages/devtools-utils/src/vue/plugin.ts |
All return readonly [Plugin, NoOpPlugin]. The NoOpPlugin always has the same metadata (name, id, defaultOpen) but its render function produces an empty fragment, so the bundler can tree-shake the real panel component and all its dependencies.
See the devtools-framework-adapters skill for the full factory API details.
The Vite plugin's removeDevtoolsOnBuild defaults to true. If you want devtools in production, you must both disable stripping AND install as a regular dependency. Missing either step causes failure.
Wrong -- devtools stripped despite wanting them in production:
// vite.config.ts
export default {
plugins: [
devtools(), // removeDevtoolsOnBuild defaults to true -- code is stripped
react(),
],
}
# package.json has devtools as devDependency
npm install -D @tanstack/react-devtools
Correct -- both changes together:
// vite.config.ts
export default {
plugins: [devtools({ removeDevtoolsOnBuild: false }), react()],
}
# regular dependency so it's available in production node_modules
npm install @tanstack/react-devtools
Missing removeDevtoolsOnBuild: false causes the AST stripping to remove all devtools imports and JSX at build time. Missing the regular dependency means node_modules may not contain the package in production environments that prune dev dependencies.
Without the Vite plugin, devtools code is never automatically stripped. If you import TanStackDevtools unconditionally, the entire devtools shell and all plugin panels ship to production.
Wrong -- always imports devtools regardless of environment:
import { TanStackDevtools } from '@tanstack/react-devtools'
function App() {
return (
<>
<YourApp />
<TanStackDevtools
plugins={
[
/* ... */
]
}
/>
</>
)
}
Correct -- conditional import based on NODE_ENV:
const Devtools =
process.env.NODE_ENV === 'development'
? (await import('./devtools-setup')).default
: () => null
function App() {
return (
<>
<YourApp />
<Devtools />
</>
)
}
The conditional must be statically evaluable by your bundler so it can eliminate the dead branch. Using a separate file for the devtools setup ensures the entire module subgraph is tree-shaken.
When building a reusable plugin package, exporting only the Plugin function (ignoring the NoOpPlugin from the tuple) means consumers have no lightweight alternative for production builds.
Wrong -- NoOp variant discarded:
const [MyPlugin] = createReactPlugin({
name: 'Store Inspector',
Component: StoreInspectorPanel,
})
export { MyPlugin }
Correct -- both variants exported:
const [MyPlugin, MyNoOpPlugin] = createReactPlugin({
name: 'Store Inspector',
Component: StoreInspectorPanel,
})
export { MyPlugin, MyNoOpPlugin }
Consumers then choose the appropriate variant based on their environment. Without the NoOp export, the only way to exclude the plugin is to not import the package at all, which requires the conditional-import pattern at the application level.
Development convenience pulls toward automatic stripping (dev dependencies, Vite plugin handles everything). Production usage pulls toward explicit inclusion (regular dependencies, disabled stripping, URL flag gating). These two paths are mutually exclusive in their dependency and configuration choices. A project must commit to one path. Attempting to mix them -- for example, keeping devtools as a dev dependency while setting removeDevtoolsOnBuild: false -- leads to builds that fail silently when the production environment prunes dev dependencies.
For staging/preview environments where you want devtools but not in the final production deployment, use requireUrlFlag with the development-only workflow intact, rather than switching to the production workflow.
removeDevtoolsOnBuild option and AST stripping logic live in the Vite plugin. See that skill for all Vite plugin configuration.[Plugin, NoOpPlugin] tuple pattern and all framework-specific factory APIs.packages/devtools-vite/src/plugin.ts -- Vite plugin entry, removeDevtoolsOnBuild option, sub-plugin registrationpackages/devtools-vite/src/remove-devtools.ts -- AST-based stripping logic (oxc-parser + MagicString)packages/devtools/package.json -- Conditional exports (browser.development -> dev.js, browser -> index.js, node/workerd -> server.js)packages/devtools/tsup.config.ts -- Build config producing dev.js, index.js, server.js via tsup-preset-solidpackages/devtools-utils/src/react/plugin.tsx -- createReactPlugin returning [Plugin, NoOpPlugin]packages/devtools-utils/src/react/panel.tsx -- createReactPanel returning [Panel, NoOpPanel]