Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: React vs Redact vs Preact
---

| Measured or tested | React 19.3.1 | Redact¹ | Preact compat 11.1.0 |
|---|---:|---:|---:|
| Overall speed score, five list workloads¹ | 100 | 78 | 73 |
| Test bundle, gzip² | 66.3 KiB | 19.8 KiB | 7.1 KiB |
| Basic Vite client app, React 18 and 19 fixtures | ✅ | ✅ | ✅ |
| Pending transitions and deferred results | ✅ | ✅ | ❌ |
| React 19 action state | ✅ | ✅ | ❌ |
| TanStack Start, React 18 and 19 fixtures | ✅ | ✅ | ❌ |
| React Router data mode | ✅ | ✅ | ❌ |
| React Router SSR, Redact 0.2.3 | ✅ | ✅ | ❌ |
| Next Pages, Webpack, native React server | ✅ | ✅ | ✅ |
| Next App Router, Webpack | ✅ | ✅ | ❌ |
| Next Pages and App Router, Turbopack | ✅ | ✅ | ❌³ |
| Vinext App Router | ✅ | ✅ | ❌ |

✅ passed the tested fixture, ❌ failed the tested setup, not proof that every possible integration fails. Framework versions and failures are in the [full matrix](./framework-comparison.md).

¹ Performance, size, and the original compatibility runs use Redact 0.2.1. Only React Router SSR was rerun with published 0.2.3, on October 28. The performance numbers below have not been remeasured for 0.2.3.

Higher is faster, React = 100. This is a score for five synchronous list workloads, not whole-app performance. It is the geometric mean of React time divided by each runtime's time, with equal weight per workload. The two runs scored React 100, Redact 80 and 78, Preact 78 and 73. Those are repeat results, not confidence intervals.

² The same production benchmark application, including its checks, bundled with each runtime and gzipped. These are tree-shaken application bundles, not each runtime's complete API surface. Redact uses its default Vite configuration. Next and Vinext keep native React on the server.

³ The tested Preact Turbopack aliases left native React loaded, so they did not produce a working Preact replacement.

## Speed by operation

Median milliseconds per completed operation in the repeat run, lower is faster. Every operation uses 500 rows except insertion, which alternates between 500 and 750.

| Operation | React ms | Redact ms | Preact ms |
|---|---:|---:|---:|
| Mount and unmount 500 rows | 0.371 | 0.421 | 0.366 |
| Update 500 row props | 0.128 | 0.218 | 0.197 |
| Update state across 500 rows | 0.112 | 0.210 | 0.200 |
| Reverse 500 keyed rows | 0.398 | 0.253 | 0.473 |
| Add or remove 250 rows | 0.189 | 0.282 | 0.285 |

React was faster on props, state, and insertion/removal. Redact was faster on reversal. React and Preact were close on mounting. This suite does not support a blanket claim that Redact is faster.

Measured October 27, 2026 on Apple M5 Pro, Chromium 156.0.8078.4, Node v24.15.0, without CPU throttling. Each run uses seven fresh browser blocks, rotates runtime order, warms up each workload 20 times, then times 200 operations. The repeat verifies committed row counts, text, and retained keyed DOM identity during warmup, then checks final DOM and unmount cleanup. Timing uses synchronous flushes and excludes the assertions. It includes DOM mutation, not layout or paint.

These tests do not measure startup, memory, SSR, hydration speed, transitions, responsiveness under competing work, mobile devices, Firefox, or Safari. Missing React behavior is shown in the capability rows and does not earn a speed bonus.

