Found gating server memory in stacksjs/harness (harness#11 has the full history). Rendering the same ~1,700-line view against identical props, per render:
bun:jsc heapStats shows only ~1.1MB of JS-heap allocation and nothing retained after GC — the projection is fine.
- Process RSS climbs 30→209MB over the first ten renders and plateaus around 267MB, never returning to the OS — allocator high-water, not a leak. Forced
Bun.gc(true) after every render caps almost nothing (253→224MB over 60 renders), so the churn is native-side (transpiler/bundler/regex/WTF-string territory), invisible to the JS heap.
- A template containing one component tag (
<Sidebar> from @stacksjs/components) produces a 211KB page — 192KB of it inline script — and burns ~9MB/render. A trivial <div>{{ x }}</div> still churns ~1.4MB/render.
Where it goes: the client-script bundler's disk cache hits every render (verified with STX_DEBUG), but everything downstream of it re-runs per render on unchanged inputs:
- the ~190KB cached bundle is re-read from disk, re-wrapped and re-inlined,
- the DOM-API validator re-scans the full client script (its warnings print once per render),
- scope ids (
stx_sidebar_<n>_<rand>) regenerate each render, so the output is never byte-identical and can't be memoized by callers from the outside,
- the island wrapper is rebuilt.
The compiled island artifact is a function of (template content, config) only — the scope id is already threaded in as an IIFE argument (})(\"stx_sidebar_4_15gdi7\")), so the whole post-bundler artifact looks memoizable keyed by content hash, with the scope id (and data-stx-props) interpolated at render time. That would turn SSR of an unchanged template into string interpolation and cut per-render allocation by roughly an order of magnitude.
Numbers from bun 1.3.14, stx 0.2.188, macOS arm64. Reproduction is just renderView of any island-bearing view in a loop, watching ps -o rss= against heapStats().heapSize.
Downstream we now cache rendered pages keyed by a state revision (stacksjs/harness@75482a4), which covers polling — but every genuine state change still pays the full churn, and any hot stx SSR surface without an equivalent cache pays it per request.
🤖 Generated with Claude Code
Found gating server memory in stacksjs/harness (harness#11 has the full history). Rendering the same ~1,700-line view against identical props, per render:
bun:jscheapStats shows only ~1.1MB of JS-heap allocation and nothing retained after GC — the projection is fine.Bun.gc(true)after every render caps almost nothing (253→224MB over 60 renders), so the churn is native-side (transpiler/bundler/regex/WTF-string territory), invisible to the JS heap.<Sidebar>from @stacksjs/components) produces a 211KB page — 192KB of it inline script — and burns ~9MB/render. A trivial<div>{{ x }}</div>still churns ~1.4MB/render.Where it goes: the client-script bundler's disk cache hits every render (verified with STX_DEBUG), but everything downstream of it re-runs per render on unchanged inputs:
stx_sidebar_<n>_<rand>) regenerate each render, so the output is never byte-identical and can't be memoized by callers from the outside,The compiled island artifact is a function of (template content, config) only — the scope id is already threaded in as an IIFE argument (
})(\"stx_sidebar_4_15gdi7\")), so the whole post-bundler artifact looks memoizable keyed by content hash, with the scope id (anddata-stx-props) interpolated at render time. That would turn SSR of an unchanged template into string interpolation and cut per-render allocation by roughly an order of magnitude.Numbers from bun 1.3.14, stx 0.2.188, macOS arm64. Reproduction is just
renderViewof any island-bearing view in a loop, watchingps -o rss=againstheapStats().heapSize.Downstream we now cache rendered pages keyed by a state revision (stacksjs/harness@75482a4), which covers polling — but every genuine state change still pays the full churn, and any hot stx SSR surface without an equivalent cache pays it per request.
🤖 Generated with Claude Code