# TanStack Redact for programmatic SEO: bundle size vs Core Web Vitals

> What TanStack Redact is, how much it shrinks Next.js bundles, what breaks, and how to pilot it on one programmatic template before trusting it with your CWV.

Maciej Wiatr, 2026-11-02. https://maciejwiatr.com/blog/tanstack-redact-programmatic-seo-core-web-vitals

TanStack Redact is a build-time swap. It replaces React and React DOM in your client bundle with a smaller runtime, and your components stay as they are. For a directory or comparison portal with thousands of templated pages, that is attractive: one config change cuts JavaScript on every page at once. The open question is whether it moves the Core Web Vitals that templated pages are judged on, and the sources don't settle it yet. This post covers what Redact is, what the numbers measure, what can break, and how I would trial it on one template before touching the whole site. Facts were checked on 2 November 2026, a week after version 0.2.0.

## What is TanStack Redact?

TanStack calls it ["a smaller browser replacement for React and React DOM"](https://tanstack.com/blog/introducing-tanstack-redact). Tanner Linsley's launch post from 26 October 2026 announces version 0.2.0 with "stable browser integrations" for TanStack Start, Vite, Vinext and Next.js, on both the App Router and the Pages Router. The current release is 0.2.3, from 28 October.

The [overview docs](https://tanstack.com/redact/latest/docs/overview.md) describe it as "an alternative runtime for React apps… not a replacement for React." Your imports don't change. The swap happens in the build.

On Next.js you wrap your config with `withRedact`, according to the [Next.js docs](https://tanstack.com/redact/latest/docs/next.md):

```js
// next.config.mjs
import { withRedact } from '@tanstack/redact/next'

const nextConfig = {}

export default withRedact(nextConfig)
```

Those docs also set some rules:

- Keep `react` and `react-dom` installed.
- Don't set `jsxImportSource` to Redact.
- Use stable Next.js 15.5 or 16.0 to 16.4. Prereleases and untested minors are rejected.
- Expect the full runtime. Turbopack and Webpack both work, but the Vite feature presets don't apply.

On Vite and TanStack Start, a `redact()` plugin redirects React imports in the client and SSR builds, according to the [GitHub README](https://github.com/TanStack/redact). Start also needs `ssr: { noExternal: true }`. You install it with `pnpm add @tanstack/redact`.

If you search for "tanstack redact github", you may land on the repo's root [`package.json`](https://github.com/TanStack/redact/blob/main/package.json). It shows `tanstack-react` at version 0.0.0, marked private. That file belongs to the monorepo. The package you install is `@tanstack/redact`.

## How much smaller is a Next.js bundle with TanStack Redact?

The launch post built a small app twice, once with React and once with Redact. It then measured the gzip size of all emitted client JS and CSS, lazy chunks included:

![TanStack blog table of gzip client bundle sizes with React vs Redact for TanStack Start, Vite, Vinext and Next.js.](https://maciejwiatr.com/assets/blog/tanstack-redact-programmatic-seo-core-web-vitals/shot-1.webp "The launch post's own comparison table: look at the Next.js row, where the saving is 35.7 KB (20.4%), much smaller than the 70.5% on Vite (tanstack.com, Nov 2026)")

| Framework | React | Redact | Saved |
|---|---|---|---|
| Vite | 68.3 KB | 20.1 KB | 48.1 KB (70.5%) |
| TanStack Start | 109.3 KB | 62.3 KB | 47.0 KB (43.0%) |
| 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%) |

On Next.js the saving is 35.7 KB, or 20.4%. Two caveats apply. First, these are whole-build totals, so they don't tell you what one page downloads. Linsley himself says the percentages "will vary by app." Second, only the runtime gets smaller. The more of your own code you ship, the smaller the share you save.

The real apps in the sources bear that out:

- **tanstack.com** cut 980 KB of client JS, which is 4.7% of its full bundle ([Projecting React](https://tannerlinsley.com/posts/projecting-react)).
- **Dotflowy**, a Vite outliner, saved 44,365 bytes, or 5.79% ([issue #462](https://github.com/cameronapak/dotflowy/issues/462)).
- **Camox**, a website builder, saw template page JS go from 200 to 155 KB, and from 360 to 314 KB with three content blocks ([issue #625](https://github.com/remidej/camox/issues/625)). By my arithmetic that is about 13% to 22%.

Look out for "66% smaller" in other coverage. That figure is the runtime alone, measured on the [performance page](https://tanstack.com/redact/latest/docs/performance.md): React 19.3.0 at 69,162 bytes gzip against Redact's default Vite build at 23,311 bytes. It doesn't describe your app.

## Does TanStack Redact change what Googlebot sees on programmatic pages?

On Next.js, server HTML doesn't change. The docs say: "Next.js keeps React for server rendering, Server Components, action transport, and cache behavior. Server imports aren't rewritten." The HTML Googlebot fetches is the same HTML you serve today.

Googlebot also renders pages in an ["evergreen version of Chromium"](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics), so client code still matters. My inference is that a hydration bug could make the rendered DOM differ from the server HTML. Redact has shipped such bugs recently: 0.2.3 fixed server-rendered metadata [triggering body replacement during hydration](https://github.com/TanStack/redact/releases). Run the template through URL Inspection on both builds and compare the rendered HTML.

On TanStack Start and Vite, the picture differs. Redact also handles the SSR build there, everything except RSC, so your server output does come from Redact.

## Will a smaller JS bundle improve LCP and INP on templated pages?

Nobody has shown that it does. Google's targets are [LCP within 2.5 s and INP under 200 ms](https://developers.google.com/search/docs/appearance/core-web-vitals). TanStack's own launch post doesn't mention CWV at all, and [Appwrite quotes TanStack](https://appwrite.io/blog/post/tanstack-redact-lighter-react-runtime) saying "smaller bundles don't automatically mean faster hydration or interactions."

![Google Search Central page listing Core Web Vitals: LCP within 2.5 seconds, INP under 200 ms and CLS under 0.1.](https://maciejwiatr.com/assets/blog/tanstack-redact-programmatic-seo-core-web-vitals/shot-2.webp "Google's targets that any Redact pilot has to hit: LCP within 2.5 seconds and INP under 200 milliseconds (developers.google.com, Nov 2026)")

The lab data points both ways:

- On mostly prerendered tannerlinsley.com, Lighthouse LCP improved from 1.42 to 1.24 s, or 12.1% (median of 30 runs).
- On RSC-heavy pages of tanstack.com, LCP was 8% to 43% worse. That measurement used an early TanStack Start version and hasn't been rerun on 0.2.x or on Next.js.
- In TanStack's microbenchmarks, hydration was 21.4% slower and passive effects 27.0% slower. Sparse state updates were 71.9% faster.

Hydration is what a programmatic landing page does on every visit. If your LCP element streams through Server Components on the App Router, that RSC regression matters most. My guide to [programmatic SEO in Next.js](https://maciejwiatr.com/blog/programmatic-seo-nextjs-app-router-guide) covers that setup in more detail.

For INP, less script can mean fewer long tasks during load, and [web.dev explains](https://web.dev/articles/optimize-inp) that long tasks block input. But TanStack says its "handler-to-commit time isn't paint latency or field INP," and no field data has been published.

## What breaks with Redact's synchronous rendering, and which pages should you test first?

The [compatibility docs](https://tanstack.com/redact/latest/docs/compatibility.md) say "rendering passes stay synchronous, without time slicing or interruptible component renders." In practice:

- `useDeferredValue` "retains stale results" but schedules nothing at a lower priority.
- Suspense has no priority scheduling.

In React, [`useDeferredValue`](https://react.dev/reference/react/useDeferredValue) lets typing interrupt a background re-render of a long list. Under Redact, each keystroke can force a full re-render that can't be interrupted. The launch post admits "a heavy render can still delay input."

On a portal, I would test these first:

1. **Faceted filters and search-as-you-type** over large result lists.
2. **Virtualized lists.** Windowing failed on Redact in Dotflowy and passed on React.
3. **Dialogs.** In Dotflowy, Escape didn't dismiss two of them.
4. **Maps and graphs built on `useSyncExternalStore`.** Fixes for zustand and React Flow only landed in 0.2.1.
5. **Suspense-heavy or RSC-streamed hero sections**, where LCP is decided.

Browser coverage has a gap. Safari and Firefox are listed as unverified in the compatibility docs, although the Next.js test matrix runs WebKit. Check real iPhones yourself.

## How to roll out Redact on one programmatic template

Neither integration offers a per-route switch, and the alias covers the whole client build. Dotflowy's author concluded that "per-component adoption is not a safe small pilot." Camox found the alias also replaced React in its separate editing app.

So my recommendation, which comes from no source, is to run a second build. The flowchart shows the setup: route one template's URLs to the Redact build at the edge, then compare field data.

```mermaid
flowchart TD
    A[Second build with Redact] --> B[Route one template]
    B --> C[RUM with web-vitals]
    B --> D[URL Inspection check]
    C --> E[Compare LCP and INP]
    E --> F[CrUX History weekly]
    F --> G[Search Console URL group]
    G --> H{Better after 28 days?}
    H -->|Yes| I[Add next template]
    H -->|No| J[Route back to React]
```

Tag your RUM data with the build so you can split it:

```js
import { onLCP, onINP } from 'web-vitals'

const build = document.documentElement.dataset.build // "react" or "redact"

function send(metric) {
  navigator.sendBeacon('/rum', JSON.stringify({ ...metric, build, path: location.pathname }))
}

onLCP(send)
onINP(send)
```

Your own RUM is the fastest signal. Google's data lags behind it:

- The [Search Console CWV report](https://support.google.com/webmasters/answer/9205520) groups similar URLs, reflects the last 28 days, and rates a group by its worst metric. Groups without enough data fall back to the origin.
- The [CrUX History API](https://developer.chrome.com/docs/crux/history-api) gives weekly 28-day periods split by phone, desktop and tablet. It updates Mondays around 04:00 UTC.
- Long-tail URLs may never show up at page level. [CrUX only includes](https://developer.chrome.com/docs/crux/methodology) pages that are "sufficiently popular," and the threshold isn't published.

For the measurement side in general, see the [measurement hub](https://maciejwiatr.com/programmatic-seo/measurement).

## Is TanStack Redact production-ready for large Next.js or TanStack Start sites?

It is ready to pilot, and I wouldn't make it the default yet.

The case for it:

- The Next.js gate passes [162 configurations and 2,694 scenario groups](https://github.com/TanStack/redact/blob/main/benchmarks/NEXT_COVERAGE.md).
- TanStack calls the 0.2.0 integrations stable.

The case for waiting:

- It is pre-1.0, and the MIT license was only declared in 0.2.1.
- The only named production sites are TanStack's own.
- Hydration fixes were still landing on 27 and 28 October.
- Both independent evaluations, Dotflowy and Camox, said defer or "not yet."
- Camox also found about 25 KB of unused visitor JS, a larger and lower-risk win.

Before shipping Redact anywhere, I would check:

- Unused client JS on the template.
- Rendered HTML parity in URL Inspection.
- Filter typing on a throttled phone.
- RSC-streamed LCP.
- 28 days of field data on one template before adding the next.
