Loading Qwik Interactivity Only When Users Click
Qwik resumability loads event-handler code only when users interact, reducing upfront JavaScript. Includes a lazy button example and serialization limits.
03 Aug 2025, 03:03 UTC

The problem: shipping interactivity nobody uses
A typical server-rendered page still ships JavaScript for every interactive component, even if most visitors never click them. That unused code delays time to interactive and wastes bandwidth on slow connections.
Qwik takes a different approach called resumability. Instead of hydrating the whole component tree on the client, Qwik serializes component state into the HTML and pauses. When a user clicks a button or types in a field, the browser downloads only the code for that specific event handler and resumes the component from the serialized state.
The practical takeaway: mark lazy boundaries with a dollar sign suffix, such as onClick$, and let Qwik's optimizer split the handler into its own chunk. You get interactivity on demand rather than a large upfront JavaScript bundle.
Worked example: a lazy Notify me button
Create a new Qwik project in your terminal. No root or administrator permissions are required.
npm create qwik@latest <project-name>Run this in an empty directory. The command downloads the official starter, so network access and registry credentials may be needed if you are behind a private proxy.
Open src/components/notify-button.tsx and add a component whose click handler lives in a lazy-loaded chunk:
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<button onClick$={() => {
console.log('Notifying subscriber...');
}}>
Notify me
</button>
);
});The onClick$ suffix tells Qwik's optimizer that this handler is a lazy-loadable boundary. The function body should not be included in the initial page JavaScript; only a small reference is serialized into the HTML.
Start the dev server from the project directory:
cd <project-name> && npm run devOpen the browser's developer tools, go to the Network tab, and reload the page. You should see the initial HTML without a separate chunk for the handler. Click the button and watch for a new JavaScript request containing the handler code. If no chunk appears after the click, the optimizer may not have split the boundary, or the installed Qwik version may handle the suffix differently.
Risk: on a slow connection, the user feels a delay between clicking and the handler running because the chunk must download first. Resumability moves work from load time to interaction time; it does not remove network latency.
State must survive serialization
Resumability relies on serializing component state into HTML. Use useSignal for fine-grained reactivity so Qwik can track updates without re-rendering unrelated components.
import { component$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const count = useSignal(0);
return (
<div>
<span>{count.value}</span>
<button onClick$={() => { count.value += 1; }}>Increment</button>
</div>
);
});A plain number serializes safely. Class instances, closures, and DOM references inside signal state may not resume correctly because they cannot be represented in HTML. If you need complex state, keep it serializable or handle rehydration manually.
Trade-offs and version checks
- Lazy boundaries add round-trips. Each interaction may trigger a chunk download. On high-latency networks, that can hurt perceived responsiveness more than a larger initial bundle.
- Serialization limits. Non-serializable state breaks resume or requires custom serializers.
- Qwik City is separate. If you use Qwik City for file-system routing or server endpoints, it has its own release cycle and breaking changes.
Before adopting this pattern in production, check package.json for @builder.io/qwik and @builder.io/qwik-city versions, then compare them against the changelog for the installed release. Qwik's public API and optimizer behavior can change between major versions.
How to verify the result
- Build or preview the production build, not just the dev server, because dev mode may bundle differently.
- Open the Network tab, filter by JavaScript, and interact with the component. Confirm the handler chunk loads only after the event.
- Change a
useSignalvalue, refresh the page, and confirm the value resumes from the server-rendered HTML without a custom serializer.
If the chunk never loads, inspect the optimizer output and the Qwik version in your lockfile. The dollar-sign convention is established, but exact chunk names and splitting behavior depend on the release.
Closing
Use Qwik resumability when your page has interactive elements that only a fraction of visitors will touch. Mark those handlers with $, keep signal state serializable, and verify chunk loading in the Network tab before shipping. The trade-off is interaction-time network latency, so test on slower connections to decide if the trade-off fits your users.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.