Uno sguardo pratico all'approccio compilatore per le performance web, confrontando framework pesanti in runtime con output a build-time e proponendo un semplice quadro decisionale.

Gli utenti raramente descrivono la performance in termini tecnici. Dicono che l'app sembra pesante. Le pagine impiegano un attimo in più a mostrare qualcosa, i pulsanti rispondono in ritardo e azioni semplici come aprire un menu, digitare in una ricerca o cambiare scheda si inceppano.
I sintomi sono familiari: un primo caricamento lento (UI vuota o a metà), interazioni scattose (click che arrivano dopo una pausa, scorrimento a scatti) e spinner lunghi dopo azioni che dovrebbero essere istantanee, come salvare un form o filtrare una lista.
Gran parte di questo è costo a runtime. In termini semplici, è il lavoro che il browser deve fare dopo il caricamento della pagina per rendere l'app utilizzabile: scaricare altro JavaScript, analizzarlo, eseguirlo, costruire la UI, agganciare handler e poi continuare a fare lavoro ad ogni aggiornamento. Anche su dispositivi veloci c'è un limite a quanto JavaScript puoi spingere nel browser prima che l'esperienza inizi a rallentare.
I problemi di performance emergono anche più tardi. All'inizio l'app è piccola: poche schermate, dati leggeri, UI semplice. Poi il prodotto cresce. Il marketing aggiunge tracker, il design componenti più ricchi, i team aggiungono stato, funzionalità, dipendenze e personalizzazioni. Ogni cambiamento sembra innocuo da solo, ma il lavoro totale si accumula.
Per questo i team iniziano a considerare idee orientate al compilatore. L'obiettivo di solito non è raggiungere punteggi perfetti. È continuare a rilasciare senza che l'app diventi più lenta ogni mese.
La maggior parte dei framework frontend ti aiuta a fare due cose: costruire un'app e mantenere la UI sincronizzata con i dati. La differenza chiave è quando avviene la seconda parte.
Con un framework pesante in runtime, gran parte del lavoro avviene nel browser dopo il caricamento della pagina. Spedisci un runtime generico in grado di gestire molti casi: tracciare cambiamenti, decidere cosa aggiornare e applicare quegli aggiornamenti. Questa flessibilità può essere ottima per lo sviluppo, ma spesso significa più JavaScript da scaricare, analizzare ed eseguire prima che la UI sia pronta.
Con l'ottimizzazione a tempo di compilazione, gran parte di quel lavoro si sposta nella fase di build. Invece di inviare al browser un grande insieme di regole, lo strumento di build analizza i tuoi componenti e genera codice più diretto e specifico per l'app.
Un modello mentale utile:
La maggior parte dei prodotti reali sta da qualche parte nel mezzo. Gli approcci compiler-first spediscono comunque del codice runtime (routing, fetching dei dati, animazioni, gestione degli errori). I framework runtime-heavy si appoggiano anche a tecniche di build (minificazione, code splitting, rendering server-side) per ridurre il lavoro client. La domanda pratica non è quale schieramento sia “giusto”, ma quale mix si adatta al tuo prodotto.
Rich Harris è una delle voci più chiare dietro il pensiero frontend orientato al compilatore. Il suo argomento è semplice: fai più lavoro in anticipo così gli utenti scaricano meno codice e il browser fa meno lavoro.
La motivazione è pratica. Molti framework pesanti in runtime spediscono un motore general-purpose: logica dei componenti, reattività, diffing, scheduling e helper che devono funzionare per ogni possibile app. Quella flessibilità costa byte e CPU. Anche quando la tua UI è piccola, puoi comunque pagare il prezzo di un runtime grande.
Un approccio compilatore inverte il modello. Durante la build, il compilatore guarda i tuoi componenti reali e genera il codice specifico per aggiornare il DOM di cui hanno bisogno. Se un'etichetta non cambia mai, diventa semplice HTML. Se cambia solo un valore, viene emesso soltanto il percorso di aggiornamento per quel valore. Invece di spedire una macchina UI generica, spedisci l'output su misura per il tuo prodotto.
Questo spesso porta a un risultato semplice: meno codice di framework inviato agli utenti e meno lavoro fatto a ogni interazione. Si nota soprattutto sui dispositivi di fascia bassa, dove l'overhead a runtime diventa visibile rapidamente.
I compromessi rimangono rilevanti:
Una regola pratica: se la tua UI è in gran parte determinabile a build time, un compilatore può generare output molto compatto. Se la UI è altamente dinamica o guidata da plugin, un runtime più pesante può essere più semplice.
L'ottimizzazione a build sposta il luogo dove avviene il lavoro. Più decisioni vengono prese durante la build e meno lavoro resta per il browser.
Un risultato visibile è meno JavaScript inviato. Bundle più piccoli riducono il tempo di rete, il tempo di parsing e il ritardo prima che la pagina risponda a un tap o click. Su telefoni di fascia media questo conta più di quanto molti team si aspettino.
I compilatori possono anche generare aggiornamenti DOM più diretti. Quando la build vede la struttura di un componente, può produrre codice che tocca solo i nodi DOM che realmente cambiano, senza tanti strati di astrazione ad ogni interazione. Questo rende aggiornamenti frequenti più scattanti, specialmente in liste, tabelle e form.
L'analisi a build-time può poi rafforzare tree-shaking e rimozione del codice morto. Il guadagno non è solo file più piccoli, ma meno percorsi di codice che il browser deve caricare ed eseguire.
L'hydration è un altro ambito dove le scelte di build possono aiutare. L'hydration è il passo in cui una pagina renderizzata dal server diventa interattiva attaccando handler e ricostruendo lo stato nel browser. Se la build può marcare ciò che necessita davvero interattività e ciò che no, puoi ridurre il lavoro al primo caricamento.
Come effetto collaterale, la compilazione spesso migliora anche lo scoping del CSS. La build può riscrivere nomi di classi, rimuovere stili non usati e ridurre perdite di stile tra componenti. Questo abbassa costi imprevisti man mano che la UI cresce.
Immagina una dashboard con filtri e una grande tabella di dati. Un approccio compiler-first può mantenere il caricamento iniziale più leggero, aggiornare solo le celle cambiate dopo un click su un filtro ed evitare di idratare parti della pagina che non diventano mai interattive.
Un runtime più grande non è automaticamente negativo. Spesso compra flessibilità: pattern decisi a runtime, molte componenti di terze parti e flussi di lavoro consolidati.
I framework heavy in runtime brillano quando le regole dell'interfaccia cambiano spesso. Se ti servono routing complessi, layout nidificati, form ricchi e un modello di stato profondo, un runtime maturo può sembrare una rete di sicurezza.
Un runtime aiuta quando vuoi che il framework gestisca molte cose mentre l'app è in esecuzione, non solo in fase di build. Questo può rendere i team più veloci giorno per giorno, anche se aggiunge overhead.
Vantaggi comuni includono un grande ecosistema, pattern familiari per stato e fetching dei dati, dev tools solidi, estendibilità in stile plugin e onboarding più semplice quando assumi persone che conoscono lo stack.
La familiarità del team è un costo e un beneficio reali. Un framework leggermente più lento con cui il team può consegnare con fiducia può battere un approccio più veloce che richiede riqualificazione, disciplina più rigida o tooling personalizzato per evitare trappole.
Molti reclami su app “lente” non sono causati dal runtime del framework. Se la tua pagina aspetta API lente, immagini pesanti, troppi font o script di terze parti, cambiare framework non eliminerà il problema principale.
Un pannello amministrativo interno dietro login spesso va bene anche con un runtime più grande, perché gli utenti sono su dispositivi potenti e il lavoro è dominato da tabelle, permessi e query backend.
“Abbastanza veloce” può essere l'obiettivo giusto nelle prime fasi. Se stai ancora verificando il valore del prodotto, mantieni alta la velocità di iterazione, stabilisci budget di base e affronta la complessità compiler-first solo quando hai prove che serva.
La velocità di iterazione è il tempo al feedback: quanto rapidamente qualcuno può cambiare una schermata, eseguirla, vedere cosa è successo e correggerlo. I team che mantengono questo ciclo corto rilasciano più spesso e imparano più rapidamente. Per questo i framework runtime-heavy possono sembrare produttivi all'inizio: pattern familiari, risultati rapidi, tanto comportamento integrato.
Il lavoro di performance rallenta quel ciclo quando è fatto troppo presto o troppo diffusamente. Se ogni pull request si trasforma in dibattiti di micro-ottimizzazione, il team smette di rischiare. Se costruisci una pipeline complessa prima di sapere cosa è il prodotto, si perde tempo a lottare con il tooling invece di parlare con gli utenti.
Il trucco è mettersi d'accordo su cosa significa “abbastanza buono” e iterare dentro quei limiti. Un budget di performance ti dà quel vincolo. Non si tratta di inseguire punteggi perfetti, ma di limiti che proteggono l'esperienza mantenendo il progresso nello sviluppo.
Un budget pratico potrebbe includere:
Se ignori la performance, di solito paghi dopo. Quando un prodotto cresce, la lentezza si lega a decisioni architetturali, non solo a piccoli ritocchi. Una riscrittura tardiva può significare congelare funzionalità, riqualificare il team e rompere flussi che prima funzionavano.
Il tooling compiler-first può spostare questo compromesso. Potresti accettare build leggermente più lente, ma riduci il lavoro fatto su ogni dispositivo, ad ogni visita.
Rivedi i budget man mano che il prodotto si dimostra. All'inizio proteggi le basi. Con il crescere del traffico e delle entrate, stringi i budget e investi dove le modifiche muovono metriche reali, non orgoglio.
Le discussioni sulla performance si complicano quando nessuno concorda su cosa significhi “veloce”. Scegli un piccolo insieme di metriche, scrivile e trattale come un tabellone condiviso.
Un set iniziale semplice:
Misura su dispositivi rappresentativi, non solo sul laptop di sviluppo. CPU veloci, cache calde e server locali possono nascondere ritardi che emergono su un telefono di fascia media in rete mobile.
Mantieni il tutto pragmatico: scegli due o tre dispositivi che rispecchiano gli utenti reali e esegui lo stesso flusso ogni volta (schermata home, login, un'attività comune). Fai questo con costanza.
Prima di cambiare framework, cattura una baseline. Prendi la build di oggi, registra i numeri per gli stessi flussi e tieni quella fotografia di riferimento.
Non giudicare la performance da un singolo punteggio di laboratorio. Gli strumenti di laboratorio aiutano, ma possono premiare la cosa sbagliata (ottimo primo caricamento) ignorando ciò di cui gli utenti si lamentano (menu scattosi, digitazione lenta, ritardi dopo la prima schermata).
Quando i numeri peggiorano, non indovinare. Controlla cosa è stato rilasciato, cosa ha bloccato il rendering e dove è stato speso il tempo: rete, JavaScript o API.
Per fare una scelta calma e ripetibile, tratta le decisioni su framework e rendering come decisioni di prodotto. L'obiettivo non è la tecnologia migliore, ma il giusto equilibrio tra performance e ritmo di lavoro del team.
Una thin slice dovrebbe includere le parti impegnative: dati reali, autenticazione e la tua schermata più lenta.
Se vuoi un modo rapido per prototipare quella thin slice, Koder.ai ti permette di costruire flussi web, backend e mobile via chat e poi esportare il codice sorgente. Questo ti aiuta a testare una rotta reale presto e mantenere gli esperimenti reversibili con snapshot e rollback.
Documenta la decisione in linguaggio semplice, incluso cosa ti farebbe rivederla (crescita del traffico, quota mobile, obiettivi SEO). Così la scelta resta valida anche quando il team cambia.
Le decisioni sulla performance di solito vanno storte quando i team ottimizzano ciò che vedono oggi, non ciò che gli utenti sentiranno tra tre mesi.
Un errore è over-ottimizzare nella prima settimana. Un team passa giorni a limare millisecondi su una pagina che cambia ancora quotidianamente, mentre il vero problema è che gli utenti non hanno ancora le funzionalità giuste. All'inizio, accelera l'apprendimento. Blocca il lavoro di performance profondo quando rotte e componenti si stabilizzano.
Un altro errore è ignorare la crescita dei bundle finché non diventa un problema. Le cose vanno bene a 200 KB, poi qualche aggiunta “piccola” dopo ti porta a megabyte. Un'abitudine semplice aiuta: traccia la dimensione dei bundle nel tempo e tratta i salti improvvisi come bug.
I team spesso scelgono rendering solo client-side per tutto, anche quando alcune rotte sono per lo più statiche (pagine pricing, documentazione, onboarding). Quelle pagine possono essere servite con molto meno lavoro sul dispositivo.
Un killer più silenzioso è aggiungere una grande libreria UI per comodità senza misurare il suo costo nelle build di produzione. La comodità è valida. Sii solo chiaro su cosa stai pagando in JavaScript, CSS extra e interazioni più lente su telefoni di fascia media.
Infine, mescolare approcci senza confini chiari crea app difficili da debug. Se metà dell'app si aspetta aggiornamenti generati dal compilatore mentre l'altra metà si affida a magie a runtime, ottieni regole poco chiare e errori confusi.
Alcune linee guida che funzionano nei team reali:
Immagina un team di 3 persone che costruisce una SaaS per scheduling e fatturazione. Ha due facce: un sito marketing pubblico (landing, pricing, docs) e una dashboard autenticata (calendario, fatture, report, impostazioni).
Con una strada runtime-first scelgono una configurazione pesante in runtime perché rende rapide le modifiche UI. La dashboard diventa una grande app client-side con componenti riusabili, una libreria di stato e interazioni ricche. L'iterazione è veloce. Col tempo il primo caricamento comincia a sentirsi pesante su telefoni di fascia media.
Con una strada compiler-first scelgono un framework che sposta più lavoro al build time per ridurre JavaScript client. I flussi comuni come aprire la dashboard, cambiare schede e cercare risultano più scattanti. Il compromesso è che il team deve essere più deliberato su pattern e tooling, e alcune scorciatoie runtime non sono così plug-and-play.
Il cambiamento è raramente per gusto: è solitamente pressione dalla realtà: pagine più lente riducono le iscrizioni, più utenti arrivano su dispositivi economici, clienti enterprise chiedono budget prevedibili, la dashboard resta aperta a lungo e la memoria conta, o i ticket di supporto citano lentezza su reti reali.
Un'opzione ibrida spesso vince. Mantieni le pagine marketing leggere (server-rendered o per lo più statiche, codice client minimo) e accetta più runtime nella dashboard dove l'interattività ripaga.
Usando i passaggi decisionali: nominano i percorsi critici (iscrizione, prima fattura, report settimanale), li misurano su un telefono di fascia media, fissano un budget e scelgono l'ibrido. Default compiler-first per pagine pubbliche e componenti condivisi, runtime-heavy solo dove accelera chiaramente l'esperimentazione.
Il modo più semplice per rendere reali queste idee è un loop settimanale corto.
Inizia con una scansione di 15 minuti: la dimensione del bundle sta crescendo? Quali rotte sembrano lente? Quali sono i maggiori pezzi UI su quelle rotte (tabelle, grafici, editor, mappe)? Quali dipendenze pesano di più? Poi scegli un collo di bottiglia che puoi risolvere senza riscrivere lo stack.
Per questa settimana, mantieni piccolo l'impegno:
Per mantenere le scelte reversibili, definisci confini chiari tra rotte e funzionalità. Preferisci moduli che puoi sostituire in seguito (grafici, editor rich text, SDK analitici) senza toccare tutta l'app.
La maggior parte delle volte non è solo la rete: è il costo a runtime: il browser che scarica, analizza ed esegue JavaScript, costruisce l'interfaccia e continua a fare lavoro ad ogni aggiornamento.
Per questo un'app può risultare “pesante” anche su un buon laptop quando il carico JavaScript diventa grande.
Hanno lo stesso obiettivo (far fare meno lavoro al client), ma il meccanismo è diverso.
Significa che il framework può analizzare i tuoi componenti in fase di build e produrre codice su misura per la tua app, invece di spedire un grande motore UI generico.
Il vantaggio pratico è solitamente bundle più piccoli e meno lavoro CPU durante le interazioni (click, digitazione, scorrimento).
Inizia con:
Misura lo stesso flusso utente ogni volta così puoi confrontare le build in modo affidabile.
Può aiutare, ma prima verifica dove va davvero il tempo: rete, immagini pesanti, font, script di terze parti o CPU JavaScript. Se il collo di bottiglia non è il runtime del framework, cambiare framework non risolverà il problema principale.
La scelta del framework è una leva fra molte: misurate prima dove passa il tempo (network, CPU, rendering o backend).
Scegli runtime-heavy quando ti servono flessibilità e velocità di iterazione:
Se il runtime non è il collo di bottiglia, la comodità può valere qualche byte in più.
Una regola pratica:
L'ibrido funziona meglio se scrivi regole chiare, così l'app non diventa un miscuglio confuso di assunzioni.
Usa un budget che protegga la sensazione utente senza bloccare la release. Per esempio:
I budget sono guide, non una gara per il punteggio perfetto.
L'hydration è il lavoro necessario per rendere interattiva una pagina renderizzata dal server: attaccare handler e ricostruire lo stato nel browser.
Se si idrata troppo, il primo caricamento può sembrare lento anche se l'HTML è subito visibile. La build può ridurre l'hydration segnando ciò che davvero deve essere interattivo.
Un buon “thin slice” dovrebbe includere il lato reale e difficile:
Se prototipi questa slice, Koder.ai può aiutarti a costruire il percorso web + backend via chat ed esportare il codice sorgente, così puoi misurare e confrontare approcci senza impegnarti in un rewrite completo.