From 345faa6a31e3d09ae57700fbc85e61fb09617769 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Sun, 25 Oct 2026 14:05:30 -0600 Subject: [PATCH 1/2] Measure optional Redact feature ranges for Start and Vite --- launch/benchmarks/redact-frameworks/README.md | 38 +++++-- .../redact-frameworks/browser-checks.json | 22 ++++ .../benchmarks/redact-frameworks/measure.mjs | 29 ++++- .../benchmarks/redact-frameworks/results.json | 100 ++++++++++++++---- launch/benchmarks/redact-frameworks/serve.mjs | 6 +- .../redact-frameworks/server-checks.json | 7 ++ src/data/redact-framework-sizes.json | 14 ++- 7 files changed, 174 insertions(+), 42 deletions(-) diff --git a/launch/benchmarks/redact-frameworks/README.md b/launch/benchmarks/redact-frameworks/README.md index 23591de14..3301b341e 100644 --- a/launch/benchmarks/redact-frameworks/README.md +++ b/launch/benchmarks/redact-frameworks/README.md @@ -2,12 +2,12 @@ Measured October 25, 2026 with the same counter component, controlled input, and CSS in four single-route apps. Each pair changes only the renderer integration. The landing page uses these measurements, with one shared scale for all eight bars. -| 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% | +| Framework | React | Redact default | Unused features disabled | Saved by default | +| -------------- | -------: | -------------: | -----------------------: | ---------------: | +| TanStack Start | 109.3 KB | 62.3 KB | 59.8 KB | 47.0 KB, 43.0% | +| Vite | 68.3 KB | 20.1 KB | 12.6 KB | 48.1 KB, 70.5% | +| Vinext | 138.1 KB | 98.8 KB | Not available | 39.3 KB, 28.5% | +| Next.js | 175.6 KB | 139.9 KB | Not available | 35.7 KB, 20.4% | These are starter app measurements. A larger app has its own routes, dependencies, and chunk layout, so its percentage will differ. The separate [Vinext benchmark](https://github.com/TanStack/redact/blob/main/benchmarks/VINEXT_BENCHMARKS.md) measures a larger workload over six rounds and saves 39.4 KB, or 25.4%. @@ -15,9 +15,29 @@ These are starter app measurements. A larger app has its own routes, dependencie Each file is compressed separately with Node's default `gzipSync`, then the sizes are added. We count all emitted client JavaScript and CSS, including lazy chunks and framework polyfills. KB means 1,000 bytes. These totals are not the download for a single navigation. HTML, inline scripts and Flight payloads, images, source maps, and server output are excluded. -Start and Vite use Redact's default Vite integration. Start sets `ssr.noExternal: true` in both builds. This lets Vite transform React imports inside server dependencies as well as application code. Without it, Node can load an external dependency with native React alongside application code using Redact. Next.js uses `withRedact` with Turbopack. Vinext uses the maintained integration in both modes, with `renderer: 'react'` or `renderer: 'redact'`, so both builds receive the same platform corrections. Next.js and Vinext keep native React on the server. No presets, feature flags, minifier settings, or application code are changed between the two builds of a framework. +Start and Vite use Redact's Vite integration. Start sets `ssr.noExternal: true` in all three builds. This lets Vite transform React imports inside server dependencies as well as application code. Without it, Node can load an external dependency with native React alongside application code using Redact. Vinext uses the maintained integration in both modes, with `renderer: 'react'` or `renderer: 'redact'`, so both builds receive the same platform corrections. Next.js uses `withRedact` with Turbopack. Vinext and Next.js keep native React on the server and use the full Redact runtime, without feature presets. The React and default Redact pairs use default settings. Application code, dependencies, and minifier settings stay the same in every variant. -This is one clean production build per variant, measuring bytes rather than speed. All eight builds were opened in the browser and checked for client startup, a counter update, a controlled input update, and the expected renderer. Start, Vinext, and Next.js were also checked for server-rendered content before JavaScript ran. See [browser checks](./browser-checks.json) and [server checks](./server-checks.json). +This is one clean production build per variant, measuring bytes rather than speed. All ten builds were opened in the browser and checked for client startup, a counter update, a controlled input update, and the expected renderer. All seven Start, Vinext, and Next.js variants were also checked for server-rendered content before JavaScript ran. See [browser checks](./browser-checks.json) and [server checks](./server-checks.json). + +## Optional features + +The lighter green segment shows the bytes removed by disabling features this demo does not use. The lower endpoints are specific to this app. The headline savings compare the default Redact build with React. + +Start disables Activity, Fragment refs, and portals. Hydration, context, Suspense, memoization, forwarded refs, lazy components, and class components, including error boundaries, stay enabled: + +```ts +redact({ + features: { activity: false, fragmentRefs: false, portal: false }, +}) +``` + +The client-rendered Vite counter needs none of the optional features, so its smaller build uses: + +```ts +redact({ preset: 'nano' }) +``` + +Keep the features your application and dependencies use. Both configurations are recorded in `results.json` and reproduced by `measure.mjs`. Bar labels round up to whole KB; bar lengths use exact bytes. ## Versions and source @@ -40,4 +60,4 @@ node /path/to/measure.mjs "$PWD" node /path/to/serve.mjs "$PWD" ``` -`measure.mjs` generates every fixture from source, builds React then Redact for each framework, records `results.json`, and preserves all eight builds. `serve.mjs` opens local production previews on ports 4380 through 4387, in the order Start, Vite, Vinext, and Next.js, React first in each pair. The small Node HTTP bridge for Start and Vite is only a preview harness and is not part of the measured output. +`measure.mjs` generates every fixture from source, builds React and default Redact for each framework, adds the smaller Start and Vite builds, records `results.json`, and preserves all ten builds. `serve.mjs` opens local production previews on ports 4380 through 4389, in the order Start, Vite, Vinext, and Next.js. Each framework serves React, default Redact, then its smaller build when available. The small Node HTTP bridge for Start and Vite is only a preview harness and is not part of the measured output. diff --git a/launch/benchmarks/redact-frameworks/browser-checks.json b/launch/benchmarks/redact-frameworks/browser-checks.json index f20ff99b5..3751d1342 100644 --- a/launch/benchmarks/redact-frameworks/browser-checks.json +++ b/launch/benchmarks/redact-frameworks/browser-checks.json @@ -23,6 +23,17 @@ "renderer identity" ] }, + { + "framework": "Start", + "renderer": "redact-lean", + "passed": true, + "checks": [ + "hydration", + "counter update", + "controlled input", + "renderer identity" + ] + }, { "framework": "Vite", "renderer": "react", @@ -45,6 +56,17 @@ "renderer identity" ] }, + { + "framework": "Vite", + "renderer": "redact-lean", + "passed": true, + "checks": [ + "client mount", + "counter update", + "controlled input", + "renderer identity" + ] + }, { "framework": "Vinext", "renderer": "react", diff --git a/launch/benchmarks/redact-frameworks/measure.mjs b/launch/benchmarks/redact-frameworks/measure.mjs index ec25c426c..3c05b2131 100644 --- a/launch/benchmarks/redact-frameworks/measure.mjs +++ b/launch/benchmarks/redact-frameworks/measure.mjs @@ -9,6 +9,12 @@ import { gzipSync } from 'node:zlib' // Run inside an isolated directory with the pinned dependencies installed. const root = resolve(process.argv[2]) const hash = (bytes) => createHash('sha256').update(bytes).digest('hex') +const leanOptions = { + Start: { + features: { activity: false, fragmentRefs: false, portal: false }, + }, + Vite: { preset: 'nano' }, +} const counter = `'use client' import { useEffect, useState } from 'react' @@ -50,8 +56,9 @@ const report = { redactTarballSha256: hash(await readFile(join(root, 'redact.tgz'))), counterSha256: hash(counter), cssSha256: hash(css), + leanOptions, method: - 'Same single-route counter and controlled input in all four frameworks. Production client builds with default optimization settings. Start bundles its server dependencies through Vite in both variants so React imports inside dependencies are transformed too. Sum of gzipSync bytes for every emitted client .js and .css file, compressed separately. Includes lazy chunks and framework polyfills. Excludes server output, HTML, inline scripts, maps, and images. Each chart compares the same framework and dependencies, changing only its Redact integration. KB means 1,000 bytes. These are starter apps, not a promise for every app or a browser speed measurement.', + 'Same single-route counter and controlled input in all four frameworks. Production client builds with default optimization settings. Start bundles its server dependencies through Vite in every variant so React imports inside dependencies are transformed too. Sum of gzipSync bytes for every emitted client .js and .css file, compressed separately. Includes lazy chunks and framework polyfills. Excludes server output, HTML, inline scripts, maps, and images. Each React/default pair changes only its Redact integration. The extra Start and Vite builds disable unused features with the recorded leanOptions. KB means 1,000 bytes. These are starter apps, not a promise for every app or a browser speed measurement.', results: [], } @@ -129,7 +136,7 @@ import react from '@vitejs/plugin-react' import { redact } from '@tanstack/redact/vite' export default defineConfig({ ssr: { noExternal: true }, - plugins: [tanstackStart(), react(), ...(process.env.RENDERER === 'redact' ? [redact()] : [])], + plugins: [tanstackStart(), react(), ...(process.env.RENDERER !== 'react' ? [redact(process.env.RENDERER === 'redact-lean' ? ${JSON.stringify(leanOptions.Start)} : {})] : [])], }) `, ) @@ -167,7 +174,7 @@ export const Route = createFileRoute('/')({ component: Counter }) `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { redact } from '@tanstack/redact/vite' -export default defineConfig({ plugins: [react(), ...(process.env.RENDERER === 'redact' ? [redact()] : [])] }) +export default defineConfig({ plugins: [react(), ...(process.env.RENDERER !== 'react' ? [redact(process.env.RENDERER === 'redact-lean' ? ${JSON.stringify(leanOptions.Vite)} : {})] : [])] }) `, ) await write( @@ -222,7 +229,11 @@ export default process.env.RENDERER === 'redact' ? withRedact(config) : config } } - for (const renderer of ['react', 'redact']) { + for (const renderer of [ + 'react', + 'redact', + ...(framework in leanOptions ? ['redact-lean'] : []), + ]) { console.log(`Building ${framework} + ${renderer}`) await run( app, @@ -252,13 +263,21 @@ export default process.env.RENDERER === 'redact' ? withRedact(config) : config console.log( `${framework} + ${renderer}: ${result.gzipBytes} gzip bytes across ${emitted.length} files`, ) - // Preserve both complete builds for browser verification after measurement. + // Preserve every complete build for browser verification after measurement. if (framework !== 'Next.js') { const { rename } = await import('node:fs/promises') await rename(join(app, 'dist'), join(app, `dist-${renderer}`)) } } } +for (const framework of Object.keys(leanOptions)) { + const variants = report.results.filter((item) => item.framework === framework) + assert( + variants.find((item) => item.renderer === 'redact-lean').gzipBytes < + variants.find((item) => item.renderer === 'redact').gzipBytes, + `${framework}: feature selection did not reduce output`, + ) +} await writeFile( join(root, 'results.json'), JSON.stringify(report, null, 2) + '\n', diff --git a/launch/benchmarks/redact-frameworks/results.json b/launch/benchmarks/redact-frameworks/results.json index 54ef8001d..90d2642dc 100644 --- a/launch/benchmarks/redact-frameworks/results.json +++ b/launch/benchmarks/redact-frameworks/results.json @@ -1,5 +1,5 @@ { - "recordedAt": "2026-10-25T17:15:08.787Z", + "recordedAt": "2026-10-25T19:55:07.317Z", "node": "v24.22.0", "zlib": "1.3.2.1-motley-285e94b", "packageVersions": { @@ -19,7 +19,19 @@ "redactTarballSha256": "6f8592d92061ab3845b11a5635a2853ec5ba61b600f7ca213cfa42b510ec1099", "counterSha256": "642b7bf7693281301e2d3b42826b679b40f03cd723be41b0aaea9fa190727ca1", "cssSha256": "bed31695998ef8641c353a5f356dbadc243b4669edf6fc631fe7b647c1674643", - "method": "Same single-route counter and controlled input in all four frameworks. Production client builds with default optimization settings. Start bundles its server dependencies through Vite in both variants so React imports inside dependencies are transformed too. Sum of gzipSync bytes for every emitted client .js and .css file, compressed separately. Includes lazy chunks and framework polyfills. Excludes server output, HTML, inline scripts, maps, and images. Each chart compares the same framework and dependencies, changing only its Redact integration. KB means 1,000 bytes. These are starter apps, not a promise for every app or a browser speed measurement.", + "leanOptions": { + "Start": { + "features": { + "activity": false, + "fragmentRefs": false, + "portal": false + } + }, + "Vite": { + "preset": "nano" + } + }, + "method": "Same single-route counter and controlled input in all four frameworks. Production client builds with default optimization settings. Start bundles its server dependencies through Vite in every variant so React imports inside dependencies are transformed too. Sum of gzipSync bytes for every emitted client .js and .css file, compressed separately. Includes lazy chunks and framework polyfills. Excludes server output, HTML, inline scripts, maps, and images. Each React/default pair changes only its Redact integration. The extra Start and Vite builds disable unused features with the recorded leanOptions. KB means 1,000 bytes. These are starter apps, not a promise for every app or a browser speed measurement.", "results": [ { "framework": "Start", @@ -71,6 +83,31 @@ } ] }, + { + "framework": "Start", + "renderer": "redact-lean", + "gzipBytes": 59778, + "files": [ + { + "path": "assets/index-8PKgnleu.js", + "bytes": 167389, + "gzipBytes": 59218, + "sha256": "ee09d42966041a2063e128b854b3485e87389f01db7c510bd62bc547e98eeb01" + }, + { + "path": "assets/index-BrYQuRTz.css", + "bytes": 241, + "gzipBytes": 189, + "sha256": "62ea65329a5844dfb94c95ad3d0bb666da2e998f50a88229d86a72d5b72390d4" + }, + { + "path": "assets/routes-ieaXzjDW.js", + "bytes": 557, + "gzipBytes": 371, + "sha256": "a055170457c5832e0862d760e476f31e23643c8a644f3fe3c5e64019923f4778" + } + ] + }, { "framework": "Vite", "renderer": "react", @@ -109,19 +146,38 @@ } ] }, + { + "framework": "Vite", + "renderer": "redact-lean", + "gzipBytes": 12590, + "files": [ + { + "path": "assets/index-BrYQuRTz.css", + "bytes": 241, + "gzipBytes": 189, + "sha256": "62ea65329a5844dfb94c95ad3d0bb666da2e998f50a88229d86a72d5b72390d4" + }, + { + "path": "assets/index-CmDjlZH7.js", + "bytes": 30414, + "gzipBytes": 12401, + "sha256": "c06a9aab10584753ef3dd73ec969b9cda75ceda320ad4aa7610e0bbec32ae3cd" + } + ] + }, { "framework": "Vinext", "renderer": "react", "gzipBytes": 138094, "files": [ { - "path": "_next/static/95aa7be8-9659-4ff0-9197-de663bea07f7/_buildManifest.js", + "path": "_next/static/bb64c4d8-f7ce-4124-a895-fda8315b989e/_buildManifest.js", "bytes": 176, "gzipBytes": 139, "sha256": "4cce301fe69e28d6463904ecf618427e95091bf3584ea347d8ca55d1077e1fb6" }, { - "path": "_next/static/95aa7be8-9659-4ff0-9197-de663bea07f7/_ssgManifest.js", + "path": "_next/static/bb64c4d8-f7ce-4124-a895-fda8315b989e/_ssgManifest.js", "bytes": 76, "gzipBytes": 60, "sha256": "653f3e53e89b4f8548ff86c19e92bb3c6b84b6be7485a320b1e00893ed877479" @@ -181,6 +237,18 @@ "renderer": "redact", "gzipBytes": 98779, "files": [ + { + "path": "_next/static/11ede8c5-db42-4363-bbd5-eee230bdcf64/_buildManifest.js", + "bytes": 176, + "gzipBytes": 139, + "sha256": "4cce301fe69e28d6463904ecf618427e95091bf3584ea347d8ca55d1077e1fb6" + }, + { + "path": "_next/static/11ede8c5-db42-4363-bbd5-eee230bdcf64/_ssgManifest.js", + "bytes": 76, + "gzipBytes": 60, + "sha256": "653f3e53e89b4f8548ff86c19e92bb3c6b84b6be7485a320b1e00893ed877479" + }, { "path": "_next/static/chunks/BWaJ3C7D.js", "bytes": 911, @@ -222,18 +290,6 @@ "bytes": 241, "gzipBytes": 189, "sha256": "62ea65329a5844dfb94c95ad3d0bb666da2e998f50a88229d86a72d5b72390d4" - }, - { - "path": "_next/static/d4219925-835c-4b0a-8dbd-ce21fce7e7e1/_buildManifest.js", - "bytes": 176, - "gzipBytes": 139, - "sha256": "4cce301fe69e28d6463904ecf618427e95091bf3584ea347d8ca55d1077e1fb6" - }, - { - "path": "_next/static/d4219925-835c-4b0a-8dbd-ce21fce7e7e1/_ssgManifest.js", - "bytes": 76, - "gzipBytes": 60, - "sha256": "653f3e53e89b4f8548ff86c19e92bb3c6b84b6be7485a320b1e00893ed877479" } ] }, @@ -291,19 +347,19 @@ "sha256": "8bbb58cbfd4633c1d828c6bc37bc32abc768a149c01b1dfc4f5131fd81274551" }, { - "path": "NdnK0xH5LMQwOKWJEoBdU/_buildManifest.js", + "path": "EZN1vWdZRBGVR-raIzoF5/_buildManifest.js", "bytes": 219, "gzipBytes": 154, "sha256": "9476e0afd3f9da68a64e6ec58631191fd3eaca62c9f5a377e7517fe9a89e59ea" }, { - "path": "NdnK0xH5LMQwOKWJEoBdU/_clientMiddlewareManifest.js", + "path": "EZN1vWdZRBGVR-raIzoF5/_clientMiddlewareManifest.js", "bytes": 96, "gzipBytes": 66, "sha256": "5483e3295dc35dfe29ea932efc85341c916f10ff78ff5b0e965e003710893a4a" }, { - "path": "NdnK0xH5LMQwOKWJEoBdU/_ssgManifest.js", + "path": "EZN1vWdZRBGVR-raIzoF5/_ssgManifest.js", "bytes": 80, "gzipBytes": 64, "sha256": "678f6ce2cb80b1fe72fc67e7412be6e2ab6ada083111b64f7c40d35e3cba5e00" @@ -364,19 +420,19 @@ "sha256": "8bbb58cbfd4633c1d828c6bc37bc32abc768a149c01b1dfc4f5131fd81274551" }, { - "path": "I5aaZFtg_xGKQHYGMZiI5/_buildManifest.js", + "path": "ihKICl1cD6PpEayME0Kbi/_buildManifest.js", "bytes": 219, "gzipBytes": 154, "sha256": "9476e0afd3f9da68a64e6ec58631191fd3eaca62c9f5a377e7517fe9a89e59ea" }, { - "path": "I5aaZFtg_xGKQHYGMZiI5/_clientMiddlewareManifest.js", + "path": "ihKICl1cD6PpEayME0Kbi/_clientMiddlewareManifest.js", "bytes": 96, "gzipBytes": 66, "sha256": "5483e3295dc35dfe29ea932efc85341c916f10ff78ff5b0e965e003710893a4a" }, { - "path": "I5aaZFtg_xGKQHYGMZiI5/_ssgManifest.js", + "path": "ihKICl1cD6PpEayME0Kbi/_ssgManifest.js", "bytes": 80, "gzipBytes": 64, "sha256": "678f6ce2cb80b1fe72fc67e7412be6e2ab6ada083111b64f7c40d35e3cba5e00" diff --git a/launch/benchmarks/redact-frameworks/serve.mjs b/launch/benchmarks/redact-frameworks/serve.mjs index d7e377e7e..85db53dbe 100644 --- a/launch/benchmarks/redact-frameworks/serve.mjs +++ b/launch/benchmarks/redact-frameworks/serve.mjs @@ -14,7 +14,11 @@ if (!framework) { const children = [] let nextPort = 4380 for (const name of ['start', 'vite', 'vinext', 'next']) { - for (const runtime of ['react', 'redact']) { + for (const runtime of [ + 'react', + 'redact', + ...(['start', 'vite'].includes(name) ? ['redact-lean'] : []), + ]) { const serverPort = nextPort++ children.push( spawn( diff --git a/launch/benchmarks/redact-frameworks/server-checks.json b/launch/benchmarks/redact-frameworks/server-checks.json index 2c8dee4ca..f4503f0f3 100644 --- a/launch/benchmarks/redact-frameworks/server-checks.json +++ b/launch/benchmarks/redact-frameworks/server-checks.json @@ -14,6 +14,13 @@ "passed": true, "checks": ["server-rendered greeting", "counter", "controlled input"] }, + { + "framework": "Start", + "renderer": "redact-lean", + "status": 200, + "passed": true, + "checks": ["server-rendered greeting", "counter", "controlled input"] + }, { "framework": "Vinext", "renderer": "react", diff --git a/src/data/redact-framework-sizes.json b/src/data/redact-framework-sizes.json index 3c61058ed..e35df5ef3 100644 --- a/src/data/redact-framework-sizes.json +++ b/src/data/redact-framework-sizes.json @@ -1,26 +1,30 @@ { - "recordedAt": "2026-10-25T17:15:08.787Z", + "recordedAt": "2026-10-25T19:55:07.317Z", "unit": "gzip bytes", "comparisons": [ { "framework": "Start", "reactBytes": 109332, - "redactBytes": 62306 + "redactBytes": 62306, + "redactLeanBytes": 59778 }, { "framework": "Vite", "reactBytes": 68255, - "redactBytes": 20130 + "redactBytes": 20130, + "redactLeanBytes": 12590 }, { "framework": "Vinext", "reactBytes": 138094, - "redactBytes": 98779 + "redactBytes": 98779, + "redactLeanBytes": null }, { "framework": "Next.js", "reactBytes": 175610, - "redactBytes": 139872 + "redactBytes": 139872, + "redactLeanBytes": null } ] } From b1d48fe021deda142fc30d3e0b13704165ff348f Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Sun, 25 Oct 2026 14:08:41 -0600 Subject: [PATCH 2/2] Lead with Start and show measured Redact feature ranges --- launch/redact-next-vinext.md | 18 ++-- src/blog/redact-nextjs-vinext.md | 36 ++++--- src/components/landing/RedactLanding.tsx | 119 ++++++++++++++--------- 3 files changed, 101 insertions(+), 72 deletions(-) diff --git a/launch/redact-next-vinext.md b/launch/redact-next-vinext.md index d7a113a65..19dece41b 100644 --- a/launch/redact-next-vinext.md +++ b/launch/redact-next-vinext.md @@ -1,8 +1,8 @@ -# Redact Next.js and Vinext launch +# Redact Vinext and Next.js launch Prepared October 25, 2026. The announcement, release summary, and posts below are ready for publication after the package is released and its published integrations are verified. No announcement has been sent or scheduled. -The audience is developers who already use React with Next.js, Vinext, or Vite and want smaller client bundles without rewriting their components. Lead with framework support, then show the measured size difference and explain the scheduling tradeoff. +The audience is developers who already use React with TanStack Start, Vite, Vinext, or Next.js and want smaller client bundles without rewriting their components. Lead with framework support, then show the measured size difference and explain the scheduling tradeoff. ## Release checks @@ -11,8 +11,8 @@ The release candidate now carries maintained framework corrections in Redact. It | Check | Current evidence | Required next step | | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | [Redact release](https://github.com/TanStack/redact/pull/52) | 1,859 runtime tests, types, packaging, and all 38 size gates pass | Require the final PR and publication checks to pass | -| Next.js compatibility | All 162 configurations and 2,694 scenarios pass across six versions | Retain the required complete matrix gate | | Vinext and Cloudflare | All 102 local configurations and 1,410 scenarios pass, plus all 18 hosted configurations and 282 scenarios | Retain the final package and lockfile evidence | +| Next.js compatibility | All 162 configurations and 2,694 scenarios pass across six versions | Retain the required complete matrix gate | | Dependency security | Undici 7.30.1 and fflate 0.7.5 pass their regressions, final consumer audit has zero vulnerabilities | Retain both documented overrides and the final lockfile | | Landing page and announcement | Final benchmark and compatibility evidence incorporated | Publish after the package is available and verify the live pages | @@ -34,24 +34,24 @@ The final preview was reviewed at 390 px and 1280 px with the frozen candidate. ## Announcement -The website draft is [Redact for Next.js and Vinext](../src/blog/redact-nextjs-vinext.md). Its intended URL is `https://tanstack.com/blog/redact-nextjs-vinext`. The draft remains unlisted, but this site's draft URLs can be opened directly, so its visible release notice stays in place until launch. +The website draft is [Redact for Vinext and Next.js](../src/blog/redact-nextjs-vinext.md). Its intended URL is `https://tanstack.com/blog/redact-nextjs-vinext`. The draft remains unlisted, but this site's draft URLs can be opened directly, so its visible release notice stays in place until launch. The existing landing page is the main launch destination: `https://tanstack.com/redact/latest`. Framework setup stays in the existing docs. GitHub release notes link to those same guides and evidence. ## Release summary -Redact adds stable browser integrations for Next.js and Vinext, covering App Router and Pages Router. Keep your React imports and dependencies. Native React continues to handle server rendering and Server Components, while Redact hydrates and renders in the browser. +Redact adds stable browser integrations for Vinext and Next.js, covering App Router and Pages Router. Keep your React imports and dependencies. Native React continues to handle server rendering and Server Components, while Redact hydrates and renders in the browser. In the measured Vinext app, Redact reduced all emitted client JavaScript and CSS from 154.9 KB to 115.5 KB gzip, including lazy routes. Production build times were effectively equal. Browser rendering and hydration speed were not measured. Redact renders synchronously, without React's time slicing or interruptible component renders. -[Next.js setup](https://tanstack.com/redact/latest/docs/next) · [Vinext setup](https://tanstack.com/redact/latest/docs/vinext) · [Compatibility](https://tanstack.com/redact/latest/docs/compatibility) · [Benchmark](https://github.com/TanStack/redact/blob/main/benchmarks/VINEXT_BENCHMARKS.md) +[Start and Vite setup](https://tanstack.com/redact/latest/docs/vite-plugin) · [Vinext setup](https://tanstack.com/redact/latest/docs/vinext) · [Next.js setup](https://tanstack.com/redact/latest/docs/next) · [Compatibility](https://tanstack.com/redact/latest/docs/compatibility) · [Benchmark](https://github.com/TanStack/redact/blob/main/benchmarks/VINEXT_BENCHMARKS.md) ## X thread Post 1: ```text -Redact now supports Next.js and Vinext, including App Router and Pages Router. +Redact now supports Vinext and Next.js, including App Router and Pages Router. Keep your components and React imports, use a smaller browser runtime. React stays on the server. @@ -92,7 +92,7 @@ After release, an optional shared walkthrough with a Vinext contributor could us ## Measure adoption -Use the site's existing page views and campaign parameters. Compare the seven days after launch with the preceding seven days for Redact landing visits and visits to the Next.js and Vinext guides. Track confirmed compatibility reports and their resolution separately. npm downloads provide background context, they do not identify which framework someone used or prove a successful migration. +Use the site's existing page views and campaign parameters. Compare the seven days after launch with the preceding seven days for Redact landing visits and visits to the Vinext and Next.js guides. Track confirmed compatibility reports and their resolution separately. npm downloads provide background context, they do not identify which framework someone used or prove a successful migration. ## Claim checks @@ -102,7 +102,7 @@ Use the site's existing page views and campaign parameters. Compare the seven da | 39.2 KB / 27.2% less entry output | Same report, browser entry plus eager JavaScript imports, not every possible route request | | Starter app bundle charts | [Four-framework measurements](./benchmarks/redact-frameworks/README.md), same counter and input: Start saves 47.0 KB / 43.0%, Vite 48.1 KB / 70.5%, Vinext 39.3 KB / 28.5%, and Next.js 35.7 KB / 20.4%. All emitted client JS and CSS, gzip, one production build per variant. | | Performance | Build times were effectively equal. Local startup was 2.2% faster for App Router and 4.9% faster for the large Pages fixture. Browser and server rendering speed were not measured | -| Framework coverage | [Next.js report](https://github.com/TanStack/redact/blob/main/benchmarks/NEXT_COVERAGE.md) and [Vinext report](https://github.com/TanStack/redact/blob/main/benchmarks/VINEXT_COVERAGE.md), all required cases must pass before the stable announcement | +| Framework coverage | [Vinext report](https://github.com/TanStack/redact/blob/main/benchmarks/VINEXT_COVERAGE.md) and [Next.js report](https://github.com/TanStack/redact/blob/main/benchmarks/NEXT_COVERAGE.md), all required cases must pass before the stable announcement | | Rendering tradeoff | [Compatibility guide](https://tanstack.com/redact/latest/docs/compatibility), synchronous rendering with no time slicing or interruptible component renders | Reply to requests for a universal Next.js percentage with the app-specific distinction above. Reply to scheduling questions with the actual rendering tradeoff. Avoid claims of universal React parity or faster hydration. diff --git a/src/blog/redact-nextjs-vinext.md b/src/blog/redact-nextjs-vinext.md index 0526cb46a..368363009 100644 --- a/src/blog/redact-nextjs-vinext.md +++ b/src/blog/redact-nextjs-vinext.md @@ -1,5 +1,5 @@ --- -title: Redact for Next.js and Vinext +title: Redact for Vinext and Next.js published: 2026-10-25 draft: true excerpt: Keep your React components and imports, use a smaller browser runtime. App Router and Pages Router, with React still running on the server. @@ -10,7 +10,7 @@ library: redact > This release isn't published yet. [Follow the release](https://github.com/TanStack/redact/pull/52). -Redact's Next.js and Vinext integrations let you keep your components, hooks, and React imports while swapping in a smaller browser runtime. That includes App Router and Pages Router, even if your app only uses Pages Router. +Redact's Vinext and Next.js integrations let you keep your components, hooks, and React imports while swapping in a smaller browser runtime. That includes App Router and Pages Router, even if your app only uses Pages Router. React still handles server rendering and Server Components. Your framework still owns routing, caching, and Server Action transport. Redact hydrates the HTML and runs your client components in the browser. @@ -33,18 +33,7 @@ Production build times were effectively equal. Development startup was 2.2% fast Install `@tanstack/redact` and keep your React dependencies. Your component imports stay the same. -For Next.js, wrap your existing configuration: - -```js -// next.config.mjs -import { withRedact } from '@tanstack/redact/next' - -export default withRedact({ - // Your existing Next.js options -}) -``` - -The wrapper configures Turbopack and Webpack. The accepted release lines are Next.js 15.5 and 16.0 through 16.4. Prereleases and untested minor versions are rejected. The [Next.js guide](/redact/latest/docs/next) has the pinned versions and coverage details. +TanStack Start and Vite use the [Vite integration](/redact/latest/docs/vite-plugin). For Vinext, add Redact after `vinext()`: @@ -59,7 +48,22 @@ export default defineConfig({ }) ``` -Keep React, React DOM, and React Server DOM installed. Both framework integrations leave the server on React, so don't change `jsxImportSource` to Redact. See the [Vinext guide](/redact/latest/docs/vinext) for its dependency pins. Existing Vite apps can keep using the [Vite integration](/redact/latest/docs/vite-plugin). +The [Vinext guide](/redact/latest/docs/vinext) lists its dependency pins. + +For Next.js, wrap your existing configuration: + +```js +// next.config.mjs +import { withRedact } from '@tanstack/redact/next' + +export default withRedact({ + // Your existing Next.js options +}) +``` + +The wrapper configures Turbopack and Webpack. The accepted release lines are Next.js 15.5 and 16.0 through 16.4. Prereleases and untested minor versions are rejected. The [Next.js guide](/redact/latest/docs/next) has the pinned versions and coverage details. + +Keep React, React DOM, and React Server DOM installed. Both framework integrations leave the server on React, so don't change `jsxImportSource` to Redact. ## What happens when the app gets busy? @@ -67,7 +71,7 @@ Redact renders synchronously. It doesn't have React's time slicing or interrupti Suspended transitions can keep the current screen visible. Forms can show pending state, optimistic changes can commit or roll back, and streamed content can arrive as it's ready. The [compatibility guide](/redact/latest/docs/compatibility) spells out the differences. -The browser suites exercise hydration and DOM identity, navigation and history, streamed Suspense, Server Actions, optimistic state, Cache Components, errors, and Fast Refresh. They use native React controls and run in Chromium, Firefox, and WebKit. The full reports show every configuration and retain failures: [Next.js](https://github.com/TanStack/redact/blob/main/benchmarks/NEXT_COVERAGE.md), [Vinext](https://github.com/TanStack/redact/blob/main/benchmarks/VINEXT_COVERAGE.md). +The browser suites exercise hydration and DOM identity, navigation and history, streamed Suspense, Server Actions, optimistic state, Cache Components, errors, and Fast Refresh. They use native React controls and run in Chromium, Firefox, and WebKit. The full reports show every configuration and retain failures: [Vinext](https://github.com/TanStack/redact/blob/main/benchmarks/VINEXT_COVERAGE.md), [Next.js](https://github.com/TanStack/redact/blob/main/benchmarks/NEXT_COVERAGE.md). To compare an actual user journey, the repository includes the same Next.js app running with React and Redact side by side. It walks through navigation, an optimistic save, server validation, comments, and returning to the original page. From a repository checkout, run: diff --git a/src/components/landing/RedactLanding.tsx b/src/components/landing/RedactLanding.tsx index cf0709980..f46f306f8 100644 --- a/src/components/landing/RedactLanding.tsx +++ b/src/components/landing/RedactLanding.tsx @@ -19,32 +19,6 @@ const frameworkLogos = new Map([ ['Next.js', '/images/frameworks/next.ico'], ]) const setups = [ - { - name: 'Next.js', - file: 'next.config.mjs', - guide: 'next', - detail: - 'App Router and Pages Router, with Turbopack or Webpack. Keep React installed, it still runs your server.', - code: `import { withRedact } from '@tanstack/redact/next' - -export default withRedact({ - // Your existing Next.js options -})`, - }, - { - name: 'Vinext', - file: 'vite.config.ts', - guide: 'vinext', - detail: - 'Add Redact after Vinext. Keep your Next.js imports, routes, and server React.', - code: `import { defineConfig } from 'vite' -import vinext from 'vinext' -import { redact } from '@tanstack/redact/vinext' - -export default defineConfig({ - plugins: [vinext(), redact()], -})`, - }, { name: 'Start', file: 'vite.config.ts', @@ -73,6 +47,32 @@ import { redact } from '@tanstack/redact/vite' export default defineConfig({ plugins: [react(), redact()], +})`, + }, + { + name: 'Vinext', + file: 'vite.config.ts', + guide: 'vinext', + detail: + 'Add Redact after Vinext. Keep your Next.js imports, routes, and server React.', + code: `import { defineConfig } from 'vite' +import vinext from 'vinext' +import { redact } from '@tanstack/redact/vinext' + +export default defineConfig({ + plugins: [vinext(), redact()], +})`, + }, + { + name: 'Next.js', + file: 'next.config.mjs', + guide: 'next', + detail: + 'App Router and Pages Router, with Turbopack or Webpack. Keep React installed, it still runs your server.', + code: `import { withRedact } from '@tanstack/redact/next' + +export default withRedact({ + // Your existing Next.js options })`, }, ] @@ -120,27 +120,48 @@ export default function RedactLanding() {
{[ - { name: 'React', bytes: item.reactBytes, redact: false }, - { name: 'Redact', bytes: item.redactBytes, redact: true }, + { + name: 'React', + bytes: item.reactBytes, + leanBytes: null, + redact: false, + }, + { + name: 'Redact', + bytes: item.redactBytes, + leanBytes: item.redactLeanBytes, + redact: true, + }, ].map((row) => (
-
+
+ {row.leanBytes && ( + @@ -152,11 +173,11 @@ export default function RedactLanding() { ))}

- Same counter app. All client JS + CSS, gzip.{' '} + Client JS + CSS, gzip. Pale green: optional features.{' '} Source ↗ @@ -202,9 +223,9 @@ export default function RedactLanding() { > {setup.name} setup and coverage ↗ - {(setup.name === 'Next.js' || setup.name === 'Vinext') && ( + {(setup.name === 'Vinext' || setup.name === 'Next.js') && (

- The Next.js and Vinext release isn't published yet.{' '} + The Vinext and Next.js release isn't published yet.{' '}

- In Next.js and Vinext, the framework keeps server rendering, + In Vinext and Next.js, the framework keeps server rendering, Server Components, caching, and action transport. Redact picks up in the browser, hydrates the HTML, and runs your client components. @@ -327,12 +348,12 @@ export default function RedactLanding() {

- Vite apps can go smaller still. + Start and Vite can go smaller still.

- Leave out features your app doesn't use, or start with nano and - add them back. Next.js and Vinext use the full runtime so their - framework features stay available. + Leave out features your app and framework don't use, or start with + nano and add them back. Vinext and Next.js use the full runtime so + their framework features stay available.

- Choose your Vite features ↗ + Choose your features ↗
- {`// A client-rendered app without class components + {`// Start: keep hydration and router features redact({ - features: { hydration: false, classComponents: false }, + features: { + activity: false, + fragmentRefs: false, + portal: false, + }, }) -// All optional features off, then context enabled -redact({ preset: 'nano', features: { context: true } })`} +// Vite: a client-only app without optional features +redact({ preset: 'nano' })`}