…t entry)
The client entry of examples/hackernews as its vite build ships it, through
the harness: the generated entry (installServerComponents + hydrate of the
plugin's document shell and default error boundary) around the example's
own src/, compiled hydratable by the measured checkout's compiler, with the
router resolved the way the example's build resolves it (the solid export
condition: dist/index.jsx + per-module output), so the router's server-form
fallback — and action behind it — is the lazy chunk it is in a Vite build.
Harness: bundle.mjs compiles .tsx like .jsx (Rolldown strips the types the
JSX compiler leaves), runs the native "use server" directive pass in
client mode under a scenario's compile.serverFunctions root, loads .css as
empty, and attributes examples/ modules as app. No other scenario's number
moved (every recorded minified matches to the byte).
Measured locally: 176,581 B minified / 54,557 B brotli; cap at measured +
10 B rounded up to 0.01 KB, to be confirmed against CI.
Co-authored-by: Cursor <cursoragent@cursor.com>
One size scenario: the client entry of
examples/hackernews— the server-components HackerNews — as itsvite buildships it, bundled through the harness. Scenario, fixture and harness tooling only — nothing underpackages/*/src, the example is not changed, no changeset. Re-based ontonext@ d231b99 (2026-10-07): #3838 (size/page-router-scenario, the@solidjs/routerwiring this stacked on) and #3860 (the frames tiers) are merged; the one commit is the scenario, with its cap re-measured on that base.Summary
The page scenarios are compositions the harness wrote for itself. This one is an application someone wrote to be deployed: three routes under
@solidjs/router, four server components, one client component, hydrating. It measures what the stack costs a real read-only server-component app, and attributes it by package. Measured locally onnext@ d231b99 at 145,471 B minified / 47,849 B brotli; the router is 23,376 of the minified, the app's own code 2,491. (The landing measurement, on49a8dca84+ #3838, was 176,581 / 54,557; the −31,110 / −6,708 between them isnext's — #3860's frames tiers moved the store engine the container-trace materializer builds on into the lazytrace.js— not this scenario's.)While wiring it: the router the two existing router scenarios measure is not the artifact a Vite app ships — see the router's two artifacts below. This PR leaves those scenarios alone and flags it.
What the scenario bundles
fixtures/hackernews/entry.jsxis@solidjs/vite-plugin@3.0.0-next.35's generated client entry for the example's config (solid({ start: {}, ssr: true, serverFunctions: { components: true } }), defaulterrorBoundary) written out:installServerComponents(), thenhydrate()of the plugin's document shell (document.jsx) and default error boundary (error-boundary.jsx) around the app. Those two files are the plugin's other generated modules, verbatim. The app is the example's ownsrc/, unchanged, reached through its~alias:createRouterwith threedefineRoutes (six feed paths,/stories/:id,/users/:id), each with apreload; the instance as the root with a render-prop layout; two<Loading>boundaries.dynamic()— the nav directly, the three routes through the router'squery. The example's"use server"views compile, client side, tocreateServerReferenceproxies;lib/api.tsis the example's whole server-function surface.Toggle: a signal, a delegated click,class/styleeffects, two templates.action, no element spread.The router is this directory's pinned
2.0.0-next.35(not thenext.29the example'spackage.jsoninstalls — the harness's router is the one whose upgrade is a recorded re-base), resolved the way the example's build resolves it: thesolidexport condition@solidjs/vite-pluginputs first, i.e.dist/index.jsxplus the router's per-module output, its JSX compiled by the measured checkout's compiler like the app's own.tsx. Itsdata/eventsimport("./serverForms.js")is a real lazy chunk here, as in a Vite build.Compiled
hydratable: true,dev: false,generate: "dom". Router modules report asother, the example's asapp.Numbers
Local, macOS, Node 26. Head = this branch's fresh build (
pnpm install --frozen-lockfile, compiler rebuilt,turbo --force), one commit onnext@ d231b99. This branch carries no runtime change — the packages it measures arenext's — so head andnextare the same bytes for every existing scenario (each matchesnext's measurement in a second worktree on the same commit to the byte, e.g.page: compiled base SC109,461 / 35,146 on both), andpage: hackernewsonnextIS this number; there is no delta to attribute.next@ d231b99)trace.js8,194 br (the store engine + the container-trace materializer, the frames traces tier) · codecdecode.js6,237 · routerserverForms.js2,388 (data/serverForms,data/action, signals'action) ·bind.js1,831 ·server.js1,023 (behind a foldedisServer) ·wire.js932 ·regions.js791 ·assets.js78349a8dca84+ #3838)serverForms.js2,360 ·server.js1,023next's: #3860's tiers (the engine and the materializer totrace.js), #3838's re-base, and the fixes sinceThe eager graph is two files on this page — the entry 60,843 min / 20,438 br and the eager
client.js84,628 / 27,411 Rolldown hoists for the modules the lazy chunks share with the entry — measured and counted asbundle.mjscounts them (each brotli'd on its own). The module-by-module attribution of the signals bytes, and what retains each, is indocumentation/plans/hackernews-signals-attribution.md(onaudit/hn-signals-attribution; measured on this scenario).For scale against the hand-written compositions on this base:
page: base + router144,238 / 45,996,page: base105,413 / 33,910. HN is a real app and lands below the hand-written router fixture — the fixture keepsShow/For/useNavigatealive and measures the flat router bundle (below).Attribution by package (head, minified)
signalscore/core.js8,581, scheduler 5,793,core/lanes.js5,343, async 4,060,store/utils.js3,425 (the merge/omit view readers, retained bydynamic()'s element arm —spread), boundaries 3,184,core/verdict.js2,682, owner 1,543, effect 1,140, heap 996, graph 861,store/types.js789, …store/store.js/ reconcile / projection 0 (the lazytrace.js),core/action.js0 (lazy)web/framesclient.js@solidjs/router(other)routing.js6,616 ·data/query.js3,260 ·utils.js2,767 ·routers/factory.jsx2,584 ·data/events.js1,997 ·routers/components.jsx1,327 ·claims.js1,296 ·routers/history.js1,045 ·routers/scrollRestoration.js835 ·paths.js457 ·serverRouteShared.js432webweb.js—dynamic(string-tag branch + the spread runtime it retains; the example has not moved todynamicComponent, see below), hydration, templatessolidsolid.js17,246 (hydration, flow components) +internal.js63web/server-functionsclient.js—createServerReference, the transport,GET/decode;subscribeFlightData/decodeResponsePayload/parseServerFunctionActionUrlare retained in the eager chunk for the lazyserverForms.jschunk, which imports them from itappapp.tsx522,toggle.tsx440,routes/stories.tsx224,story.tsx,lib/views.tsx— fourcreateServerReferences —user.tsx,lib/api.ts) plus the generated entry 205, document 232, error boundary 349The lanes (
lanes.js+verdict.js, 8,025 apportioned — 6,683 exact by source map inhackernews-signals-attribution.md, on the same runtime one signals fix earlier) are on this page because the router's navigation core readsisPending(source)/latest(source)(isRouting, intent, pending target increateRouterContext;queryreads them of the location) andonSettledincreateIntegration— not because ofaction, which is lazy here. The base page without a router has none of them.Caps — CI must confirm
Inline
limit: "47.86 KB"/capMinified: 145471— local measured (47,849 / 145,471) + 10 B at the 0.01 KB step, re-set from the re-base (the landing cap was 54.57 KB / 176,581 on the pre-tiers base). Confirm against CI's measurement (Linux, Node 24) + 10 B — never higher; if CI measures lower, lower them in this PR before merging. Now that the PR targetsnext, the Size workflow runs on it. Nofloor-caps.jsonentry.check-floor-caps origin/next: no cap raised.npm testinscripts/sizepasses;node size.mjs— the new scenario within its cap; the existing scenarios read exactly whatnext@ d231b99 reads on this machine (the known local brotli-layout lines over by 16–36 B at +15 B minified against their recorded size arenext's own since the signals fixes after the caps were seeded, andgate.mjspasses them within the minified allowance).The router's two artifacts (flagged for #3838)
@solidjs/router'sexports["."]is{ solid: "./dist/index.jsx", default: "./dist/index.js" }. Thesolidcondition isindex.jsx+ the per-module tsc output, which@solidjs/vite-pluginresolves (it putssolidfirst inresolve.conditions);dist/index.jsis the flat Rollup bundle for consumers without a Solid compiler, built withinlineDynamicImports: true, which turnsdata/events.ts's lazyimport("./serverForms.js")— the server-form fallback — intoPromise.resolve().then(() => serverForms): a static edge fromsetupNativeEventstosubmitServerForm → createServerFormAction → actionImpl → action$1 (signals),installRouterIntegrations, the flight consumer and the sf transport's flight exports.page: base + routerandpage: live + routerresolve the bare specifier with Rolldown's default conditions, so they measure the flat bundle.Same page, same build, both artifacts (measured at landing; the gap is the router's and does not move with
next):dist/index.jssolidcondition49a8dca84+ #3838)So the router's marginal cost on the base page that #3838's body records (+38,779 min / +12,137 br) is +30,446 / +9,547 for a Vite app. solidjs/solid-router#657 (open) splits the flat build so both paths get the same graph; when a router with that lands, the flat numbers move down to about the
solid-condition ones and the two router scenarios re-base. This PR does not change them (their caps are being confirmed in #3838); the HN scenario measures thesolidcondition from the start, which is what the example ships. The measurement behind this is on solid-router#655 — comment linked below.Router without
action, measuredPosted as a comment on solid-router#655. Headline, on this head: removing
action's one static edge from the flat bundle saves −7,523 min / −2,265 br on HN and −11,727 / −3,613 onpage: base + router; on thesolidconditionactionis already lazy, and dropping the fallback entirely (the issue's import-driven shape) is a further −2,196 / −764 on HN. The signals lanes stay in every variant (the navigation core'sisPending/latest/onSettled);createEffectleaves only with scroll restoration (−1,141 min / −344…−379 br); the form path's own wiring is 142 min.Harness accommodations (bundle.mjs)
Needed to bundle the example without changing it:
.tsxcompiles like.jsx. The native compiler leaves type annotations in place, so the compiled module is handed to Rolldown asmoduleType: "ts"and Rolldown strips them (as Vite's own TS strip does after the Solid plugin). Plain.ts(lib/api.ts) Rolldown already handled.compile.serverFunctions: <root>runs the compiler's"use server"directive pass in client mode on the modules under that root that contain the directive, with the Vite plugin's defaults (@solidjs/web/server-functionsruntime — the page alias routes it to the client dist; IDs hash the root-relative path, so output is host-independent;env: "production"). Replaces each exported view with acreateServerReferenceproxy and drops the server-only body and its./hnimport — exactly what the plugin does before the JSX compile. Modules without the directive are left to Rolldown..cssloads as an empty module (app.css; Vite extracts it to a stylesheet).examples/modules report asapp(packageOf/moduleOf), withexamplesRootbesidefixturesRoot. The example comes from the measured harness's checkout even underSIZE_PACKAGES_ROOT, like the fixtures.~→examples/hackernews/src;@solidjs/router→node_modules/@solidjs/router/dist/index.jsx(thesolidcondition, and the harness's pin rather than the example's).No example change. The example still mounts with
dynamic();dynamicComponent(#3870, merged with #3860) is onnextnow, so the example CAN move to it — that move is the example's own change (the demos pass), not this scenario's, and this scenario will show the saving when it lands: on HN,dynamic()'s element arm is what retains the spread runtime and the merge/omit view readers (store/utils.js3,425 +store/types.js789 + web'sspread/assign);hackernews-signals-attribution.mdmeasured the switch at −7,987 min / −2,339 br on this page.Verification
Fresh build of this head on
next@ d231b99 (pnpm install --frozen-lockfile, compiler rebuilt,turbo --force,scripts/sizenpm ci), cross-checked against a second fresh build of the same commit;npm testinscripts/size;node size.mjs;check-floor-caps.mjs origin/next; Prettier clean.Public API changes
None.