From 4ca10ceb54108631247f98c7a91ad93ba58905b7 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Wed, 21 Oct 2026 23:50:36 -0700 Subject: [PATCH] Add standalone Redact landing page and documentation --- src/routeTree.gen.ts | 21 ++ src/routes/redact.tsx | 470 ++++++++++++++++++++++++++++++++++++++++++ src/utils/sitemap.ts | 1 + 3 files changed, 492 insertions(+) create mode 100644 src/routes/redact.tsx diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index c86675182..b7342ba5e 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -37,6 +37,7 @@ import { Route as PaidSupportRouteImport } from './routes/paid-support' import { Route as PartnersRouteImport } from './routes/partners' import { Route as PartnersEmbedRouteImport } from './routes/partners-embed' import { Route as PrivacyRouteImport } from './routes/privacy' +import { Route as RedactRouteImport } from './routes/redact' import { Route as RobotsDottxtRouteImport } from './routes/robots[.]txt' import { Route as RssDotxmlRouteImport } from './routes/rss[.]xml' import { Route as ShopRouteImport } from './routes/shop' @@ -464,6 +465,11 @@ const PrivacyRoute = PrivacyRouteImport.update({ path: '/privacy', getParentRoute: () => rootRouteImport, } as any) +const RedactRoute = RedactRouteImport.update({ + id: '/redact', + path: '/redact', + getParentRoute: () => rootRouteImport, +} as any) const RobotsDottxtRoute = RobotsDottxtRouteImport.update({ id: '/robots.txt', path: '/robots.txt', @@ -2033,6 +2039,7 @@ export interface FileRoutesByFullPath { '/partners': typeof PartnersRouteWithChildren '/partners-embed': typeof PartnersEmbedRoute '/privacy': typeof PrivacyRoute + '/redact': typeof RedactRoute '/robots.txt': typeof RobotsDottxtRoute '/rss.xml': typeof RssDotxmlRoute '/shop': typeof ShopRouteWithChildren @@ -2342,6 +2349,7 @@ export interface FileRoutesByTo { '/paid-support': typeof PaidSupportRoute '/partners-embed': typeof PartnersEmbedRoute '/privacy': typeof PrivacyRoute + '/redact': typeof RedactRoute '/robots.txt': typeof RobotsDottxtRoute '/rss.xml': typeof RssDotxmlRoute '/showcases': typeof ShowcasesRoute @@ -2654,6 +2662,7 @@ export interface FileRoutesById { '/partners': typeof PartnersRouteWithChildren '/partners-embed': typeof PartnersEmbedRoute '/privacy': typeof PrivacyRoute + '/redact': typeof RedactRoute '/robots.txt': typeof RobotsDottxtRoute '/rss.xml': typeof RssDotxmlRoute '/shop': typeof ShopRouteWithChildren @@ -2972,6 +2981,7 @@ export interface FileRouteTypes { | '/partners' | '/partners-embed' | '/privacy' + | '/redact' | '/robots.txt' | '/rss.xml' | '/shop' @@ -3281,6 +3291,7 @@ export interface FileRouteTypes { | '/paid-support' | '/partners-embed' | '/privacy' + | '/redact' | '/robots.txt' | '/rss.xml' | '/showcases' @@ -3592,6 +3603,7 @@ export interface FileRouteTypes { | '/partners' | '/partners-embed' | '/privacy' + | '/redact' | '/robots.txt' | '/rss.xml' | '/shop' @@ -3910,6 +3922,7 @@ export interface RootRouteChildren { PartnersRoute: typeof PartnersRouteWithChildren PartnersEmbedRoute: typeof PartnersEmbedRoute PrivacyRoute: typeof PrivacyRoute + RedactRoute: typeof RedactRoute RobotsDottxtRoute: typeof RobotsDottxtRoute RssDotxmlRoute: typeof RssDotxmlRoute ShopRoute: typeof ShopRouteWithChildren @@ -4230,6 +4243,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PrivacyRouteImport parentRoute: typeof rootRouteImport } + '/redact': { + id: '/redact' + path: '/redact' + fullPath: '/redact' + preLoaderRoute: typeof RedactRouteImport + parentRoute: typeof rootRouteImport + } '/robots.txt': { id: '/robots.txt' path: '/robots.txt' @@ -7104,6 +7124,7 @@ const rootRouteChildren: RootRouteChildren = { PartnersRoute: PartnersRouteWithChildren, PartnersEmbedRoute: PartnersEmbedRoute, PrivacyRoute: PrivacyRoute, + RedactRoute: RedactRoute, RobotsDottxtRoute: RobotsDottxtRoute, RssDotxmlRoute: RssDotxmlRoute, ShopRoute: ShopRouteWithChildren, diff --git a/src/routes/redact.tsx b/src/routes/redact.tsx new file mode 100644 index 000000000..9f4330702 --- /dev/null +++ b/src/routes/redact.tsx @@ -0,0 +1,470 @@ +import { createFileRoute } from '@tanstack/react-router' +import { CodeBlock } from '~/components/markdown/CodeBlock' +import { seo } from '~/utils/seo' + +const repo = 'https://github.com/TanStack/redact' +const description = + 'A smaller React-compatible runtime with synchronous rendering. Install TanStack Redact, keep your React imports, and choose the features your app needs.' + +export const Route = createFileRoute('/redact')({ + head: () => ({ + meta: seo({ title: 'TanStack Redact | React, redacted.', description }), + scripts: [ + { + type: 'application/ld+json', + children: JSON.stringify({ + '@context': 'https://schema.org', + '@type': 'SoftwareSourceCode', + name: 'TanStack Redact', + description, + url: 'https://tanstack.com/redact', + codeRepository: repo, + programmingLanguage: 'TypeScript', + runtimePlatform: 'JavaScript', + }), + }, + ], + }), + component: RedactPage, +}) + +const sections = [ + ['install', 'Install'], + ['size', 'Bundle size'], + ['compatibility', 'Compatibility'], + ['features', 'Feature flags'], + ['reference', 'Plugin reference'], + ['verification', 'Before you ship'], +] + +const compatibility = [ + [ + 'Components and hooks', + 'JSX, state, effects, context, refs, memo, lazy, portals, error boundaries, and modern class lifecycles are supported. Legacy class lifecycles are no-ops.', + ], + [ + 'Suspense and hydration', + 'Fallbacks, retries, retained primary DOM, streaming boundaries, and event replay are supported. Work runs without priority scheduling.', + ], + [ + 'Transitions', + 'startTransition and useTransition run synchronously. Pending stays false. useDeferredValue returns its input. There is no time slicing or interruptible rendering.', + ], + [ + 'Actions and optimistic state', + 'useActionState 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.', + ], + [ + 'Activity', + 'Hidden DOM and state are retained. Effects, refs, and subscriptions disconnect, insertion effects stay connected. Hidden work runs synchronously.', + ], + [ + 'Server rendering', + 'String, static markup, readable stream, and pipeable stream rendering are supported. Full Fizz prerender and resume APIs are not implemented.', + ], + [ + 'Server Components', + 'The Vite RSC environment stays on upstream React. Redact does not reimplement Flight serialization.', + ], + [ + 'Caching', + 'cache returns its function and cacheSignal returns null. Client and DOM-server rendering are uncached.', + ], + [ + 'Development tools', + 'Fast Refresh works with @vitejs/plugin-react. StrictMode and Profiler render children without double invocation or profiling. React DevTools inspection is not implemented.', + ], + [ + 'React Compiler', + 'The react/compiler-runtime memo-cache entrypoint is implemented and aliased. Every compiler output pattern has not been validated.', + ], +] + +const flags = [ + ['activity', 'Hidden children unmount and lose state.'], + ['fragmentRefs', 'No Fragment DOM instance.'], + [ + 'viewTransitions', + 'No native snapshots or animation callbacks, the boundary retains state.', + ], + ['portal', 'Children render in place instead of into the target container.'], + ['context', 'Providers do not propagate values, reads return the default.'], + [ + 'suspense', + 'No fallback UI, thrown thenables still retry when they settle.', + ], + ['memo', 'No prop-equality bailout.'], + ['forwardRef', 'No wrapper forwarding, ordinary ref props still work.'], + ['lazy', 'Only already-synchronous payloads resolve.'], + [ + 'classComponents', + 'Constructor, render, and setState only, no lifecycles or error boundaries.', + ], + ['hydration', 'hydrateRoot throws, use createRoot.'], +] + +function RedactPage() { + return ( +
+
+
+
+

+ 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 + +

+
+ + {`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.

+ + 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. +

+ + {`// Your application code stays familiar. +import { useState } from 'react' +import { createRoot } from 'react-dom/client' + +function Counter() { + const [count, setCount] = useState(0) + return ( + + ) +} + +createRoot(document.getElementById('root')!).render()`} + +

+ 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. +

+ + + + + + + + + {compatibility.map(([area, behavior]) => ( + + + + + ))} + +
AreaRedact behavior
{area}{behavior}
+

+ 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. +

+ + {`// 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. +

+ + + + + + + + + {flags.map(([flag, behavior]) => ( + + + + + ))} + +
Feature flagWhen disabled
+ {flag} + {behavior}
+
+
+

Vite plugin reference

+ + {`import { redact } from '@tanstack/redact/vite' + +redact({ + preset: 'full', + features: { viewTransitions: false }, +})`} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionPurpose
+ preset + + 'full' | 'nano', defaults to{' '} + 'full'. +
+ features + + Boolean overrides for the feature flags above. +
+ skip + + An array of import specifiers to leave out of aliasing. +
+ resolveFrom + + Package resolution root, defaults to the Vite config root. + Useful in monorepos. +
+ packageRoots + + A 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. +

+
+
+
+
+
+ ) +} diff --git a/src/utils/sitemap.ts b/src/utils/sitemap.ts index f818b1740..94679295f 100644 --- a/src/utils/sitemap.ts +++ b/src/utils/sitemap.ts @@ -16,6 +16,7 @@ const HIGH_VALUE_NON_DOC_PAGES = [ '/', '/blog', '/libraries', + '/redact', '/learn', '/showcase', '/support',