Resumability & QRLs: Making Qwik City Instant‑Loading by Deferring Code Until Interaction
Qwik’s resumability and QRL lazy loading let you build instant‑loading web apps. This blog walks through how QRLs defer code until interaction, shows a concrete Qwik City example, discusses trade‑offs, and gives actionable steps to adopt the pattern.
02 Sept 2026, 06:37 UTC

Problem: The Need for Instant‑Load, Low‑JavaScript Apps
Modern users expect a page to be interactive within the first second of a network request. Traditional frameworks hydrate a large JavaScript bundle after the initial HTML arrives, delaying interactivity and increasing the load time. Qwik addresses this by changing the way the browser starts running code.
Thesis: Resumability + QRL Lazy Loading = Instant‑Loading
Qwik’s core design, called resumability, serializes component state on the server so the client can “resume” without a hydration pass. Coupled with QRLs (Qwik Resource Locators), which defer code until the user triggers an event, the framework delivers a truly instant‑loading experience with minimal JavaScript executed on first paint.
1. Resumability Fundamentals
When a Qwik City route renders, the server emits:
- Static HTML markup
- Serialized component state in a
qwik-statedata attribute - QRL references that point to the JavaScript functions needed to resume interactivity
Because the state is already present, the client can immediately attach event listeners and restore interactive components without walking the DOM to build a virtual tree. This eliminates the costly hydration step seen in React or Vue.
2. QRL Lazy Loading Explained
A QRL is a string that uniquely identifies a function or component. When the browser encounters a QRL, it does not immediately fetch the associated code. Instead, the code is loaded only when the event that references the QRL fires. This is similar to dynamic imports but scoped to user interactions.
Example QRL syntax in a component:
import { component$, useSignal, $ } from "@builder.io/qwik";
export const LazyButton = component$(() => {
const clicked = useSignal(false);
return (
<button onClick$={$(() => clicked.value = true)}>
{clicked.value ? 'Clicked' : 'Click me'}
</button>
);
});
The onClick$ attribute automatically generates a QRL that the runtime will resolve when the button is pressed.
3. Practical Example – Lazy Component in Qwik City
Below is a minimal Qwik City route that lazily loads a component when a button is clicked. The example demonstrates how QRLs are serialized in the HTML and how the code is fetched on demand.
Route File: src/routes/lazy-route.tsx
import { component$, lazy, type QwikCityRequestHandler } from "@builder.io/qwik-city";
// Lazily import the heavy component
const HeavyComponent = lazy(() => import("../components/heavy-component"));
export const onGet: QwikCityRequestHandler = async () => {
return (
<div>
<h1>Lazy Component Demo</h1>
<button onClick$={async () => {
// The click handler is a QRL that will load HeavyComponent when fired
await HeavyComponent();
}}>
Load Heavy Component
</button>
</div>
);
};
Heavy Component: src/components/heavy-component.tsx
import { component$ } from "@builder.io/qwik";
export const HeavyComponent = component$(() => {
return <div>This is a heavy component that was not loaded until click.</div>;
});
When you navigate to /lazy-route, the server emits HTML that contains a QRL reference for the onClick$ handler. The initial JavaScript bundle is tiny because HeavyComponent is not included.
To verify the lazy behavior:
- Open the page in Chrome and inspect the source. You should see a
data-qrlattribute pointing to a QRL string. - Open DevTools → Network tab. You should not see a request for
heavy-component.jsuntil you click the button. - After the click, a new request for
heavy-component.jsappears, and the component renders.
4. Trade‑offs & Limitations
- HTML Size: Serialized state and QRLs increase the first‑byte payload. On low‑bandwidth connections, this can negate the benefit of small JavaScript.
- Build Complexity: The build pipeline must preserve QRL references and generate separate chunks. Misconfiguring imports can cause eager loading, defeating the optimization.
- Pure Static Builds: Resumability requires server rendering. If you export the site as a static bundle without a server, the serialized state is omitted and interactivity is lost.
- Signal Usage: While signals provide fine‑grained reactivity, they are only effective when paired with resumability. Using plain React‑style state in a Qwik component will not provide the same benefits.
Actionable Closing
To adopt QRL lazy loading in an existing Qwik City project:
- Wrap heavy components with
lazy()and reference them only in event handlers. - Run
npm run buildand inspect the output folder. Verify that each lazy component has its own chunk and that the main bundle remains small. - Use Chrome DevTools to monitor Network requests on interaction. The request for the lazy chunk should fire only after the user triggers the event.
- Measure
First Contentful PaintandTime to Interactivebefore and after adding QRLs to confirm the performance gain. - Keep an eye on HTML size. If it grows beyond your target, consider splitting the state further or moving non‑critical data to client‑side fetches.
By leveraging resumability and QRL lazy loading, you can build Qwik City applications that feel instant while keeping JavaScript payloads to a minimum. The trade‑offs are manageable with careful build configuration and monitoring.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.