Skip to content

@tanstack/devtools-event-client ships unconditionally in production bundles via form-core #2132

Open

Description

@veksen

Describe the bug

@tanstack/form-core unconditionally imports and uses @tanstack/devtools-event-client in production builds. The devtools event client (7.5 KB unminified) is a hard dependency in form-core's package.json and is imported at the top level of FormApi.ts and utils.ts with no process.env.NODE_ENV guard, no import.meta.env.DEV check, and no dead-code-elimination hint.

This means every app using @tanstack/react-form ships the full devtools plugin system to users, regardless of whether TanStack Devtools is installed or used.

What ships in production

Analyzing a Vite production build, the useForm chunk contains:

  • The full EventClient class from @tanstack/devtools-event-client (~7.5 KB source, ~4 KB minified)
  • tanstack-devtools-global event listeners
  • tanstack-connect / tanstack-connect-success connection handshake logic
  • Retry loops with setInterval for reconnection attempts
  • CustomEvent dispatching infrastructure
  • 🌴 [tanstack-devtools:form-devtools-plugin] debug log strings (minified but present)
  • @tanstack/pacer-lite's liteThrottle (~0.5 KB) used solely to throttle devtools state broadcasting

In our production bundle, this accounts for ~11.6 KB of dev-only code in the useForm chunk (39 KB total).

Root cause

In packages/form-core/src/EventClient.ts:

import { EventClient } from '@tanstack/devtools-event-client'

class FormEventClient extends EventClient {
  constructor() {
    super({ pluginId: 'form-devtools', reconnectEveryMs: 1000 })
  }
}

export const formEventClient = new FormEventClient()

This singleton is instantiated unconditionally at module load time. It's then imported and used throughout FormApi.ts (mount, update, reset, submit) and utils.ts (throttleFormState).

Because the formEventClient singleton has side effects at instantiation (it creates internal state, sets up connection properties), bundlers cannot tree-shake it even though form-core declares "sideEffects": false.

Related issues

Expected behavior

The devtools event client infrastructure should not be included in production builds. Possible approaches:

  1. Guard with process.env.NODE_ENV !== 'production' — wrap devtools imports and usage so bundlers can dead-code-eliminate the entire devtools path in production builds.
  2. Lazy/conditional initialization — only instantiate the FormEventClient when devtools are actually connected (e.g., when globalThis.__TANSTACK_EVENT_TARGET__ exists).
  3. Move devtools integration to the devtools package — instead of form-core importing the event client, have @tanstack/react-devtools hook into form instances externally (similar to how React DevTools hooks into React).

Your minimal, reproducible example

  1. npm create vite@latest my-app -- --template react-ts
  2. npm install @tanstack/react-form
  3. Use useForm in any component
  4. npx vite build && npx vite-bundle-visualizer
  5. Observe @tanstack/devtools-event-client and @tanstack/pacer-lite in the production bundle

Steps to reproduce

  1. Install @tanstack/react-form@1.28.6 (or latest 1.29.0)
  2. Build for production with Vite (or any bundler)
  3. Inspect the output chunks — the devtools event client is present

Platform

  • macOS
  • Node 24.x
  • Vite 7.3.1
  • @tanstack/react-form 1.28.6
  • @tanstack/form-core 1.28.6
  • @tanstack/devtools-event-client 0.4.3

TanStack Form adapter

react-form

TanStack Form version

1.28.6

TypeScript version

5.6.3

Additional context

The @tanstack/pacer-lite dependency (hard dep of form-core) is also only used for throttleFormState which exclusively serves the devtools state broadcasting. If the devtools code were properly gated, pacer-lite could also be eliminated from production bundles.

Activity

  1. harry-whorlow commented on Apr 29, 2026

    @harry-whorlow
    Contributor

    Hi @veksen, thanks for the report! I'll take a look into this.

    For debugging purposes are you using the Vite devtools plugin?

  2. leimantas commented on May 15, 2026

    @leimantas
    Image

    same problem:

    5:01:45 PM [vite] (client) Pre-transform error: Failed to resolve import "tanstack-start-injected-head-scripts:v" from "../../node_modules/.pnpm/@tanstack+start-server-core@1.167.30/node_modules/@tanstack/start-server-core/dist/esm/router-manifest.js?v=8ff73058". Does the file exist?

    my vite.config.ts

    import { cloudflare } from '@cloudflare/vite-plugin';
    import tailwindcss from '@tailwindcss/vite';
    import { tanstackStart } from '@tanstack/react-start/plugin/vite';
    import viteReact from '@vitejs/plugin-react';
    import { defineConfig } from 'vite';
    
    const config = defineConfig({
      server: {
        port: 5173,
        strictPort: true,
      },
      plugins: [
        cloudflare({
          viteEnvironment: { name: 'ssr' },
          persistState: { path: '../../.wrangler/state' },
        }),
        tailwindcss(),
        tanstackStart(),
        viteReact(),
      ],
      resolve: { tsconfigPaths: true },
    });
    
    export default config;

    I also tried with this example: https://tanstack.com/form/v1/docs/framework/react/examples/tanstack-start
    Same problem.

  3. added
    v1This issue has been created during v1 development.
    on Aug 9, 2026
  4. sebthom commented on Aug 18, 2026

    @sebthom

    I can confirm this still happens in @tanstack/form-core@1.33.5.

    When a form is mounted, the production code can send its values and configuration through global browser events. It also listens for commands that reset or submit the form. This happens even when the application does not use TanStack Form Devtools.

    We currently patch this behavior out. Could the devtools connection be disabled by default and enabled only when an application explicitly installs or activates the devtools?

  5. harry-whorlow commented on Aug 19, 2026

    @harry-whorlow
    Contributor

    We're hoping to fix this in v2 (which is in alpha). I'll keep you posted. Sorry for the delay

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1This issue has been created during v1 development.v2: needs investigationThis hasn't been confirmed or denied in v2 yet.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions