{"id":51076932,"url":"https://github.com/tanstack/redact","last_synced_at":"2026-06-23T15:01:53.969Z","repository":{"id":357425726,"uuid":"1215537628","full_name":"TanStack/redact","owner":"TanStack","description":"An alternative logical projection of React with 100% API compliancy but simpler implementation resulting in smaller bundle size and better performance.","archived":false,"fork":false,"pushed_at":"2026-06-14T06:24:31.000Z","size":343,"stargazers_count":120,"open_issues_count":1,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-06-15T07:27:56.060Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TanStack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T02:36:25.000Z","updated_at":"2026-06-14T10:41:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/TanStack/redact","commit_stats":null,"previous_names":["tanstack/redact"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/TanStack/redact","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TanStack%2Fredact","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TanStack%2Fredact/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TanStack%2Fredact/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TanStack%2Fredact/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TanStack","download_url":"https://codeload.github.com/TanStack/redact/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TanStack%2Fredact/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34694786,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-23T15:01:52.993Z","updated_at":"2026-06-23T15:01:53.957Z","avatar_url":"https://github.com/TanStack.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# redact\n\n**React, redacted.** A minimal React-19-API-compatible drop-in replacement, **~4× smaller** than canonical React. Shipped as a single `@tanstack/redact` package with subpath exports for the `react` / `react-dom` / `react-dom/server` / `scheduler` / `react/jsx-runtime` shapes. User code keeps its canonical `import { useState } from 'react'` — the swap happens at the bundler level.\n\n- **10.03 KB** gzip at full drop-in parity (vs ~45 KB for React 19)\n- **7.49 KB** gzip with every opt-in feature stubbed (`nano` preset)\n- **731/731** unit + integration tests passing, SSR + streaming Suspense + hydration included\n- Running in production on [tanstack.com](https://tanstack.com) as of 2026-04-20\n\nFor background on the motivation, the \"projection\" framing, the architectural approach, and the production performance results, see the blog post: [Projecting React](https://tannerlinsley.com/posts/projecting-react).\n\n---\n\n## Quick start\n\n```bash\npnpm add @tanstack/redact@next\n```\n\n```ts\n// vite.config.ts\nimport { defineConfig } from 'vite'\nimport { redact } from '@tanstack/redact/vite'\n\nexport default defineConfig({\n  plugins: [redact()],\n})\n```\n\nThat's it. The plugin aliases `react` / `react-dom` / `scheduler` across Vite's client + ssr environments. The RSC environment is skipped so `@vitejs/plugin-rsc` keeps using real React for Flight serialization. User-facing imports are unchanged:\n\n```ts\nimport { useState, Suspense } from 'react'\nimport { createRoot, hydrateRoot } from 'react-dom/client'\n```\n\n### Shrink further with feature flags\n\nTwo presets — pick a starting point, flip flags from there:\n\n```ts\nredact({ preset: 'full' })        // 10.03 KB — everything on, opt OUT individual features\nredact({ preset: 'nano' })        // 7.49 KB — everything off, opt IN what you need\n```\n\nOpt out from `full`:\n\n```ts\nredact({\n  preset: 'full',\n  features: {\n    hydration: false,                  // SPA only — no SSR\n    classComponents: false,            // function components only\n  },\n})\n```\n\nOpt in from `nano`:\n\n```ts\nredact({\n  preset: 'nano',\n  features: {\n    context: true,                     // bring back just what you need\n    suspense: true,\n  },\n})\n```\n\nFull feature matrix and alternative configuration paths below.\n\n---\n\n## Feature flags\n\n### Feature matrix\n\n| Flag | Full behavior | Stub behavior (when `false`) | Savings (gzip) |\n|---|---|---|---:|\n| `portal` | `createPortal` into alt container | Children render in place, `container` ignored | ~30 B |\n| `context` | Provider push/pop + consumer walk | Provider → Fragment; `useContext` returns default | ~80 B |\n| `suspense` | Boundary + fallback + streaming hydration, DOM-preserving re-suspension | Suspense → Fragment; thenables retry on settle | **~820 B** |\n| `memo` | `shallowEqual` prop-equality gate | Passes through every parent render | ~80 B |\n| `forwardRef` | Ref forwarded to inner fn | Ref dropped (React 19 \"refs as props\" still works) | ~70 B |\n| `lazy` | Full hydration coordination | Sync-resolvable payloads work; async retries on settle | ~20 B |\n| `classComponents` | Full lifecycle + `contextType` + error boundaries | `constructor` + `render` + `setState` only | ~200 B |\n| `hydration` | SSR DOM adoption, streaming boundaries, scroll guard, event replay | `hydrateRoot` throws; use `createRoot` for SPA | **~1310 B** |\n\n**Always on** (irreducible core, ~7.5 KB gzip): fiber reconciler with keyed child diffing, host DOM mount/update, `useState` / `useReducer` / `useEffect` / `useLayoutEffect` / `useInsertionEffect` / `useRef` / `useMemo` / `useCallback` / `useId` / `useSyncExternalStore` / `use` (for thenables), native event binding, Fragments, StrictMode/Profiler (aliased to Fragment), element creation + JSX runtime.\n\n### Presets\n\n| Preset | What's on | `react-dom/client` gzip | Intent |\n|---|---|---:|---|\n| `full` (default) | all 8 features | **10.03 KB** | Drop-in React — opt OUT individual features you don't need |\n| **`nano`** | none | **7.49 KB** | Start minimal — opt IN individual features you need |\n\nTwo presets, not a spectrum: every app either wants most of React (start from `full`, opt out) or a tight bundle (start from `nano`, opt in). Per-feature overrides merge on top of preset defaults either way.\n\n---\n\n## Configuration\n\nFour ways to configure, depending on your bundler and ergonomics preference.\n\n### 1. Vite plugin (recommended)\n\n`@tanstack/redact/vite`'s `redact()` plugin. Covered in [Quick start](#quick-start) above. Full options:\n\n```ts\ninterface RedactOptions {\n  preset?: 'nano' | 'full'                         // default: 'full'\n  features?: {\n    portal?: boolean\n    context?: boolean\n    suspense?: boolean\n    memo?: boolean\n    forwardRef?: boolean\n    lazy?: boolean\n    classComponents?: boolean\n    hydration?: boolean\n  }\n  skip?: ReadonlyArray\u003cstring\u003e                     // don't alias these specifiers\n  resolveFrom?: string                             // override package resolution root\n  packageRoots?: Record\u003cstring, string\u003e            // explicit package paths\n}\n```\n\nThe plugin also handles Vite-specific wiring: `optimizeDeps.exclude` for the shim packages, `ssr.noExternal` so SSR bundles inline them, and an `enforce: 'pre'` hook ordering so the alias wins over other resolvers.\n\n### 2. Bundler aliases (Webpack / Rollup / esbuild / …)\n\nThe package exposes every feature module as a `./features/*` subpath export. Any bundler with a path-alias feature can redirect a feature's `index` to its `stub` to opt the feature out of the bundle.\n\n**Subpath layout:**\n\n```\n@tanstack/redact/features/\n  portal/ context/ suspense/ memo/ forward-ref/ lazy/ class/ hydration/\n    index    ← re-exports from ./full by default\n    full     ← real implementation\n    stub     ← graceful degradation\n```\n\n**Webpack example (stubs hydration + suspense):**\n\n```js\n// webpack.config.js\nmodule.exports = {\n  resolve: {\n    alias: {\n      '@tanstack/redact/features/hydration/index':\n        '@tanstack/redact/features/hydration/stub',\n      '@tanstack/redact/features/suspense/index':\n        '@tanstack/redact/features/suspense/stub',\n    },\n  },\n}\n```\n\n**Rollup:**\n\n```js\nimport alias from '@rollup/plugin-alias'\n\nexport default {\n  plugins: [\n    alias({\n      entries: [\n        {\n          find: '@tanstack/redact/features/hydration/index',\n          replacement: '@tanstack/redact/features/hydration/stub',\n        },\n      ],\n    }),\n  ],\n}\n```\n\n**esbuild:**\n\n```js\nimport { build } from 'esbuild'\n\nawait build({\n  entryPoints: ['src/app.tsx'],\n  bundle: true,\n  alias: {\n    '@tanstack/redact/features/hydration/index':\n      '@tanstack/redact/features/hydration/stub',\n  },\n})\n```\n\n**Gotchas:**\n\n- **On-disk folder names vs. config keys**: `forward-ref/` ↔ `forwardRef`, `class/` ↔ `classComponents`. When configuring aliases manually, match the on-disk folder.\n- **Single-instance requirement**: `@tanstack/redact` (and any subpath of it) must resolve to **one** installed copy in your app. Mixing source + dist, or two different tarballs, duplicates `ReactSharedInternals` and breaks hooks. The package's `ReactSharedInternals` is stashed on `globalThis` under a registered symbol as a defense-in-depth, but you should still aim for a single copy.\n- **Feature interdependencies**: Suspense's full implementation imports hydration helpers. If hydration is stubbed but Suspense is full, the Suspense feature uses hydration's no-op stubs (fine — you're not hydrating). Suspense stubbed + hydration full is also fine (streaming boundaries just won't render fallback UI because `Suspense` maps to Fragment).\n\n### 3. Prebuilt bundle presets (planned)\n\nNot yet shipped. The planned shape:\n\n```ts\nimport { createRoot } from '@tanstack/redact/dom/nano/client'\n```\n\nZero bundler configuration; useful for script-tag usage, non-bundler Node tools, or users who just want the smallest install without thinking about it.\n\n**Why not yet:** the preset bundle would need its own self-contained `_all.js` built with the right stubs compiled in — stubs can't reliably overlay a module that registers full variants first (registration order matters, last-write-wins). We want to gather real Vite-plugin usage data before deciding which prebuilt configurations are worth publishing. Open an issue with your use case if this unblocks you.\n\n### 4. npm aliases (limited)\n\n`npm:` package aliases in `package.json` work for the top-level `react` mapping but **not** for subpaths — there's no spec-level way to point `react-dom` at a subpath like `@tanstack/redact/dom` purely via `package.json`. So this path only gets you partway:\n\n```jsonc\n// package.json — works, but only swaps `react` itself\n{\n  \"dependencies\": {\n    \"react\": \"npm:@tanstack/redact@next\"\n  }\n}\n```\n\nAnything that imports `react-dom`, `react-dom/client`, `react-dom/server`, or `scheduler` will still resolve to the real React in `node_modules` unless your bundler can rewrite those specifiers — at which point you may as well use Path 1 (Vite plugin) or Path 2 (bundler aliases). This is a real trade-off of the single-package layout: the install side is simpler but the no-bundler workflow loses some flexibility versus a multi-package shim. If you need a no-bundler full swap, open an issue with your toolchain and we can publish individual `@tanstack/redact-dom`, `@tanstack/redact-server`, etc. compatibility re-export packages.\n\n---\n\n## Advanced: authoring custom features \u0026 bundler plugins\n\nIf you're extending the system, writing a bundler plugin for a tool without one, or just curious how the swap works — the internal API surface is exported from `@tanstack/redact/_all`.\n\n### Registration primitives\n\nFeature modules self-register by calling these at module load:\n\n```ts\nimport {\n  registerRenderer,\n  registerTypeMatcher,\n  registerElementMarker,\n  type RenderFn,\n  type TypeMatcher,\n} from '@tanstack/redact/_all'\n\n// Install a renderer for a FiberTag. Later calls overwrite earlier ones —\n// stubs exploit this order-dependence.\nfunction registerRenderer(tag: FiberTag, fn: RenderFn): void\n\n// Add a type matcher. Iterated in registration order during fiber creation,\n// after core checks (string → Host, REACT_FRAGMENT_TYPE → Fragment) and\n// before the function-vs-class fallback.\ntype TypeMatcher = (type: any, marker: any) =\u003e FiberTag | null\nfunction registerTypeMatcher(m: TypeMatcher): void\n\n// Extend the accepted $$typeof set for child normalization. Default:\n// REACT_ELEMENT_TYPE, REACT_LEGACY_ELEMENT_TYPE. Portal adds REACT_PORTAL_TYPE.\nfunction registerElementMarker(sym: symbol): void\n```\n\n### Capability hooks\n\nCross-cutting concerns (thrown-thenable handling, context reads) install via `installCapability`:\n\n```ts\nimport { installCapability, type Capabilities } from '@tanstack/redact/_all'\n\ninterface Capabilities {\n  handleSuspended: (fiber: Fiber, thenable: Promise\u003cany\u003e) =\u003e void\n  readContext: (fiber: Fiber, ctx: any) =\u003e any\n}\n\nfunction installCapability\u003cK extends keyof Capabilities\u003e(\n  name: K,\n  fn: Capabilities[K],\n): void\n```\n\nDefaults when no feature installs an override:\n- `handleSuspended`: retry-on-settle (no boundary stack, no fallback)\n- `readContext`: returns `ctx._currentValue` with no provider-tree walk\n\nThe full Suspense feature installs a boundary-stack-based `handleSuspended`. The full Context feature installs a walking `readContext`.\n\n### Authoring a custom feature\n\n```ts\n// my-feature/full.ts\nimport {\n  FiberTag,\n  registerRenderer,\n  registerTypeMatcher,\n  reconcileChildren,\n  childrenToArray,\n  type Fiber,\n} from '@tanstack/redact/_all'\nimport { SOME_SYMBOL } from '@tanstack/redact'\n\nfunction renderMyThing(fiber: Fiber, domParent: Node, anchor: Node | null): void {\n  // your render logic\n}\n\nregisterTypeMatcher((_type, marker) =\u003e\n  marker === SOME_SYMBOL ? FiberTag.SomeTag : null,\n)\nregisterRenderer(FiberTag.SomeTag, renderMyThing)\n```\n\n```ts\n// my-feature/stub.ts\nimport { FiberTag, registerTypeMatcher } from '@tanstack/redact/_all'\nimport { SOME_SYMBOL } from '@tanstack/redact'\n\n// Stub: treat my-thing elements as Fragments (children render normally).\nregisterTypeMatcher((_type, marker) =\u003e\n  marker === SOME_SYMBOL ? FiberTag.Fragment : null,\n)\n```\n\nPair with an `index.ts` (`export * from './full'`) and let your bundler pick which to import.\n\n### Authoring a bundler plugin\n\nThe Vite plugin's core is two `resolveId` cases. Port this pattern to any bundler's resolve hook:\n\n```ts\n// Case 1: short specifier from features/index.ts\n// Matches `./portal`, `./context`, etc.\nif (importer matches /features[/\\\\]index\\.(ts|js)$/) {\n  const name = id.match(/^\\.\\/([a-z-]+)$/)?.[1]\n  if (name \u0026\u0026 flags[name] === false) {\n    return resolveFrom(`./${name}/stub`, importer)\n  }\n}\n\n// Case 2: resolved-path match for hydration\n// (imported from reconcile, root, suspense/full, lazy/full)\nif (flags.hydration === false \u0026\u0026 /\\/hydration$/.test(id)) {\n  const resolved = await resolve(id, importer)\n  if (/features[/\\\\]hydration[/\\\\]index\\.(ts|js)$/.test(resolved)) {\n    return resolved.replace(/index\\.(ts|js)$/, 'stub.$1')\n  }\n}\n```\n\nReal implementation: [packages/redact/src/vite/index.ts](packages/redact/src/vite/index.ts).\n\n### Verifying your setup\n\nWhichever path you choose, check that stubbed features' full code isn't in your output. Use your bundler's analyzer (rollup-plugin-visualizer, Webpack's bundle-analyzer, etc.) and search for `features/\u003cname\u003e/full.js`. With `hydration: false`, you should NOT see `features/hydration/full.js` or its imports (cursor machinery, event-replay, scroll-guard).\n\n---\n\n## Scope\n\n### Supported\n\n- React 19 element model, JSX (classic + automatic), Fragment, Suspense, Portal, Error boundaries, forwardRef, memo, lazy\n- Full hook surface: `useState`, `useReducer`, `useEffect`, `useLayoutEffect`, `useInsertionEffect`, `useMemo`, `useCallback`, `useRef`, `useContext`, `useSyncExternalStore`, `useId`, `useDeferredValue`, `useTransition`, `use` (Context + Promise), `useEffectEvent`\n- Class components with full lifecycle (`componentDidMount`/`componentDidUpdate`/`componentWillUnmount`, `contextType`, `shouldComponentUpdate`, `getDerivedStateFromError`, `componentDidCatch`, legacy lifecycles as no-ops)\n- SSR via `renderToString` / `renderToReadableStream` / `renderToPipeableStream` — including Suspense boundary streaming with `$RC` reveal + event replay\n- Hydration: SSR DOM adoption, deferred hydration for `use(promise)` / lazy, cursor preservation across the synchronous `endHydration`\n- Cohabitation with `@vitejs/plugin-rsc`: the Vite plugin deliberately skips the RSC environment so Flight serialization stays on real `react-server-dom`\n\n### Best-effort / subset behavior\n\n- `useTransition` / `useDeferredValue` run synchronously — no priority scheduling\n- Scheduler shim is a no-op wrapper around microtasks\n- No time slicing, no lane-based work interruption\n\n### Out of scope\n\n- `react-server-dom-*/client` Flight deserializer (TanStack Start uses its own seroval-based codec + `@vitejs/plugin-rsc`)\n- React DevTools protocol\n- Behavioral 1:1 parity with React under concurrent-mode stress\n\nSee [docs/SURFACE.md](./docs/SURFACE.md) for the full React-19 export-by-export audit.\n\n---\n\n## Development\n\n### Layout\n\nOne package, one tree, internal subdirectories per concern:\n\n```\npackages/redact/src/\n  core/                     VDOM types + symbols (FiberTag, Hook, ReactNode, …)\n  react/                    'react' entry: createElement, hooks, context, class,\n                            memo, suspense, jsx-runtime, ReactSharedInternals\n  dom/                      'react-dom' entry: reconciler, host DOM, root,\n                            createPortal, flushSync\n    features/               opt-in features (each is an index/full/stub triple)\n      portal/  context/  suspense/  memo/\n      forward-ref/  lazy/  class/  hydration/\n  server/                   'react-dom/server' entry: renderToString,\n                            renderToReadableStream, renderToPipeableStream\n  scheduler/                'scheduler' shim (no-op microtask wrapper)\n  vite/                     redact() Vite plugin: aliases + feature-flag swaps\ntests/                      vitest suite — 707 tests\nexamples/\n  ssr-demo/                 full SSR + Suspense streaming smoke app\ndocs/\n  SURFACE.md                React 19 export audit\n  SAVINGS_ANALYSIS.md       per-export size savings vs React 19\nscripts/\n  build.mjs                 per-entry esbuild build (every TS module emitted)\n  size.mjs                  per-preset / per-flag gzip report\n  size-check.mjs            CI size-budget assertions\n  size-analyze.mjs          per-module byte breakdown for a given preset\n```\n\nCross-subdir imports inside `packages/redact/src/` use relative paths\n(`../core`, `../react`, etc.). The build emits each TS module as its own\ndist file with all relative imports kept literal — that's what preserves the\nimport-graph boundaries the Vite plugin needs to swap features at consumer\nbuild time.\n\n### Commands\n\n```bash\npnpm install\npnpm build                    # esbuild dist/ + tsc declaration emit\npnpm test                     # vitest suite (707 tests)\npnpm test:types               # tsc --noEmit\npnpm size                     # gzip/brotli per entry + per feature-stub\npnpm size:check               # CI budget assertions (fails on regression)\npnpm --filter ssr-demo dev    # serve http://localhost:5173\n```\n\n### Current sizes\n\nSubpath sizes from `pnpm size`. The `react` / `react-dom/client` / `react-dom/server` column names are the user-facing aliases the Vite plugin sets up; under the hood they all resolve into `@tanstack/redact/*`.\n\n| Entry | min | gzip | brotli |\n|---|---:|---:|---:|\n| `react`              (= `@tanstack/redact`)             | 6.59 KB | 2.65 KB | 2.42 KB |\n| `react/jsx-runtime`  (= `@tanstack/redact/jsx-runtime`) | 247 B | 189 B | 187 B |\n| `react-dom/client`   (= `@tanstack/redact/dom-client`, `full`) | 29.65 KB | **10.03 KB** | 9.11 KB |\n| `react-dom/client`   (= `@tanstack/redact/dom-client`, `nano`) | 20.86 KB | **7.49 KB** | 6.79 KB |\n| `react-dom/server`   (= `@tanstack/redact/server`)      | 12.90 KB | 5.09 KB | 4.61 KB |\n| **Client total** (`full`: react + react-dom/client + jsx-runtime) | 35.81 KB | **12.24 KB** | 11.07 KB |\n\nRegenerate with `pnpm size`.\n\n---\n\n## Changelog\n\nThe project's first 9 alpha versions shipped as separate `@tanstack/react`, `@tanstack/react-dom`, `@tanstack/react-dom-server`, `@tanstack/dom-core`, `@tanstack/scheduler`, and `@tanstack/dom-vite` packages (`0.1.0-alpha.0` … `0.1.0-alpha.9`). Those packages are now deprecated. The project starts fresh as a single `@tanstack/redact` (`0.0.1`+) with subpath exports — the fixes below predate the rename and the package names refer to the previous multi-package layout.\n\n- `@tanstack/redact@0.0.1` — **first release of `@tanstack/redact`**. Consolidates the 6 previously-separate alpha packages into a single package with subpath exports (`./jsx-runtime`, `./dom`, `./dom-client`, `./dom-test-utils`, `./server`, `./scheduler`, `./vite`, `./features/*`, `./_all`). Vite plugin renamed `tanstackDom()` → `redact()`, types `TanStackDom*` → `Redact*`. `ReactSharedInternals` made a `globalThis`-stashed singleton via `Symbol.for` to defend against duplicate package copies under bundlers like Cloudflare's `vite-plugin` that mix `noExternal: true` worker bundling with separate pre-bundled dep copies. New `tests/public-exports.test.ts` snapshot guards every subpath's named-export set against silent link-time drift.\n- `react@0.1.0-alpha.8` — added `useEffectEvent` hook (stable callback over a `useInsertionEffect`-refreshed ref). Fixes missing-export errors in consumers using React 19 event handlers.\n- `react-dom@0.1.0-alpha.8` — **feature-flag system landed**: 8 opt-in features with stub/full pairs, typed Vite plugin config, `pnpm size:check` CI budget enforcement. `nano` preset ships **6.75 KB gzip** — a 26% reduction from `full`.\n- `react-dom@0.1.0-alpha.5` — `useEffect` / `useLayoutEffect` cleanup now runs at effect-run time (in the passive drain) instead of dispatch time. Coalesced renders landing back-to-back before the drain (common with router/store state updates triggered by one user action) no longer leak side-effects into the DOM.\n- `react-dom@0.1.0-alpha.4` — `renderFunction`'s deferred-hydration branch now matches `renderLazy`'s ancestor-Suspense guard (`_awaitingLazyHydration`). Fixes duplicate markup on RSC-hydrated subtrees.\n- `react-dom-server@0.1.0-alpha.4` — shell + bootstrap emits are buffered into one `TextEncoder.encode` + `ReadableStream.enqueue` instead of per-chunk, cutting Node stream overhead in the SSR CPU profile.\n\u003c/content\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftanstack%2Fredact","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftanstack%2Fredact","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftanstack%2Fredact/lists"}