Architecture investigation / 08 / October 2026
TanStack.comTwo attempts.
Closed.
Exploring where Nift fits alongside TanStack.
Not replacing the entire ecosystem.
We wanted Nift to own publication and build work while keeping the TanStack libraries and application behavior that make sense to retain. The faithful migration retained too much expensive Start/runtime work. A subsequent rewrite improved separation, but its chosen corpus-generation and dependency architecture failed the performance gate. This was never intended as “replace TanStack with Nift.”
Independent research. No TanStack affiliation or endorsement. No Nift core changes.
Retained
runtime
Start · browser · Worker
Not enough dominant work movedGenerated
corpus graph
Preparation · contexts · dependencies
Chosen representation too expensiveMigration: retained runtime too dominant. Rewrite: the particular generated corpus and dependency graph were too expensive. Neither establishes a general Nift limitation.
boundary
Phase 1 — faithful migration.
The target was the full public application, not a static imitation of its docs. Request-specific sessions, Router/Query behavior, real charts, search, chat/API boundaries and generated products remained part of the contract.
Upstream
Original content inputs
TanStack Start / Vite
React / Redact / Router / Query
Cloudflare application services
A / faithful Nift
Nift publication
Docs + projections + assets
Retained Start / Vite build
Original browser + Worker runtime
Dominant build cost remainsB / alternate engine
Same Nift publication
Official Start / Rsbuild adapter
Original browser + Worker runtime
Lower RSS / slower warm fullB changes the compiler engine. It does not transfer request-time page or docs rendering to Nift.
Nift tanstackAuthored-source / faithful
Maintained Markdown, frontmatter and structured references derive document AST, raw/download views and metadata. Nift composes prepared inputs; Start renders the public application.
Nift tanstack-agentRendered-source / agent-primary
Maintained TanStack document AST envelopes with explicit original/download projections. This is a hybrid runtime-consumed representation, not whole maintained HTML. More restructuring freedom did not relax parity.
React, Router, Query, Table and AI remain product dependencies where used. Start also supplies server functions, serialization and hydration. Vite is build tooling, not a required runtime library. Redact aliases, Tailwind, Content Collections, Sentry runtime and Cloudflare bindings remain explicit integration responsibilities.
paths
Five branches. No forced winner.
- APreserved
Faithful Start / Vite
Accepted behavior. Preserved as the conservative default, but not compelling as a complete application migration.
- BTradeoff
Official Start / Rsbuild adapter
Executable alternate engine. Memory falls; warm full publication slows. Start stays in charge of SSR.
- C1Rejected
Whole-page snapshot + real backend
Required request-specific session behavior is frozen in the published HTML.
- C2Rejected
Maintained article HTML bridge
HTML-only edits appear on the server, then disappear when the original AST-based components mount.
- RClosed
Nift-first rewrite
Separation worked. The chosen generated publication graph failed its viability gate.
Plain esbuild also failed with 204 browser-graph compilation errors: server-only imports and framework transformations cannot simply be skipped. Failed compilation and rejected C branches receive no successful-build timing.
parity
The migration failure was architectural.
Preserving behavior worked. Finding a worthwhile new ownership boundary did not.
The Rsbuild candidate reproduced with independently installed frozen runtime dependencies and a separately locked adapter toolchain. All 244 maintained static assets match byte-for-byte. Required type, lint, unit, chat and desktop suites pass, with one existing desktop skip.
Real local Worker proofs preserve API boundaries, generated output, byte-identical request-time OG PNGs, fresh sessions, Markdown negotiation, mobile navigation, search navigation and the catalog renderer. No handler was replaced with a static success stub.
Browser semantics and interaction checks are not universal pixel identity. API/service proofs use controlled fixtures and unauthenticated boundaries; private live authenticated integrations are not certified. Hosted Algolia remains; there is no omitted local search-index build.
earlier result
Keep the first faithful result visible.
The preserved T10 campaign has 225 observations: 15 cases × three implementations × five samples. It predates the T11 cache work and the T12 architecture comparison below. These cohorts must not be spliced into a single ranking.
| T10 / seconds, median [min–max] | Upstream Start / Vite | Nift authored-source | Nift rendered-source |
|---|---|---|---|
| Warm full | 31.2228.73–36.26 | 32.4232.12–36.31 | 28.9528.72–30.42 |
| Fresh application state | 24.3124.16–24.50 | 33.0932.72–34.87 | 32.4529.14–32.58 |
| Unchanged command | 32.1131.00–33.93 | 7.046.76–7.85 | 6.215.66–6.57 |
| One document | 2.101.98–2.18 | 6.486.11–9.24 | 5.695.43–5.96 |
| Route rename | 8.728.42–9.00 | 4.224.15–4.26 | 4.033.99–4.07 |
| Route delete | 8.608.56–8.90 | 4.234.21–4.57 | 4.434.20–4.64 |
Unchanged and rename/delete publication improved; ordinary upstream document edits remained competitive or faster. Upstream changed-doc timing is its native ready-input R2 refresh, whereas Nift measures complete document/assets publication. This is production work, not development-server/HMR latency. Fresh-state cleanup placement also differs; the original report keeps those boundaries visible.
The T11 replacement 225-row campaign is still pending. Its optimized authored timings are not inferred from the agent-only T12 results.
measurements
Less memory. More time. Still Start.
The bounded T12 investigation compares two agent-source pipelines: faithful Start/Vite and official Start/Rsbuild. It is not a comparison of authored versus rendered source.
Time
Memory
Rsbuild saves memory here, but loses warm-full time. Neither removes the dominant runtime build. This is the observed tradeoff in this application, not a judgement about Rsbuild generally.
| T12 / seconds, median [min–max] | A / faithful Start + Vite | B / Start + Rsbuild |
|---|---|---|
| Warm full | 32.9430.50–36.83 | 41.5540.10–43.20 |
| Fresh application state | 46.3039.92–49.81 | 44.5342.50–57.54 |
| Unchanged command | 2.612.56–3.04 | 2.892.74–3.18 |
| One document | 2.542.53–2.62 | 2.742.67–2.95 |
| Shared shell | 32.1230.35–32.65 | 35.0535.01–37.14 |
| Browser component | 34.6130.52–37.52 | 35.1034.33–35.91 |
| Route rename | 3.593.55–3.73 | 3.273.21–3.62 |
| Route delete | 3.823.26–4.46 | 3.893.54–5.37 |
80 accepted samples: eight workloads × two variants × five serialized repetitions. Clock includes required browser and backend bundling, content, assets and Worker configuration. No separate service build is hidden. Fresh-state ranges overlap substantially; no fresh-build winner is claimed. Installed dependencies and acquisition are excluded; fresh application state does not mean cold OS caches. Fixed cohort order and shared-host load limit small-difference claims.
incremental memory
The boundary is visible in iteration memory.
Document-only edits and lifecycle updates reuse verified runtime bundles. Shared shell and browser-component edits cross the retained compiler boundary. Peak memory rises with that boundary, not with Nift simply rendering one page.
Unchanged command
One document
Shared shell
Browser component
Route rename
Route delete
| T12 / MiB, median [min–max] | A individual process | B individual process | A sampled tree | B sampled tree |
|---|---|---|---|---|
| Warm full | 4,507.64,471.2–4,562.6 | 3,715.73,613.6–3,754.8 | 5,124.05,077.5–5,192.6 | 3,867.23,777.1–3,916.8 |
| Fresh application state | 4,516.34,406.6–4,559.6 | 3,876.83,861.6–3,931.9 | 5,161.75,007.9–5,197.0 | 4,038.94,018.8–4,095.0 |
| Unchanged command | 301.1299.1–302.8 | 321.6316.6–325.6 | 456.2453.4–461.4 | 474.0470.1–478.7 |
| One document | 301.1298.3–308.3 | 319.3317.1–327.0 | 454.4452.5–466.4 | 477.9477.4–484.5 |
| Shared shell | 4,512.14,425.2–4,548.0 | 3,824.73,745.1–3,846.8 | 5,115.55,034.5–5,155.7 | 3,979.33,892.2–4,009.7 |
| Browser component | 4,524.94,450.8–4,612.3 | 3,710.23,626.0–3,780.9 | 5,135.25,063.0–5,190.5 | 3,869.93,788.7–3,936.4 |
| Route rename | 303.7301.4–311.4 | 321.2318.6–327.8 | 458.8455.3–463.7 | 483.0481.3–485.8 |
| Route delete | 302.8299.8–303.9 | 319.1318.4–322.6 | 457.9453.9–458.3 | 478.5475.5–484.8 |
Individual RSS is GNU time maximum process/phase usage, not aggregate memory. Tree RSS is a nominal 50ms PID-deduplicated descendant sum: it can miss short peaks and double-count shared mappings. It is not PSS. Both scopes and observed ranges remain separate; neither is a dedicated-machine memory guarantee.
dominant cost
Nift did not consume 4.4 GiB to render pages.
Prepared docs / projections / composition
Useful publication work. Not the dominant full-build cost.
Start + browser/server emission
Expensive runtime build responsibility remains here.
| Warm full component / median seconds | A / Start + Vite | B / Start + Rsbuild |
|---|---|---|
| Retained engine, both environments | 27.99 | 36.14 |
| Complete content stage | 4.26 | 4.82 |
| Nift composition within content stage | 0.84 | 0.94 |
| Retained asset publication | 0.14 | 0.17 |
| Publisher compilation | 0.35 | 0.40 |
Vite's approximately 4.4 GiB process peak occurs around server emission. V8 live heap was much smaller at the sampled RSS peak, and heap caps did not materially remove that RSS peak. This does not establish a native allocator cause; overlapping diagnostic heap trials are not accepted timing comparisons. Rsbuild's own Node compiler becomes dominant around SSR completion.
This does not show that Nift itself has the same build cost or memory profile as TanStack Start. It shows that the migration retained too much of the existing runtime/build architecture for Nift to meaningfully change the dominant cost.
Nift composition is a nested component, not an additional cost to sum on top of content publication. Median components need not sum to a median total. These are complete orchestrated pipelines, not native @markup performance.
rejected splits
The deeper branches ended at real contracts.
C1 / branch terminated
A live backend cannot unfreeze a static shell.
Original backend forwarding worked and its request seed rotated. The Nift-published whole page retained the old seed. Required partner/session behavior therefore failed. Static output alone was not a faithful substitute.
C2 / branch terminated
Server-visible HTML was not the maintained authority.
Three real pages covered ordinary content, a table and tabs/code. Twelve scoped browser states initially matched. An HTML-only edit published normally and appeared in SSR, then vanished after the original interactive components mounted from their unchanged AST. A shadow AST would require coordinated maintenance; a clean HTML authority needs a different component/island boundary.
We stopped rather than weaken parity, fake dynamic functionality or reimplement substantial parts of Start. These bounded failures do not prove that every possible service/island design is impossible.
what worked
Useful work. An unconvincing migration.
- Verified bundle reuseUnchanged publication avoids redundant application compilation.
- Docs stay outside the heavy buildNormal document edits reuse the unchanged browser/backend bundles.
- Correct publication and retirementCustom publisher, explicit dependencies, rename/delete handling and cache repair preserve outputs.
- Independent recomputationNormal and forced output equality survives a fresh dependency install.
Those gains matter, but they do not make the entire application migration worthwhile. Full/fresh build cost, retained peak memory and compiler integration complexity remain. The source cache improvements do not transfer request-time HTML ownership.
Why publish a negative result?
Labs records what actually happens, including targets where migration is not compelling. Publishing the failed boundary makes the successful experiments more credible and keeps architecture fit separate from benchmark cherry-picking.
rewrite
Phase 2 — Nift-first rewrite. Closed.
Initialized with nift init --rewrite, this attempt moved static HTML and publication onto Nift while retaining useful TanStack components, frontend islands and explicit backend services.
Retained runtime dominates
Generated corpus too costly
Nift-owned HTML remained authoritative and retained runtime pieces continued to work. Exact design and behavior parity was pursued. The resulting implementation still failed its agreed viability gate after one bounded performance pass; the experiment closed without further rescue or parity polishing.
Workload distinction: the rewrite emits 14,247 complete static pages plus 14,107 fragments, while upstream and the faithful migration serve docs dynamically. These differ in responsibility. That difference does not waive the agreed viability gate.
| Rewrite decision gate / seconds | Median | Observed range |
|---|---|---|
| Warm full | 192.28 | 187.08–194.47 |
| Unchanged | 10.36 | 9.92–11.06 |
| Docs edit | 37.50 | 37.03–40.59 |
| Frontend edit | 163.69 | 148.03–181.07 |
Three serialized samples per scenario; diagnostic viability evidence, not a final five-sample acceptance campaign. Accepted upstream/migration measurements were not rerun. Fresh-state, shared-layout and backend-only cases were not rerun after the hard full-build gate failed.
rewrite memory
Two memory scopes. Keep them separate.
These are different metrics and should not be read interchangeably. Individual-process RSS improved substantially from the earlier rewrite, but sampled tree memory remained high. The bounded pass changed warm full from 232.01s to 192.28s, docs edits from 63.40s to 37.50s and frontend edits from 482.51s to 163.69s. Unchanged publication regressed from 5.79s to 10.36s.
GNU time maximum process/phase RSS is not aggregate memory. Sampled descendant RSS can double-count shared mappings and miss short peaks; it is not PSS. Persistent preview/database services are excluded.
rewrite proofs
What worked. What remained too expensive.
- Five thin viewport differencesPreserved as open visual items, not final parity acceptance.
- 14,107 docs outputsFrontend edits rerendered zero docs; docs edits affected six docs. Browser/backend builds stayed off for docs edits.
- Subsystem separationNift-owned HTML, selective retained runtime pieces and frontend/backend boundaries worked.
- 4.72 GB retired cachesMapped repeated uncompressed shell caches were removed after equivalence checks.
| Warm-full phase / median seconds | Cost |
|---|---|
| Frontend producer | 26.91 |
| Corpus preparation | 36.10 |
| Exclusive Nift work | 129.12 |
Exclusive Nift work includes dependency processing, caching, composition, output and orchestration for this generated graph. It is not a measure of ordinary Nift template rendering or built-in Markdown performance. Producer phases overlap; component medians need not sum to a median total.
The rewrite successfully separated Nift-owned publication from retained runtime services, but its generated corpus representation and preparation pipeline were too expensive to justify. Whole-corpus page-context traversal/serialization persisted, and fine-grained shared imports increased dependency/composition work.
The chosen corpus-generation and dependency architecture failed. This does not establish that a simpler Nift template/content architecture would have the same cost.
No general Nift core ceiling was demonstrated. A simpler shared-template approach was not disproved; the experiment stopped because its one authorized rescue pass missed the agreed gate.
lessons
Guidance for the next rewrite, elsewhere.
Preserving too much incumbent architecture can obscure Nift’s benefits; architectural freedom alone does not guarantee a better result. Staticizing a large dynamic-docs surface introduces real responsibility and cost.
- Require corpus-scale generation and usable publication coverage early, rather than isolated parity proofs.
- Run a complete-workload performance viability gate before extended parity refinement.
- Require explicit frontend, backend and content invalidation boundaries.
- Use dependency-aware preparation and a bounded shared-chrome strategy.
- Make
@input,@contentand@pathguidance prominent; use@pathfor internal links, images, video and other local publication references. - Prefer ordinary shared templates and small per-page content/metadata when they meet the contract; retain islands where their functionality earns the complexity.
Bounded shared templates, grouped immutable chrome data, avoiding unchanged context reserialization and profiling large dependency graphs remain separate Nift/tooling research candidates. They do not reopen this experiment.
evidence
Inspect the result. Keep the boundaries.
This page closes both the migration and rewrite as unsuccessful. Rewrite measurements use Nift 4.10.0 and Node 25.9.0; the migration toolchain below belongs to its separate cohort. It uses the accepted T12 investigation and keeps T10 historical evidence separate. The broader T11 replacement 225-row campaign is not complete and is not used to overstate the conclusion. Repository checkpoints were clean and synchronized at publication; frozen links preserve the exact source/evidence state.
Toolchain: Nift 4.9.0, Node 25.9.0 and pnpm 11.1.0. Raw logs, environment/sampler records, restoration receipts and rejected experiments remain in the experiment repositories. Labs stores compact rendering summaries and displayed assets only.
- Rewrite closeout
- Rewrite accepted source checkpoint
- Focused three-sample performance gate
- Rewrite paired browser/pixel evidence
- Complete composed-shell equivalence
- Pinned upstream application
- Nift tanstack / authored-source repository
- Nift tanstack-agent / rendered-source repository
- T12 architectural ownership report
- T12 raw 80-sample dataset
- T12 summaries and ranges
- Component costs
- Independent reproduction receipt
- Whole-page rejection
- HTML authority rejection
- Preserved T10 / 225-row summary
- Original T10 intermediate report
- Heap/cache optimization qualifications
- Prototype code and dependency inventory
conclusion
Neither tested architecture is recommended.
We would not recommend either tested TanStack.com architecture.
Migration
Too much expensive incumbent runtime/build architecture remained; Nift did not take ownership of enough dominant work.
Rewrite
Separation improved, but the particular generated corpus representation, preparation pipeline and dependency graph built for this rewrite remained too expensive.
This establishes neither that Nift is generally unsuitable for TanStack projects nor that ordinary shared templates and content would incur the same cost. It establishes that neither tested implementation was compelling for this application and workload.
Labs publishes unsuccessful experiments as well as successful ones. The purpose is to learn where Nift is useful, not manufacture a win on every target.