- Shipped
- 19 Agosti 2026, 20:37 UTC
- Author
- Kamo
- Commit
- 7ba3b84
Step B. TrustBar, WhatReplacesWhat, HowItWorks, UseCases, Features, CTAStrip and Pricing are server components now. They render to HTML and ship no JavaScript. The only thing that had been keeping them on the client was analytics. Not one of the seven had state, a ref or an effect — their entire interactivity was `onClick={() => trackEvent(...)}` on a link, and that single prop was dragging each section's whole tree into hydration. It is a `data-track` attribute now, read by one delegated listener mounted once for the page. Deliberately NOT `data-analytics-event`, and that is the reason TrackedClicks exists rather than the tracker's own attribute: analytics installs a capture-phase listener and, for any anchor carrying it, calls preventDefault(), awaits its beacon, then sets location.href — turning a client navigation into "wait for analytics, then hard reload", measured on this site at 1.5-8s of dead time per click, and banned outright by **************** The listener here never touches the event: no preventDefault, no stopPropagation, no waiting. All five event names survive, verified against the pre-conversion sources — cta-start-free, cta-view-pricing, cta-contact-sales, nav-click, home-feature-learn-more. The rendered counts are higher than the call-site counts because the call sites are inside `.map()`: twelve feature cards, six plans. All 22 attributes land on the <a> or <button> itself and every data-track-props value parses as JSON. HomeClient is no longer a client component, which is the change that made the rest possible — a client component cannot import a server one, so the directive at the top of that one file had been deciding the rendering strategy for the entire page. Its `dynamic()` wrappers and Suspense boundaries went with it: they were code-splitting components that all hydrated anyway, and reserving `min-h-[600px]` for content the server had already produced. The two easter eggs still need `ssr: false`, so they sit behind their own small client boundary. Eight components remain on the client, and the list is now an honest one: SharedNavigation, JumpToBar, ScrollProgress and Hero for scroll and playback state; ShippedStrip because it fetches; FAQ for its accordion; SignupSection for its form; Footer for back-to-top. THE TRADE, because this one is not free. A server component's rendered tree is serialised into the RSC flight payload as well as the HTML, so the document grew while the JavaScript shrank: JS chunks 261,999 -> 243,579 gzip (-18,420, and 23 chunks -> 15) document 39,895 -> 53,877 gzip (+13,982) net -4,438 Bytes are close to a wash. The point is the other axis: seven fewer component trees to hydrate, against script evaluation measured at 922 ms and an LCP "element render delay" of 1,810 ms. That is the cost this is aimed at, and it is not visible in a byte count. It is also why this lands as its own commit. If a run says otherwise, reverting this one restores the client composition without touching Step A's work.