Il problema
Il cliente voleva una dashboard per mostrare in tempo reale creazioni, trasferimenti ed eliminazioni di SmartNFT (una sorta di biglietto). Oltre agli aggiornamenti live, qualsiasi visitatore doveva poter avere una visione panoramica dell'intero ecosistema: dati storici, prezzo del token, totale in staking e valori aggregati derivati da tutto quanto sopra descritto.
Prime valutazioni
La prima priorità del progetto era quella di servire al visitatore la stragrande maggioranza delle informazioni con una latenza molto bassa. Per quanto riguarda asset come immagini, video, script e così via, la risposta più sensata era una CDN, e quella di Vercel avrebbe coperto alla perfezione questa necessità sia per clienti dentro che fuori Europa.
Oltre a questo, contava altrettanto dove risiedesse il backend (e quanto vicino fosse al database). Anche questo problema era risolvibile in pochi click, dato che su Vercel un amministratore può scegliere dove ospitare le funzioni NextJS. Persino in più luoghi contemporaneamente.
Per il database, inizialmente avevamo puntato su TimescaleDB, perché è pensato esattamente per questo tipo di lavoro - operazioni su una grandissima mole di dati strutturati in serie temporali tramite hypertable. Il problema principale, però, era il costo: la versione cloud, soprattutto con le read replica di cui avremmo avuto bisogno, era completamente fuori da qualsiasi budget ragionevole. Per costruire una prima versione funzionante del progetto, però, l'abbiamo usato comunque, così da avere la certezza che le prestazioni del database non avrebbero fatto da collo di bottiglia.
Ci serviva anche uno scraper (programma che cercasse, analizzasse e salvasse una grande mole di dati) capace di condividere rapidamente i dati raccolti on-chain con altri server che li avrebbero propagati ai visitatori. Per questo abbiamo scelto Bun. Sia per le prestazioni che per la facilità di implementazione.
Infine, per i server che dovevano inviare le informazioni in tempo reale ai visitatori, abbiamo optato per delle istanze VPS. Qualsiasi altra opzione era semplicemente troppo dispendiosa in termini di tempo di sviluppo e mantenimento per essere giustificata.
L'architettura finale si può riassumere così:
- Scraper - controlla i dati on-chain e invia dati pronti alla visualizzazione a un server principale.
- Server principale - può esso stesso gestire l'invio di dati in tempo reale, oppure essere configurato per condividere i dati solo con altri server di replica (per scalabilità).
- Server di replica - ha l'unico scopo di inviare dati in tempo reale tramite Server-Sent Events (SSE) a chi visualizza il sito.
- Backend - gestisce caching e relativa invalidazione, query verso servizi esterni come CoinGecko e altre chiamate API.
- Client - l'interfaccia completa, costruita con Next.js, TailwindCSS e TypeScript. Questa combinazione ci ha permesso di sviluppare il tutto in modo estremamente efficiente.
Modus Operandi
Conclusa la fase di design, rimaneva la fase di implementazione, che abbiamo affrontato nell'ordine sopra elencato.
Abbiamo iniziato sviluppando lo scraper, e ospitandolo su una VPS di dimensioni medio-piccole su Hetzner. Qui abbiamo incontrato il primo problema: i dati non erano affatto puliti e pronti ad essere direttamente salvati / inoltrati. Abbiamo riscontrato diverse incongruenze: timestamp corrotti, contenuti vuoti e così via. Dato la mole di dati da leggere, scartare solamente i dati completamente illeggibili e recuperare queli mal formattati è stato molto dispendioso in termini di tempo. Soprattutto perché, per la dashboard, la consistenza delle informazioni era il fattore chiave.
Un altro grande problema erano i rate limit. Non potevamo semplicemente "leggere i dati da blockchain", quindi abbiamo dovuto acquistare servizi che ci permettessero di farlo in modo facile ed efficiente (Infura, per esempio). Per i compiti più facili ci siamo anche affidati agli explorer di blockchain ma, anche qui, i rate limit erano particolarmente asfissianti.
Il contesto in cui lo scraper doveva lavorare era particolarmente difficile: un enorme volume di dati da recuperare (più di 3 milioni di transazioni da arricchire, per un totale di decine di milioni di chiamate), sotto rate limit molto pesanti, il tutto da fare il più velocemente possibile. Abbiamo attraversato diverse iterazioni per arrivare a un buon compromesso tra questi vincoli. Lo scraper finale è riusciro a recuperare ogni transazione in poco meno di una settimana. Gli aspetti chiave della sua implementazione sono stati la gestione degli errori con timeout incrementali, l'assicurarci che, se lo scraper fosse andato in crash, i dati sarebbero rimasti integri, e il parallelizzare il più possibile.
Fortunatamente, il resto è stato più semplice. Per esempio, i server di replica, sono stati sviluppati con pochissime righe di codice.
La parte più difficile di tutto il backend è stato lo scrivere query SQL estremamenti efficienti, dato che puntavamo all'avere risposte sotto i 100 ms per le query più importanti. Questo significava che non potevamo appoggiarci a un ORM, e l'ottimizzazione delle query SQL ha finito per prendere una parte significativa di tempo. Soprattutto per tutte le chiamate che necessitavano di aggregare centinaia di migliaia, se non milioni, di transazioni.
Il frontend non è stato problematico. Con qualsiasi altro stack tecnologico, il tempo di implementazione di questa sezione sarebbe stato molto più lungo: parliamo di almeno 2–3× volte tato. Fortunatamente, mentre ci avvicinavamo al rilascio dell'MVP, Next.js ha rilasciato come supporto sperimentale il caching di componenti, query e altro ancora.
Cambiamenti
C'è stato un cambiamento importante rispetto alle nostre decisioni iniziali: il database. Siamo partiti con Timescale e siamo poi passati a Supabase. La ragione principale era il costo. Supabase costava meno della metà di Timescale, pur avendo un hardware migliore. E per quanto 3 milioni di transazioni siano tante, restano comunque gestibili da qualsiasi instanza di Postgres che abbia un buon hardware e avendo uno schema che faccia vasto uso di indici.
Questo ha comportato anche una grande modifica lato backend. Supabase offre query con routing in base alla zona geograficha di chi le effettua, ma solo se chiamate tramite il proprio SDK. Quindi, per sfruttare al meglio le varie read-replica del database e funzioni del backend sparse in giro per il mondo, abbiamo optato per utilizzare quest'ultima.
Tempistiche
Lo sviluppo è iniziato a novembre 2024. Lo scraper è stata la singola funzionalità che ha richiesto più tempo: due grandi release principali rilasciate in contemporanea a due importanti aggiornamenti dello schema del database.
- Scraper V1 - rilasciato a marzo 2025
- Scraper V2 - rilasciato a maggio 2025
- Lancio pubblico - 1 giugno 2025
Il frontend e tutto il resto sono stati sviluppati in parallelo.
Team
L'intero progetto è stato realizzato prima ancora che NFW Web Studio esistesse. È stato realizzato da un singolo membro del team, mentre il design è stato realizzato esternamente.
Qualche numero
Lighthouse / Speed Insights
| Metrica | Punteggio |
|---|---|
| Performance | 28 |
| Accessibilità | 100 |
| Best Practices | 100 |
| SEO | 100 |
| Agentic Browsing | 1/2 |
Una nota su quel punteggio lato Performance: il progetto è stato rilasciato nella primavera del 2025, sviluppato attivamente fino a novembre 2025 e tenuto in normale manutenzione fino all'inizio del 2026. Questa dashboard sfrutta attualmente un database fortemente sottodimensionato e collocato in un'unica località. I punteggi di accessibilità, best practices e SEO riflettono come l'applicazione è stata effettivamente costruita; il dato sulla performance riflette in gran parte l'infrastruttura una volta finita la manutenzione e supporto attivi, più che l'architettura originale. Affidarsi pesantemente ai Suspense boundaries sarebbe un trucco per migliorare estremamente il punteggio duplicandolo se non triplicandolo. Anche se, per la natura del progetto, sono i dati e la loro coerenza a contare, quindi, avere dei loader senza dati, servirebbe a gran poco.
Analisi delle query
Escludendo le query dello scraper, il database racconta una storia chiara: le query in quella che definiamo "hot path" (cammino critico) sono estremamente veloci, mentre quelle pesanti sono deliberatamente tenute fuori da esso.
Hot path:
- Feed eventi live (
get_smartnft_events) - ~141.000 chiamate a ~16,6 ms di media, con un tasso di cache hit del ~99%. Questo è il feed in tempo reale che alimenta la dashboard, ed è rapido. - Lookup di un singolo SmartNFT (
get_smartnft) - ~38.800 chiamate a ~9 ms di media, ~95% di cache hit. Le viste di dettaglio si risolvono quasi istantaneamente. - Listing degli indirizzi - ~172.600 chiamate a ~44 ms di media, ~97% di cache hit. Comodamente entro il budget per una lista paginata.
Aggregati pesanti (deliberatamente fuori dal percorso critico):
Una manciata di query sono estremamente costose.
Per esempio il riepilogo dei publisher (get_smartnft_publishers_summary, ~3,2 s di media) e il calcolo dei saldi dei wallet (get_wallet_balances, ~1,4 s di media, che a ogni chiamata somma su ~1,4M di trasferimenti). Questo è stato un compromesso accettabile: queste query hanno un volume molto basso di chiamate, sono estremamente ottimizzate a un livello superiore grazie al caching e non bloccano mai il rendering iniziale.
