They overlap in vocabulary, but they are different machines.
Qwik Components Qwik components are resumable component units.
A Qwik component can have:
component$()
useSignal()
useStore()
useTask$()
useVisibleTask$()
onClick$ / QRL handlers
routeLoader$ data
serialized/resumed closure state
lazy-loaded code chunks
render outputThe important part is that Qwik’s component is part of the reactive execution graph. It can be rendered on the server, paused, serialized into HTML metadata, and later resumed in the browser without full hydration.
So Qwik lifecycle/handlers are deeply tied to:
QRL lazy loading
optimizer/compiler
serialized closures
task tracking
component render schedulingAsync Framework Components Our “components” should stay as scoped fragment factories.
They do not rerender. They do not hydrate. They do not become a persistent render graph.
const ProductCard = defineComponent(function ProductCard(props) {
const selected = this.signal(false);
this.handler("select", function () {
selected.set(true);
});
this.on("attach", function () {
// DOM exists
});
return html`
<article signal:class="${["card", { selected }]}">
<h2 signal:text="${props.title}"></h2>
<button on:click="${this.handler(function () {
selected.set(true);
})}">
Select
</button>
</article>
`;
});A component invocation creates:
a scope id
scoped signals
scoped handlers
scoped effects
scoped lifecycle callbacks
static HTML
bindings that point at signals
cleanup recordsAfter that, updates happen through signals and bindings, not by rerunning the component function.
Async Components We should be careful with this phrase.
I would not make “async components” mean:
const Product = async function () {
return html`...`;
}as the main model. That starts pulling us toward render scheduling and suspense ownership.
Instead:
components are sync fragment factories
async work belongs in asyncSignal, server(), partials, or route loadersGood:
const Product = defineComponent(function Product({ productId }) {
const product = this.asyncSignal("product", async function () {
return this.server.products.get(productId.value);
});
return html`
<article async:boundary="${product.id}">
${this.suspense(product, {
loading: () => html`<p>Loading...</p>`,
ready: () => html`<h1 signal:text="${product.id}.title"></h1>`,
error: () => html`<p signal:text="${product.id}.$error.message"></p>`
})}
</article>
`;
});Avoid as the core model:
const Product = defineComponent(async function () {
const product = await this.server.products.get(...);
return html`...`;
});That can exist later as server-only partial sugar, but not Layer 1 component behavior.
Handlers Qwik handlers:
<button onClick$={async () => { ... }} />are compiled into QRL lazy references. The browser can load the handler later on demand.
Our handlers:
<button on:click="cart.add">resolve through a registry:
handler: {
"cart.add"() {
this.signals.update("cartCount", n => n + 1);
}
}Inline component handlers:
<button on:click="${this.handler(function () {
selected.set(true);
})}">should compile/runtime-register to a scoped handler id:
component.Product.1.handler.2Then cleanup unregisters it when the component scope is destroyed.
So our handler model is:
registry id -> function
scoped id -> function
command chain -> sequential awaited commands
server command -> proxy/registry callNo QRL required in Layer 1.
Lifecycle Qwik:
useTask$ -> before render / tracked reactive task, server + browser rules
useVisibleTask$ -> client visible/mounted eager task
cleanup -> when task reruns or component unmountsOur lifecycle should be smaller:
this.on("attach", fn);
this.on("visible", fn);
this.effect(fn);Meaning:
attach -> after DOM inserted, scanned, and initial bindings flushed
visible -> after root element becomes visible
effect -> signal-driven side effect, scoped cleanupAlso HTML pseudo-events:
<div on:attach="measure">
<div on:visible="analytics.view">But docs should prefer component lifecycle on the root element / component context.
Main Difference Qwik lifecycle can participate in render/resume semantics.
Our lifecycle is DOM activation lifecycle.
Qwik: component rendered/resumed, task graph runs
Async: fragment inserted/scanned, bindings/lifecycle runWhat We Should Spec I’d lock this in:
1. Components are synchronous scoped fragment factories.
2. Async data inside components uses asyncSignal + suspense templates.
3. Server-rendered async HTML uses partials/routes, not client component rerenders.
4. Handlers are registry/scoped ids, not serialized closures.
5. Lifecycle is attach/visible/effect, scoped and scheduler-phased.
6. Component cleanup cancels scoped scheduler jobs, unregisters scoped handlers/signals, and releases DOM bindings.Short Comparison
| Concept | Qwik | Async Framework |
|---|---|---|
| Component | resumable reactive unit | scoped fragment factory |
| Async data | routeLoader/useAsync/useAsyncComputed signals | asyncSignal/resource signal |
| Handler | QRL lazy function | registry/scoped function id |
| Lifecycle | task/visible task in render/resume model | attach/visible/effect in DOM activation model |
| Update | component/render graph | signal bindings |
| Cleanup | component/task cleanup | scope cleanup |
| Build | optimizer central | optional higher layers later |
So yes, we have components, handlers, and lifecycle too. But ours should remain more like:
HTML fragment + scoped registry + signal bindingswhile Qwik is:
compiled resumable component/task graph