Acumatica · Qwik

Qwik Resumability — A Field Guide

What Qwik's resumability model actually replaces hydration with, how the $ optimizer makes it work, and when the win is real versus when it's not worth the ecosystem trade-off.

John Kihiu12 min read

Every mainstream frontend framework — React, Vue, Svelte, even Solid — ships JavaScript to the browser, then re-executes enough of the component tree to attach event listeners and rebuild internal state. That step is hydration, and its cost scales with how much of the page is interactive, not with how big the page looks. Qwik's pitch is that this step is unnecessary: if you serialize the right things into the HTML at build/render time, the browser can "resume" exactly where the server left off, attaching one listener at a time, only when a user actually interacts with something.

What hydration actually costs

Server-side rendering solves the blank-screen problem — the user sees painted content immediately — but the page isn't interactive until hydration finishes. Hydration means downloading the JS for every component that could be interactive, executing all of it, rebuilding the virtual DOM tree in memory, diffing it against the real DOM, and attaching event listeners. Frameworks have chased partial fixes for years — code splitting, islands architecture, progressive hydration, `` boundaries — because the fundamental problem doesn't go away: the client still has to re-run component logic it already ran once on the server, just to know where to put event listeners.

Resumability: serialize the "where," not just the "what"

Qwik's approach is to serialize the application's state and a listener map directly into the HTML as it renders on the server — which component needs which event listener on which element, and what closures those listeners need. The client doesn't re-execute the component tree at all on load. It downloads essentially no JS up front beyond a small runtime (the "qwikloader"), and when a user clicks a button, that runtime looks up which lazy-loaded chunk contains the click handler, fetches just that chunk, and runs it — with the serialized state already available, no re-render needed to reconstruct it. The framework "resumes" execution at the point of interaction instead of replaying everything that came before it.

Resumability moves cost from "load" to "interaction"

Hydration front-loads a fixed cost on every page load, whether or not the user ever clicks anything. Resumability defers almost all JS execution until the specific moment a specific interaction happens, and only for the code that interaction needs — which is why Time-to-Interactive stays flat as the page gets bigger, instead of growing with component count.

The `$` symbol and the optimizer

Qwik marks lazy-loadable boundaries with a trailing $component$(), onClick$(), useTask$(). This isn't decoration; the Qwik Optimizer (a build-time compiler step) uses that convention to find closures it can safely extract into their own separately-loadable modules. Each `$`-marked function gets pulled out, given a unique import path, and referenced from the serialized HTML by that path rather than being inlined into one big bundle. That's what makes fine-grained lazy loading possible — the framework isn't guessing which code might be needed later; the build step has already split it into fetchable pieces and recorded exactly where each piece is invoked.

TSX · Qwik component
import { component$, useSignal } from '@builder.io/qwik';

export const Counter = component$(() => {
  const count = useSignal(0);

  return (
    <button onClick$={() => count.value++}>
      Clicked {count.value} times
    </button>
  );
});

useSignal creates reactive state that Qwik can serialize into the DOM as an attribute, and onClick$ marks the handler as its own lazy-loadable chunk. On first render, the server outputs the button with the count already painted and a reference to where the click handler lives — no client-side JS has run yet. The first click downloads and executes just that handler's chunk.

The honest tradeoffs

None of this is free. The mental model is a real shift — you have to think about your app in terms of serializable state and lazy boundaries, which is a different discipline than "just write a component and don't think about bundling." Debugging is less mature: fewer battle-tested devtools, a smaller StackOverflow corpus, and an ecosystem of component libraries that's a fraction of React's size. Qwik City (its meta-framework, roughly Next.js's equivalent) is capable but younger, with fewer examples of large-scale production deployments to learn from.

The resumability win isn't universal

For a small dashboard behind a login where users are already engaged and JS payload isn't the bottleneck, hydration cost is rarely what's slowing you down — pick whatever framework your team knows. Resumability's advantage shows up specifically on large, content-heavy, mostly-server-rendered pages with lots of potential interactivity and a cold, unauthenticated visitor — marketing sites, content platforms, e-commerce catalogs — where Time-to-Interactive under hydration would otherwise scale with page complexity.

Wrapping up

Resumability isn't a faster version of hydration — it's an argument that hydration's basic premise, replaying the component tree to reattach behavior, is the wrong default. Whether that argument matters for your project depends entirely on what "slow" means for your users: if it's initial load on a content-heavy page, Qwik's model directly targets that cost. If your bottleneck is somewhere else entirely, the ecosystem and tooling maturity of React or Vue will usually get you to production faster than the theoretical performance win justifies.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.