React è il framework frontend più diffuso, e proprio per questo viene proposto per qualunque progetto. Ma un framework è uno strumento con un dominio di applicazione, e la discussione su quando usarlo è quasi sempre impostata sulla domanda sbagliata: quante pagine avrà il sito?
Il numero di pagine non c'entra quasi niente. Un sito da cento pagine sostanzialmente statiche non ha alcun bisogno di un framework; un'applicazione con tre schermate e uno stato condiviso complesso ne ha molto bisogno.
La domanda giusta: quanti tipi di stato#
// La domanda che decide, e non è "quante schermate ho".// È: QUANTI TIPI DI STATO diversi deve gestire l'interfaccia?// 1. Stato del server (dati che vivono altrove: elenchi, dettagli, profili)// → non è "stato": è una CACHE di dati remoti. Ha bisogno di// invalidazione, rifetch, deduplica, gestione degli errori.// È il 70% di ciò che le persone mettono in uno store globale, ed// è il posto sbagliato: uno store non sa quando il dato è vecchio.// 2. Stato dell'URL (filtri, pagina corrente, ordinamento, tab aperta)// → deve stare nella querystring, non in memoria. Altrimenti un link// condiviso non riproduce quello che l'utente stava guardando, e// il tasto "indietro" fa la cosa sbagliata.// 3. Stato locale del componente (un menù aperto, un campo in scrittura)// → useState, e non deve uscire da lì.// 4. Stato condiviso davvero globale (utente autenticato, tema, carrello)// → questo, e solo questo, giustifica uno store.// Nella maggior parte delle applicazioni sono tre o quattro valori.// Se dopo questa suddivisione il punto 4 è quasi vuoto, probabilmente// non serviva un framework: serviva un modo migliore di leggere i dati.Questa suddivisione è la parte più utile dell'intero articolo, perché fa emergere una cosa che nella pratica si vede in quasi tutti i progetti: gran parte di ciò che le persone chiamano «stato globale» non è stato. È una copia locale di dati che vivono sul server, e trattarla come stato significa doversi occupare a mano di invalidazione, aggiornamento e coerenza — cioè dei problemi difficili, con lo strumento sbagliato.
// ✗ Il modello che genera più codice e più bug: i dati remoti trattati// come stato locale. Tre useState, un useEffect, e le condizioni di// corsa da gestire a mano.function Fatture() { const [dati, setDati] = useState(null); const [caricamento, setCaricamento] = useState(true); const [errore, setErrore] = useState(null); useEffect(() => { let annullato = false; // ← e chi se lo ricorda sempre fetch('/api/fatture') .then(r => r.json()) .then(d => { if (!annullato) setDati(d); }) .catch(e => { if (!annullato) setErrore(e); }) .finally(() => { if (!annullato) setCaricamento(false); }); return () => { annullato = true; }; }, []); ...}// ✓ Una libreria di data fetching fa questo, più deduplica, cache,// rifetch alla riconnessione e invalidazione dichiarativa.function Fatture() { const { data, isPending, error } = useQuery({ queryKey: ['fatture'], queryFn: () => fetch('/api/fatture').then(r => r.json()) }); ...}Le due versioni non differiscono solo per lunghezza. La prima ha almeno tre problemi che si manifestano più tardi: nessuna deduplica (due componenti che chiedono le stesse fatture fanno due richieste), nessuna cache (tornando indietro si ricarica tutto), nessun aggiornamento al ritorno online. La seconda li ha risolti tutti perché sono il problema che quella libreria risolve.
Lo stato dell'URL#
Il secondo tipo è quello che si sbaglia con più frequenza, e il sintomo è riconoscibile: l'utente applica tre filtri, copia il link e lo manda a un collega, che apre una pagina vuota. Oppure preme «indietro» e invece di tornare al filtro precedente esce dall'applicazione.
Filtri, ordinamento, pagina corrente, scheda aperta, termini di ricerca: tutto questo è stato che appartiene all'URL. Metterlo in memoria significa rompere due funzioni che l'utente dà per scontate — condividere un indirizzo e navigare con i tasti del browser — per guadagnare nulla.
Il costo che non entra nel preventivo#
# Il costo che nessuno mette nel preventivo: la manutenzione.# Un progetto React fermo due anni non e' "fermo": e' in debito.npm outdatedPackage Current Wanted Latestreact 18.2.0 18.3.1 19.2.0react-router 6.8.0 6.30.0 7.9.1vite 4.3.9 4.5.5 7.1.4@types/react 18.0.28 18.3.12 19.2.1npm audit12 vulnerabilities (3 moderate, 8 high, 1 critical)# Il salto di versione principale porta modifiche non retrocompatibili# che vanno assorbite in blocco. Fatto ogni sei mesi e' mezza giornata;# fatto dopo due anni e' una settimana, con test di regressione su tutto.Questo è l'aspetto che vale la pena discutere con il committente prima, perché determina il costo di possesso su tre anni. Un progetto vanilla con due dipendenze si aggiorna in un pomeriggio; un progetto con sessanta pacchetti transitivi richiede un ritmo di manutenzione costante — e se quel ritmo non viene mantenuto, il costo non scompare: si accumula e si presenta tutto insieme quando serve una modifica urgente.
Cosa è cambiato con il rendering lato server#
// Il punto in cui il discorso è cambiato: rendere lato server non è più// una scelta fra "tutto sul client" e "tutto sul server".//// Componente server → gira sul server, non arriva mai al browser.// Può leggere il database direttamente.// Zero JavaScript spedito per quella parte.//// Componente client → 'use client', arriva al browser, ha stato// e gestori di eventi.//// La conseguenza pratica: il peso del JavaScript non è più proporzionale// alla complessità dell'applicazione, ma alla sua INTERATTIVITÀ. Una// dashboard con molte tabelle e pochi controlli può spedire pochissimo.//// Il costo: serve un runtime Node in produzione, il modello mentale è// più complesso, e il confine fra server e client è un posto nuovo in// cui sbagliare (un segreto letto in un componente server e passato// come prop a uno client finisce nel bundle).È il cambiamento più significativo degli ultimi anni per questa decisione. Il compromesso storico era binario: o pagine statiche leggere ma poco interattive, o un'applicazione ricca che spedisce al browser tutto il proprio codice. I componenti eseguiti sul server rompono quella dicotomia — la parte non interattiva non arriva mai al browser.
Va detto anche il rovescio: serve un runtime Node in produzione (non più un semplice server di file statici), il modello mentale è più complesso, e il confine fra server e client è un posto nuovo in cui sbagliare. Il caso da tenere presente: un segreto letto in un componente server e passato come proprietà a uno client finisce nel bundle spedito al browser — con le conseguenze descritte nell'articolo su cosa si può proteggere nel frontend.
Le tre configurazioni che quasi nessuno fa#
// vite.config.ts — le tre cose che vale la pena configurare subito,// e che quasi nessuno configura.import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';import { visualizer } from 'rollup-plugin-visualizer';export default defineConfig({ plugins: [ react(), // 1. Vedere COSA c'è nel bundle. Senza, si scopre a progetto finito // di aver incluso una libreria di date da 300 KB per formattare // tre timestamp. visualizer({ open: true, gzipSize: true }) ], build: { target: 'es2020', // 2. Un tetto esplicito: se il bundle lo supera, la build avvisa. chunkSizeWarningLimit: 250, rollupOptions: { output: { // 3. Separare le dipendenze dal proprio codice: cambiando una // riga di applicazione, i visitatori non riscaricano React. manualChunks: { react: ['react', 'react-dom'], router: ['react-router'] } } } }});L'analizzatore di bundle è quello che ripaga di più e costa meno: senza, si scopre a progetto finito di aver incluso una libreria di gestione date da trecento kilobyte per formattare tre timestamp, o due copie della stessa dipendenza in versioni diverse. Con il grafico davanti, quelle scoperte si fanno il primo giorno.
Il test a sette domande#
Il test onesto, da fare PRIMA di scegliere. Contate quante di questefrasi descrivono il progetto che state per cominciare: □ Lo stesso dato compare in schermate diverse e deve restare allineato □ L'utente resta nella stessa "sessione" per decine di minuti □ Ci sono aggiornamenti in tempo reale (WebSocket, polling, notifiche) □ Esistono componenti complessi riusati con varianti in molti punti □ Più persone lavoreranno sulla stessa base di codice □ L'interfaccia ha più di ~15 schermate con navigazione fra loro □ Ci sono moduli lunghi, a più passi, con validazione interdipendente 0-1 → un framework è un costo senza contropartita 2-3 → zona grigia: decide il team e l'orizzonte del progetto 4+ → il framework si ripaga; sceglierne uno e configurarlo bene| Progetto | Scelta ragionevole | Perché |
|---|---|---|
| Sito vetrina, blog, portfolio | server-side + JS minimo | nessuno stato condiviso, priorità al tempo di avvio |
| Landing con modulo e validazione | vanilla + Vite | interattività localizzata |
| Catalogo con filtri e carrello | zona grigia | dipende da quanto stato attraversa le schermate |
| Gestionale, cruscotto, configuratore | framework | stato condiviso, molte schermate, componenti riusati |
| Applicazione in tempo reale | framework | la sincronizzazione a mano non regge |
| Sito editoriale con isole interattive | framework con idratazione parziale | HTML statico + JavaScript solo dove serve |
| Applicazione con molti dati e poca interazione | componenti server | il peso segue l'interattività, non la complessità |
Un'ultima considerazione che riguarda il mestiere più che la tecnica: la scelta del framework è la decisione più difficile da revocare di un progetto frontend. Cambiare database si può fare dietro un'interfaccia; cambiare framework significa riscrivere l'interfaccia. Vale quindi la pena di dedicarle qualche ora di analisi onesta all'inizio — e di essere disposti a rispondere «non serve», che è la risposta corretta più spesso di quanto la moda suggerisca.