È uscito il Corso Java Completo
Torna al blog

Cos'è Qwik e cosa significa resumability

Cos'è Qwik e come funziona la resumability: perché riprende il lavoro del server invece di rieseguirlo, cosa significa il simbolo $ e quando ha senso.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

9 min di lettura

Hai fatto tutto bene: rendering lato server, HTML che arriva in 200 millisecondi, la pagina si vede subito. Poi provi a cliccare un pulsante e non succede niente per due secondi. Quei due secondi hanno un nome — idratazione — e sono il problema che Qwik prova a eliminare. In questo articolo trovi cos'è la resumability, perché è diversa dall'idratazione, cosa significa quel $ sparso nel codice e se conviene davvero.

Cos'è

Qwik è un framework che serializza dentro l'HTML tutto lo stato e tutti i collegamenti agli eventi calcolati dal server, così il browser riprende esattamente da dove il server ha finito invece di rieseguire l'applicazione da capo per "riattaccare" l'interattività.

Quel "riprende invece di rieseguire" è la resumability, e tutto il resto discende da lì.

Il problema: cosa succede davvero con l'idratazione

Per capire la soluzione bisogna vedere bene il problema, perché è un costo che quasi tutti pagano senza saperlo.

Quando usi Next.js, SvelteKit, Nuxt o qualunque cosa faccia rendering lato server, la sequenza è questa:

  1. Il server esegue i componenti e produce HTML.
  2. Il browser riceve l'HTML e lo mostra. La pagina sembra pronta.
  3. Il browser scarica il bundle JavaScript dell'applicazione.
  4. Il browser riesegue tutti i componenti per ricostruire in memoria l'albero dei componenti, ricalcolare lo stato e capire quale funzione va attaccata a quale pulsante.
  5. Solo adesso la pagina è interattiva davvero.

Il passaggio 4 è l'idratazione, e il punto scomodo è che è lavoro duplicato: il server aveva già calcolato tutto quello, e il browser lo rifà da zero solo per scoprire dove mettere gli ascoltatori degli eventi.

Il costo cresce con la dimensione dell'applicazione. Non con quello che l'utente vede: con quanto codice c'è. Una pagina con dieci componenti interattivi paga poco; la stessa pagina dentro un e-commerce con duecento componenti paga molto, anche se sullo schermo c'è la stessa identica cosa.

E in mezzo c'è una finestra spiacevole — la pagina si vede ma non risponde — in cui chi clicca ha la sensazione che il sito sia rotto. È ciò che misura l'Interaction to Next Paint tra i Core Web Vitals.

La resumability: riprendere invece di rieseguire

Qwik parte da una domanda diversa: se il server ha già fatto quel lavoro, perché buttarlo via?

La risposta è: scriverlo nell'HTML. Qwik serializza nell'HTML stesso lo stato dell'applicazione, il grafo delle dipendenze reattive e i riferimenti alle funzioni da eseguire per ogni evento.

Il risultato assomiglia a questo:

<button on:click="./chunk-a7f.js#handleClick[0]">
  Cliccato 3 volte
</button>

<script type="qwik/json">
  {"objs":[3],"subs":[...]}
</script>

Guarda quel on:click: non è JavaScript, è un puntatore. Dice quale file scaricare, quale funzione chiamarci dentro e quale pezzo di stato passarle. Finché nessuno preme quel pulsante, quel file non viene scaricato e quella funzione non esiste nel browser.

All'avvio Qwik carica un solo pezzo di codice, di circa un kilobyte: un ascoltatore globale che intercetta gli eventi, legge l'attributo, scarica il pezzo giusto e lo esegue. Nient'altro.

Ed ecco la conseguenza che è tutto il punto: il costo di avvio non dipende da quanto è grande l'applicazione. Dieci componenti o duemila, il browser esegue quel kilobyte e basta. Non c'è un albero da ricostruire, perché lo stato è già scritto nell'HTML e viene letto solo quando serve. Non è che l'idratazione è più veloce: è che non avviene.

Idratazione (React, Svelte, Vue)Resumability (Qwik)
Cosa arriva al browserHTML + bundle dell'appHTML con stato serializzato
All'avvio il browserRiesegue tutti i componentiEsegue ~1 KB di ascoltatore
Costo di avvioCresce con la dimensione dell'appCostante
Quando arriva il codiceTutto prima dell'interattivitàAlla prima interazione, a pezzi
Cosa scarica chi non cliccaTuttoQuasi nulla

Il caricamento progressivo del codice

La seconda metà dell'idea è che il codice arrivi in pezzi molto piccoli e solo al momento del bisogno.

Il code splitting esiste da sempre, ma lo decidi tu, a mano, e in pratica si applica alle rotte o a qualche componente pesante. In Qwik il compilatore spezza automaticamente il codice a un livello molto più fine: ogni gestore di evento, ogni effetto, ogni corpo di componente può diventare un file a sé.

Su una pagina di prodotto, chi si limita a leggere le caratteristiche non scarica il codice del selettore di quantità, né quello della galleria, né quello del modulo di recensione.

Per evitare che l'attesa si veda, Qwik precarica in modo speculativo i pezzi che l'utente probabilmente userà, in background e a bassa priorità, dopo che la pagina è già utilizzabile. È una differenza importante rispetto all'idratazione: lì il codice serve prima che l'utente possa fare qualsiasi cosa, qui arriva mentre l'utente sta già leggendo.

Il simbolo $: dove il codice si spezza

Se guardi codice Qwik, la prima cosa che noti sono i dollari.

import { component$, useSignal, $ } from "@builder.io/qwik";

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

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

