Skip to content
TanStack

TanStack React Redact

React, redacted.

Keep your components, hooks, and React imports. Swap in a smaller runtime that renders synchronously, with one Vite plugin.

Redact 0.1.0 measurements. Savings compare all client exports with React 19.3.0. Nano disables all optional features. App sizes vary. Measurement details

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { redact } from '@tanstack/redact/vite'

export default defineConfig({
  plugins: [react(), redact()],
})

One plugin. Same imports.

shell
pnpm add @tanstack/redact

Add redact() to your existing Vite plugins, keep your framework and React plugins in place. The plugin aliases React, React DOM, server rendering, scheduler, and JSX entrypoints for client and SSR builds.

tsx
// Your application code stays familiar.
import { useState } from 'react'
import { createRoot } from 'react-dom/client'

function Counter() {
  const [count, setCount] = useState(0)
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  )
}

createRoot(document.getElementById('root')!).render(<Counter />)

Redact is a React-compatible runtime, not a complete implementation of every React behavior. Check the compatibility differences below before switching an existing app.

Less runtime to download.

Production ESM runtime bundles, all React, JSX, DOM, and DOM-client exports retained.
RuntimeGzip size
React 19.3.069,162 bytes
Redact, default Vite configuration23,311 bytes
Redact, native animation enabled28,159 bytes

The default bundle was 66.3% smaller in the Redact 0.1.0 release measurement from September 11, 2026. These are runtime sizes, your app will tree-shake differently, and the runtimes have different scheduling capabilities. Measurement inputs and configurations ↗

Measured separately in the same release, the default DOM-client entry is 20,139 bytes gzipped and the nano DOM-client entry is 12,466 bytes. The React API entry is 2,764 bytes. These entries overlap, their sizes are not additive. Nano disables all optional features and is not a full-compatibility preset. Choose your feature flags.

Performance depends on the workload. Published microbenchmarks include faster state updates and slower hydration and Suspense retries. They do not establish whole-app speedups. Read the performance measurements ↗

Know what changes.

Redact keeps the React component model and everyday APIs, while leaving out concurrent scheduling. These behaviors describe the default Vite configuration.

AreaRedact behavior
Components and hooksJSX, state, effects, context, refs, memo, lazy, portals, error boundaries, and modern class lifecycles are supported. Legacy class lifecycles are no-ops.
Suspense and hydrationFallbacks, retries, retained primary DOM, streaming boundaries, and event replay are supported. Work runs without priority scheduling.
TransitionsstartTransition and useTransition run synchronously. Pending stays false. useDeferredValue returns its input. There is no time slicing or interruptible rendering.
Actions and optimistic stateuseActionState returns the initial state, a no-op dispatch, and false. useOptimistic has no optimistic overlay and its setter is a no-op. useFormStatus stays idle.
ActivityHidden DOM and state are retained. Effects, refs, and subscriptions disconnect, insertion effects stay connected. Hidden work runs synchronously.
Server renderingString, static markup, readable stream, and pipeable stream rendering are supported. Full Fizz prerender and resume APIs are not implemented.
Server ComponentsThe Vite RSC environment stays on upstream React. Redact does not reimplement Flight serialization.
Cachingcache returns its function and cacheSignal returns null. Client and DOM-server rendering are uncached.
Development toolsFast Refresh works with @vitejs/plugin-react. StrictMode and Profiler render children without double invocation or profiling. React DevTools inspection is not implemented.
React CompilerThe react/compiler-runtime memo-cache entrypoint is implemented and aliased. Every compiler output pattern has not been validated.

Native view transition animation is experimental and opt-in. Production animation, hidden-tab behavior, broader resource and navigation races, and Safari and Firefox remain unverified. Full API support reference ↗

Only ship the parts you use.

The default full preset enables every optional feature except native animation. Disable behavior your app does not use, or start with nano and turn features on individually. Overrides merge with the preset.

ts
// A client-rendered app without class components
redact({
  features: { hydration: false, classComponents: false },
})

// Every optional feature off, then context enabled
redact({ preset: 'nano', features: { context: true } })

// Experimental native view transition animation
redact({ features: { viewTransitions: true } })

Disabling a feature changes behavior, it does not just remove an unused export. The nano preset is not a full-compatibility preset. Feature size savings overlap and are not additive.

Feature flagWhen disabled
activityHidden children unmount and lose state.
fragmentRefsNo Fragment DOM instance.
viewTransitionsNo native snapshots or animation callbacks, the boundary retains state.
portalChildren render in place instead of into the target container.
contextProviders do not propagate values, reads return the default.
suspenseNo fallback UI, thrown thenables still retry when they settle.
memoNo prop-equality bailout.
forwardRefNo wrapper forwarding, ordinary ref props still work.
lazyOnly already-synchronous payloads resolve.
classComponentsConstructor, render, and setState only, no lifecycles or error boundaries.
hydrationhydrateRoot throws, use createRoot.

Vite plugin reference

ts
import { redact } from '@tanstack/redact/vite'

redact({
  preset: 'full',
  features: { viewTransitions: false },
})
OptionPurpose
preset'full' | 'nano', defaults to 'full'.
featuresBoolean overrides for the feature flags above.
skipAn array of import specifiers to leave out of aliasing.
resolveFromPackage resolution root, defaults to the Vite config root. Useful in monorepos.
packageRootsA map of package names to absolute package directories, bypassing node_modules lookup for local builds.

For runtime internals and manual configuration, see the development guide ↗.

Try it with your app.

Run your tests and production build, then check hydration, forms, portals, navigation, and any third-party components your app uses. Pay particular attention to code that depends on pending transitions, actions, optimistic state, or React DevTools.

The repository publishes browser test results, real-app integration checks, and benchmark reproduction instructions. Use those alongside your own app's checks when deciding whether to switch.