I've been doing frontend for about twelve years, and a big part of that has been fighting bundle size. Code splitting. Lazy routes. Swapping heavy date libraries for lighter ones. Hunting down that one dependency that pulls in all of lodash. A Webpack 4 to 5 migration done with codemods because doing it by hand across a big codebase wasn't realistic.
But there was always one chunk of the bundle nobody touched: React and React DOM themselves. You just accepted that cost. It was the floor.
TanStack Redact just lowered the floor. And the reason I think it matters isn't the clever engineering behind it (though there's plenty of that). It's how useful it is on day one.
What Redact actually is
Redact is a smaller, React-compatible runtime for the browser. You add one plugin to your build config, and your existing React imports, components, hooks and third-party libraries run on Redact in the browser instead of React DOM.
That's the whole pitch. No new API to learn. No import { h } from 'somewhere-else'. No rewriting components. Your app code stays exactly the same.
It started as Tanner Linsley's experiment on his personal site and tanstack.com, where it's been running in production since April 2026. It stuck, other people started using it in production too, and on October 26 it shipped as 0.2.0 with stable integrations for TanStack Start, Vite, Vinext and Next.js (both App Router and Pages Router).
The usefulness is the headline
Most performance wins come with a migration cost. Move to a new framework, adopt a new rendering model, rewrite your state layer, retrain the team. The win is real, but you pay for it in weeks.
Redact flips that. Here's a plain Vite + React app:
// 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()],
})
And TanStack Start:
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import react from '@vitejs/plugin-react'
import { redact } from '@tanstack/redact/vite'
export default defineConfig({
ssr: { noExternal: true },
plugins: [tanstackStart(), react(), redact()],
})
For Next.js there's a withRedact wrapper from @tanstack/redact/next (the docs have the exact setup).
That's it. Install, add a line, build, measure.
Think about what this means in a real team:
-
No refactor PR. Your diff is a config file and a
package.jsonline. Easy to review, easy to reason about. - Rollback is trivial. Remove the plugin and you're back on React. That makes it a low-risk experiment instead of a bet-the-quarter decision.
-
Your ecosystem comes with you. Component libraries, form libraries, query libraries — they keep importing
reactand just get Redact in the browser. - Nobody has to learn anything. New hires still write React. Your docs, your Storybook, your patterns all stay valid.
I've led teams where the right performance move was blocked purely because it meant touching hundreds of components. A tool that gives you the win without that cost is the kind of thing that actually gets adopted.
The numbers
The TanStack team built the same small app with React and with Redact in each framework, then added up the gzip size of all emitted client JS and CSS:
| Framework | React | Redact | Saved |
|---|---|---|---|
| TanStack Start | 109.3 KB | 62.3 KB | 47.0 KB (43.0%) |
| Vite | 68.3 KB | 20.1 KB | 48.1 KB (70.5%) |
| Vinext | 138.1 KB | 98.8 KB | 39.3 KB (28.5%) |
| Next.js | 175.6 KB | 139.9 KB | 35.7 KB (20.4%) |
Those are the default builds. If you turn off optional features your app doesn't use, Start drops to 59.8 KB and a client-only Vite app goes down to 12.6 KB.
The team is upfront that these are whole-build totals for a small sample app, so your percentages will vary. The benchmark source is public if you want to reproduce it. I appreciate that honesty — it's rare in launch posts.
Why do 35–48 KB of gzip matter? Because that's JavaScript every user downloads, parses and executes before your app becomes interactive. On a fast laptop on fiber, sure, you won't notice. On a mid-range Android phone on a patchy mobile connection — which is a huge share of real users in markets like India — that's a noticeable chunk of your load budget, and it's coming out of the one dependency you thought you couldn't shrink.
It's not a toy React
My first worry with any "React but smaller" project is: what breaks? A counter demo working proves nothing. Real apps lean on hydration, Suspense, transitions, forms, error boundaries, and a pile of framework internals.
Redact supports JSX, hooks, context, refs, memo, lazy, portals, error boundaries, streaming SSR and hydration. Pending transitions, form actions and optimistic state are covered too.
What gave me more confidence is how they test it. The compatibility suite runs real browser journeys in Chromium, Firefox and WebKit: retained layouts, back/forward navigation, redirects, canceled navigation, Server Actions, pending forms, optimistic updates, uploads, streamed Suspense, and Fast Refresh that keeps state across edits. That's the stuff that actually breaks in production.
And you don't lose your tooling: React DevTools still works for inspection, editing and commit profiling in development.
Server Components stay with React
This is a really smart design call. With Next.js and Vinext, Redact doesn't try to replace everything. React still does server rendering and Server Components. The framework still owns routing, caching and Server Action transport. Redact picks up in the browser, hydrates the HTML, and runs your client components.
So you're not swapping out your framework's server story. You're only swapping the part that ships to your users' devices. That keeps the blast radius small, and it's exactly where the bytes matter.
Go smaller if you want to
For Start and Vite you can tune what's included:
// Start: keep hydration and router features, drop what you don't use
redact({
features: {
activity: false,
fragmentRefs: false,
portal: false,
},
})
// Vite: a client-only app with no optional features
redact({ preset: 'nano' })
The default is a sensible "full" preset, and the docs recommend starting there and only disabling features after checking what your app (and its dependencies) actually use. Next.js and Vinext use the full runtime so framework features keep working.
"Why not just use Preact?"
Fair question, and the Redact team answers it directly: Preact is smaller. But Redact keeps more of the modern React behavior your app may depend on, like pending transitions, deferred results and form actions. If you're on a modern React stack and you don't want to discover compatibility gaps one bug report at a time, that matters more than the last few kilobytes.
The honest tradeoff
Redact renders synchronously. No time slicing, no interruptible renders. Suspended transitions, pending forms and optimistic state still work, but a genuinely heavy render can block input while it runs.
For a lot of apps, that's fine. Marketing and content sites, docs, ecommerce storefronts, typical CRUD dashboards, embedded widgets — most of their renders are small, and the bundle savings are pure win.
Where I'd be careful: screens that render huge trees in one go, or code that relies on useTransition to keep typing responsive during an expensive update. The TanStack team says the same thing: try it against your busiest screens first. Their compatibility guide spells out the boundaries clearly.
That's not a knock on Redact. It's a clear, documented choice, and it's the reason the runtime can be this small.
Why I'm a fan
What I like most is the attitude behind it. This isn't "React is bad, switch to X." Tanner is explicit that he still believes in React and wants it to keep growing. Redact is just another runtime option inside the React ecosystem for people who want to keep their components and ship less.
That's the kind of tool I want more of: respects the code you already have, gives you a real win, tells you the tradeoff upfront, and lets you walk back with one line if it doesn't fit.
If you have a React app on Vite, TanStack Start or Next.js, the cheapest experiment you'll run this month is:
pnpm add @tanstack/redact- Add
redact()(orwithRedact) to your config - Build, compare your bundle, click through your heaviest screens
Worst case you learn something about your app. Best case you delete tens of kilobytes from every page load without changing a single component.
Links
- Launch post: https://tanstack.com/blog/introducing-tanstack-redact
- Docs: https://tanstack.com/redact/latest
- Compatibility guide: https://tanstack.com/redact/latest/docs/compatibility
- GitHub: https://github.com/TanStack/redact
Top comments (0)