[Benchmark source and commands](https://github.com/TanStack/redact/tree/6a2ca15ed0c9453aebf013645381b334fe0fa13d/scripts/framework-comparison), [first run](https://github.com/TanStack/redact/blob/6a2ca15ed0c9453aebf013645381b334fe0fa13d/benchmarks/results/framework-comparison/performance.json), [repeat run](https://github.com/TanStack/redact/blob/6a2ca15ed0c9453aebf013645381b334fe0fa13d/benchmarks/results/framework-comparison/performance-repeat.json), [API and size details](./preact.md).
12 changes: 12 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@
"label": "Compatibility",
"to": "compatibility"
},
{
"label": "React vs Redact vs Preact",
"to": "comparison"
},
{
"label": "Redact vs Preact",
"to": "preact"
},
{
"label": "Framework Compatibility Matrix",
"to": "framework-comparison"
},
{
"label": "TanStack Start",
"to": "start"
Expand Down
71 changes: 71 additions & 0 deletions docs/framework-comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: Framework Compatibility Matrix
---

Can you replace React with Preact compat or Redact? It depends on the framework, its version, and the features your app uses. We tested the same small applications with native React, Preact **11.1.0**, and Redact **0.2.1** on **October 27, 2026**. On **October 28**, we reran React Router framework SSR with published **Redact 0.2.3** and native React in both modes. Only that row uses 0.2.3; the other rows remain the original versioned results.

**✅ means the fixture passed in development and production. ❌ means at least one required check failed in the tested setup.** An X does not prove that a custom integration is impossible. In particular, the Preact Turbopack rows fail because native React still loads, not because we verified a working Preact replacement and then found an application bug.

All native React controls passed. SSR fixtures check server HTML, preservation of the original DOM during hydration, stateful clicks, navigation without reloading, and browser history. Start also checks a server function; React Router framework mode checks a server loader and form action. Vite rows cover basic client rendering and state updates, not every React API.

| Fixture | React control | Preact 11.1.0 | Redact 0.2.1; SSR row 0.2.3 |
|---|---|---|---|
| Vite 8.3.5 + React 18.3.1 | ✅ | ✅ | ✅ |
| Vite 8.3.5 + React 19.3.1 | ✅ | ✅ | ✅ |
| React Router 8.5.0, data mode + React 19.3.1 | ✅ | ❌ | ✅ |
| React Router 8.5.0, framework SSR + React 19.3.1 | ✅ | ❌ | ✅ |
| TanStack Start 1.169.4 + React 18.3.1 | ✅ | ❌ | ✅ |
| TanStack Start 1.169.4 + React 19.3.1 | ✅ | ❌ | ✅ |
| Next 16.4.3 Pages + React 18.3.1, Webpack | ✅ | ✅ | ✅ |
| Next 16.4.3 Pages + React 19.3.1, Webpack | ✅ | ✅ | ✅ |
| Next 16.4.3 App Router, Webpack | ✅ | ❌ | ✅ |
| Vinext 1.3.0 App Router + React 19.3.1 | ✅ | ❌ | ✅ |
| Next 16.4.3 Pages + React 18.3.1, Turbopack | ✅ | ❌ | ✅ |
| Next 16.4.3 Pages + React 19.3.1, Turbopack | ✅ | ❌ | ✅ |
| Next 16.4.3 App Router, Turbopack | ✅ | ❌ | ✅ |

The separate official `next-plugin-preact` 3.0.7 setup, including its recommended React package aliases, also failed all four Next Pages/App development and production checks. Pages hit a missing `renderToReadableStream`; App production hit a missing `cache`. The passing Pages rows above use browser-only replacement with native React on the server.

## Where the failures happen

**React Router 8.5.0 with Preact:** the framework imports `useOptimistic` internally. Preact compat does not export it, so both the data-mode and framework-mode fixtures fail before interaction. Your own components do not have to use that hook to hit the failure.

**React Router framework mode with Redact:** 0.2.3 passes development and production, including preservation of the server-rendered button, state updates, server form actions, and navigation. Version 0.2.1 replaced the button during production hydration because it did not adopt module-preload links left in the body. That fix is now published. [0.2.3 production result](https://github.com/TanStack/redact/blob/6a2ca15ed0c9453aebf013645381b334fe0fa13d/benchmarks/results/framework-comparison/release-0.2.3/router-framework-redact-production.json), [native React control](https://github.com/TanStack/redact/blob/6a2ca15ed0c9453aebf013645381b334fe0fa13d/benchmarks/results/framework-comparison/release-0.2.3/router-framework-react-production.json).

**TanStack Start with Preact:** the standard Preact Vite preset does not provide the React Refresh runtime Start expects in development. Production builds and sends server HTML, but a pending Promise escapes during client startup and the counter never becomes interactive. This happened in both the React 18 and React 19 fixtures. A custom framework integration was not built to bypass these failures.

**Next App Router with Preact and Webpack:** server HTML arrives, but the browser replacement fails to complete hydration. Development reports an error in Next's link-observer setup; production does not reach an interactive counter. Next Pages with Webpack passes, which is why “Next.js support” needs more than one row.

**Next with Preact and Turbopack:** the tested browser aliases leave native React running. The page appearing to work is not a Preact pass. Redact's dedicated adapter passes the runtime identity check and the application checks.

**Vinext App Router with Preact:** browser-only aliases preserve native React on the server, but client hydration raises a pending Promise and does not become interactive. Native React and Redact pass the same fixture.

[Exact errors, repeat runs, configurations, and raw results](https://github.com/TanStack/redact/blob/6a2ca15ed0c9453aebf013645381b334fe0fa13d/benchmarks/FRAMEWORK_COMPARISON.md).

## A working Vite app does not establish React behavior parity

A separate fixture suspends a transition and a search result. These results hold for both React 18.3.1 and React 19.3.1 in development and production:

| Behavior while new content is suspended | React | Preact compat | Redact |
|---|---|---|---|
| Transition reports pending | ✅ | ❌ | ✅ |
| Transition keeps previous content visible | ✅ | ❌ | ✅ |
| Deferred query keeps previous results visible | ✅ | ❌ | ✅ |

Preact's transition renders the loading fallback and reports pending as `false`. Its deferred-value hook immediately supplies the new value, so the old search results disappear into the loading fallback. React and Redact retain the previous results until the new content is ready.

With React 19, we also executed `useActionState`: React and Redact tracked pending work and committed the result. Preact does not export the hook. These are behavioral checks, separate from simply checking whether an API name exists. [Behavior results](https://github.com/TanStack/redact/blob/6a2ca15ed0c9453aebf013645381b334fe0fa13d/benchmarks/results/framework-comparison/modern.json).

The deeper core fixture also checks context, refs, keyed identity, portals, effects, string SSR, hydration, external stores, and lazy loading. Preact matched those rendering checks, but differed from React in some `react-is` element and context detection checks. That can matter for libraries that inspect React element types. [Core results](https://github.com/TanStack/redact/blob/6a2ca15ed0c9453aebf013645381b334fe0fa13d/benchmarks/results/framework-comparison/core.json).

## What this table does not cover

The baseline covers **78 configurations in Chromium 156 on macOS with Node 24.15.0**. The four React Router follow-up runs use Node 24.22.0 and the same Chromium version. These are small fixtures, not every feature in each framework. They do not establish Firefox or Safari support, hosted deployments, arbitrary component libraries, Fast Refresh state preservation, every streaming and Server Action case, or other framework releases. React Router's optional RSC mode was not tested. No speed or memory ranking follows from these results.

React Router 8.5.0 requires React >=19.2.7, and Vinext 1.3.0 requires React ^19.3.1. Their React 18 combinations are outside their declared support ranges. Next App Router uses its own React build, so we do not label it a React 18 application just because React 18 can be installed for Pages Router.

Next and Vinext retain native React on the server in these replacement tests. Start and React Router use their normal SSR paths with server dependencies bundled to apply the runtime aliases consistently. No missing API was stubbed to make an application pass.

Use these results to narrow your choices, then test your actual app. Preact works in some modern React setups. Redact passes more of these particular framework and behavior checks. Neither is a universal React replacement.

[Reproduce the matrix](https://github.com/TanStack/redact/blob/6a2ca15ed0c9453aebf013645381b334fe0fa13d/scripts/framework-comparison/README.md), [broader API and size comparison](./preact.md), [Redact compatibility limits](./compatibility.md).
Loading
Loading