Il $ non è sintassi decorativa: è un'istruzione per il compilatore. Dice "questo pezzo di codice può essere estratto in un file separato e caricato solo quando serve".

Quel onClick$ diventa, in fase di build, un file a sé contenente solo quella funzione, e nell'HTML resta il puntatore che hai visto prima. Lo stesso vale per component$, useTask$, server$ e per il $() generico che usi quando vuoi rendere spezzabile una funzione tua.

Il prezzo è un vincolo reale, ed è il punto in cui si perde più tempo: una funzione estratta in un altro file non ha più accesso alla chiusura lessicale nel modo normale. Qwik riesce a ricostruire le variabili catturate solo se sono serializzabili.

// Funziona: numeri, stringhe, oggetti semplici, signal
const nome = useSignal("Edoardo");
<button onClick$={() => console.log(nome.value)}>Ciao</button>

// Non funziona: una classe non serializzabile catturata nella chiusura
const socket = new WebSocket("wss://...");
<button onClick$={() => socket.send("ping")}>Invia</button>

Il secondo caso dà un errore di serializzazione, ed è il messaggio con cui fa i conti chiunque inizi con Qwik. La soluzione è tenere il non serializzabile dentro un useVisibleTask$ o crearlo dentro il gestore stesso, ma il fatto che il problema esista è il costo del modello.

Per inciso: lo stato si legge con .value, per lo stesso motivo dei signal in SolidJS — serve un oggetto tracciabile, non un valore congelato.

Il compilatore, e perché non basta dire "è come Svelte"

Anche Qwik è in buona parte un compilatore, e chi ha letto cos'è Svelte riconosce l'idea di spostare lavoro dalla fase di esecuzione a quella di build. Ma i due risolvono problemi diversi.

Svelte riduce la quantità di codice che il browser deve eseguire non spedendo un motore di reattività. Il browser però esegue comunque il codice dei componenti all'avvio, e quindi paga comunque un'idratazione — più leggera, ma proporzionale all'applicazione.

Qwik elimina l'esecuzione all'avvio, punto. Non riduce il costo dell'idratazione: lo azzera, perché non idrata.

Errori comuni che fanno perdere ore

Trattare useVisibleTask$ come un useEffect qualunque. È l'unico punto in cui Qwik esegue codice senza che l'utente abbia interagito, quindi ogni volta che lo usi stai reintroducendo un pezzo di idratazione. Se il progetto ne è pieno, hai preso il costo di Qwik senza il vantaggio. Va usato solo per quello che richiede davvero il DOM del browser.

Catturare nelle chiusure roba non serializzabile. Classi, funzioni non marcate con $, istanze di librerie esterne, riferimenti al DOM. Il messaggio d'errore c'è ma non sempre indica il punto giusto.

Integrare librerie React o Vue senza adattamento. Sono scritte presupponendo che tutto giri nel browser all'avvio. Qwik ha un ponte per React (qwik-react), ma ogni componente importato da lì si idrata normalmente: lo usi per due isole specifiche, non come strategia.

Misurare in sviluppo. In dev Qwik non spezza il codice allo stesso modo e i numeri non dicono nulla: il vantaggio si vede solo nella build di produzione.

Conviene davvero?

La parte onesta, e stavolta pesa parecchio.

Qwik è la scommessa più radicale e meno adottata. Non è una variazione su un tema noto: è un modello di esecuzione diverso, che richiede di ripensare cose date per scontate.

L'ecosistema è molto piccolo, più piccolo di quello di Solid. Le offerte di lavoro in Italia sono sostanzialmente assenti, e i progetti in produzione da cui imparare sono pochi.

Il vincolo della serializzazione è reale. Condiziona come scrivi il codice e come integri le librerie che già conosci.

E soprattutto: il vantaggio si vede su applicazioni grandi. Se la tua pagina ha tre pulsanti e un modulo, l'idratazione costa poco e stai risolvendo un problema che non hai.

Su un sito medio, Astro risolve lo stesso problema in modo molto più semplice: spedisce HTML senza JavaScript e idrata solo le singole isole interattive che indichi tu. Non elimina l'idratazione, ma la riduce a quel poco che serve davvero — e in cambio ti fa usare React, Vue o Svelte come già li conosci, con tutto il loro ecosistema. Per la scelta tra i due mondi vedi Next.js o Astro.

Quando Qwik ha senso davvero: e-commerce grandi con molto codice interattivo dove ogni decimo di secondo si traduce in conversioni, applicazioni pubbliche molto estese servite a un pubblico su dispositivi modesti, e progetti in cui il tempo fino all'interattività è un requisito scritto, non un desiderio.

In sintesi

La resumability è il contrario dell'idratazione: invece di rieseguire l'applicazione nel browser per riattaccare gli eventi, Qwik serializza stato e puntatori nell'HTML e riprende da dove il server ha lasciato.

Il costo di avvio diventa costante, circa un kilobyte, indipendentemente da quanto è grande l'applicazione. È il vantaggio centrale e non è marginale.

Il codice arriva a pezzi minuscoli, al momento dell'interazione, con un precaricamento speculativo in sottofondo.

Il $ marca i punti in cui il compilatore può spezzare il codice, e in cambio impone che le variabili catturate siano serializzabili: è il vincolo con cui si combatte davvero.

È la scommessa più radicale e meno adottata, con un ecosistema molto piccolo e un vantaggio che si vede sulle app grandi. Su un sito medio, Astro risolve lo stesso problema con molte meno complicazioni.

Per il confronto con gli altri due modelli alternativi, vedi cos'è SolidJS e cos'è Svelte. Per capire dove si colloca nel quadro delle strategie di rendering, SSG, SSR e ISR.