Cosa sono i Cloudflare Workers
Come funzionano i Cloudflare Workers: il modello a isolate V8, perché non hanno cold start, i limiti reali di CPU e API, e quando convengono davvero.
Hai una funzione serverless che fa una cosa banale — leggere un header, controllare un token, restituire un JSON — e ogni tanto ci mette mezzo secondo a rispondere perché era addormentata. E gira in Virginia, mentre i tuoi utenti sono in Italia. I Cloudflare Workers nascono per questo tipo di problema. In questo articolo trovi come funzionano davvero sotto il cofano, cosa puoi farci, dove sbattono contro un muro e quando conviene sceglierli.
Cos'è un Cloudflare Worker
Un Cloudflare Worker è codice tuo che viene eseguito direttamente sui server della rete Cloudflare, nel punto più vicino a chi ha fatto la richiesta, invece che in un data center che hai scelto tu.
La differenza rispetto a una funzione serverless classica non è solo geografica. Cambia proprio il modo in cui il codice viene avviato, ed è da lì che deriva tutto il resto.
Il Worker più corto possibile è questo:
export default {
async fetch(request, env, ctx) {
return new Response("Ciao dal bordo della rete");
},
};
Nessun server, nessun framework, nessuna configurazione di rete. Una funzione fetch che riceve una richiesta e restituisce una risposta.
Il punto tecnico: isolate invece di container
Qui sta la parte interessante, ed è quella che spiega perché i Workers si comportano diversamente da tutto il resto.
Una funzione serverless tradizionale gira dentro un container o una micro-VM. Quando arriva una richiesta e non c'è nessuna istanza già calda, la piattaforma deve: allocare la macchina virtuale, avviare un sistema operativo minimale, far partire il runtime (Node, Python, quello che è), caricare il tuo codice e le dipendenze, e solo allora eseguire. Sono centinaia di millisecondi nel caso buono, secondi interi se il pacchetto è grosso. È il cold start.
Un Worker non gira in un container. Gira in un isolate V8.
V8 è il motore JavaScript di Chrome. Un isolate è un contesto di esecuzione separato dentro lo stesso processo V8: ha la sua memoria, le sue variabili, il suo heap, e non può vedere quelli degli altri isolate. È lo stesso meccanismo che il browser usa per tenere separate le schede.
Cloudflare tiene un processo V8 già avviato su ogni server della sua rete. Quando arriva una richiesta per il tuo Worker, non deve avviare niente: crea un isolate dentro un motore che è già in esecuzione e ci carica dentro il tuo codice. Parliamo di ordini di grandezza sotto il millisecondo, non di centinaia.
Le conseguenze pratiche sono tre:
- Il cold start smette di essere un problema di cui ti preoccupi. Non è letteralmente zero, ma è talmente basso da sparire dentro la latenza di rete.
- Non ha senso "tenere calda" la funzione. Tutti i trucchi tipo il ping ogni cinque minuti diventano inutili.
- La densità è enorme, e questo si riflette sul modello di prezzo: un server può ospitare migliaia di isolate contemporaneamente, cosa impensabile con altrettanti container.
Il prezzo di questo modello è che non stai eseguendo Node.js. Stai eseguendo JavaScript su V8 con le API della piattaforma web. Ed è da qui che nascono tutti i limiti.
Cosa puoi farci davvero
I casi in cui i Workers brillano hanno un tratto comune: fanno poco lavoro, lo fanno vicino all'utente e lo fanno spesso.
- Autenticazione e autorizzazione al bordo. Verifichi un token JWT prima che la richiesta tocchi il tuo backend. Se non è valido, la respingi in millisecondi senza consumare risorse del server.
- Instradamento e riscrittura. Redirect, A/B test, versioni diverse del sito per paese o dispositivo. Il Worker si comporta come un reverse proxy programmabile davanti alla tua infrastruttura.
- API leggere. Endpoint che leggono da un archivio e restituiscono JSON. Vedi cos'è una REST API.
- Manipolazione delle risposte. Aggiungere intestazioni di sicurezza, iniettare HTML, riscrivere URL al volo.
- Webhook e integrazioni. Ricevi una chiamata da un servizio esterno, la normalizzi, la inoltri.
- Controllo fine della cache. Decidi tu cosa memorizzare e per quanto, con logica applicativa. Il contesto è in cos'è il caching.
Un esempio realistico, un controllo di autenticazione con inoltro all'origine:
export default {
async fetch(request, env) {
const auth = request.headers.get("Authorization");
if (!auth?.startsWith("Bearer ")) {
return new Response(JSON.stringify({ error: "Non autorizzato" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
const token = auth.slice(7);
if (token !== env.API_TOKEN) {
return new Response("Token non valido", { status: 403 });
}
return fetch(request);
},
};
env.API_TOKEN non è scritto nel codice: è un secret configurato a parte, esattamente come faresti con le variabili d'ambiente.
I limiti reali
Qui serve onestà, perché è dove la gente perde tempo.
Il tempo di CPU è contato, e non è il tempo totale. Il Worker può restare in attesa quanto vuole — una chiamata a un'API esterna che ci mette due secondi non ti costa nulla in termini di CPU perché il tuo codice sta fermo. Quello che è limitato è il tempo in cui la CPU sta effettivamente eseguendo le tue istruzioni. Sui piani base l'ordine di grandezza è di poche decine di millisecondi, e si può alzare parecchio sui piani a pagamento — ma resta un modello pensato per lavoro breve.
Tradotto: se devi comprimere un video, generare un PDF di duecento pagine o addestrare qualcosa, i Workers non sono il posto giusto. Non è una questione di configurazione, è il modello che non ci si presta.
Non hai Node.js completo. Nessun fs, nessun accesso al filesystem, niente processi figli, niente socket TCP grezzi nella forma a cui sei abituato. Un buon numero di moduli Node è ora emulato attivando il flag di compatibilità, ma è emulazione parziale: una libreria che fa cose di basso livello può semplicemente non partire.
Le librerie pesanti sono un problema. Ogni Worker ha un limite alla dimensione del bundle, generoso ma non infinito. Un SDK enorme, un driver di database nativo o qualsiasi cosa contenga binari compilati per Node tipicamente non ci entra o non funziona.
Il debug è diverso. Non c'è un server su cui entrare in SSH a guardare i log. Hai wrangler tail per lo streaming in tempo reale e le dashboard, e devi abituarti a quello.
Lo stato non esiste tra una richiesta e l'altra. Una variabile globale può sopravvivere se l'isolate viene riutilizzato, ma non puoi contarci: potrebbe essere un isolate nuovo, o su un continente diverso. Se ti serve stato, ti serve un archivio.
Il legame con KV, D1 e R2
Un Worker da solo non conserva niente. Cloudflare gli mette accanto degli archivi pensati per lo stesso modello distribuito:
- KV — un archivio chiave-valore replicato su tutta la rete. Letture velocissime ovunque, ma consistenza eventuale: dopo una scrittura possono passare secondi prima che tutti i punti della rete vedano il valore nuovo. Perfetto per configurazioni, feature flag, sessioni, cache. Pessimo per un contatore o un saldo.
- D1 — un database SQL basato su SQLite. Query relazionali vere, ideale per applicazioni piccole e medie. Il tema in generale è in SQL o NoSQL.
- R2 — archiviazione di oggetti compatibile con l'API S3, per file e contenuti binari. La particolarità del suo modello di prezzo è che non ti fa pagare il traffico in uscita, che è la voce che di solito fa male con gli archivi concorrenti.
- Durable Objects — il pezzo che risolve il problema dello stato coordinato: una singola istanza con la sua memoria, utile per chat, presenza, contatori consistenti.
Il collegamento si dichiara nella configurazione, e nel codice te lo ritrovi dentro env:
# wrangler.toml
name = "mio-worker"
main = "src/index.js"
compatibility_date = "2026-09-01"
[[kv_namespaces]]
binding = "CACHE"
id = "il-tuo-namespace-id"
[[d1_databases]]
binding = "DB"
database_name = "produzione"
database_id = "il-tuo-database-id"
export default {
async fetch(request, env) {
const cached = await env.CACHE.get("prodotti", { type: "json" });
if (cached) {
return Response.json(cached);
}
const { results } = await env.DB.prepare(
"SELECT id, nome, prezzo FROM prodotti WHERE attivo = 1"
).all();
await env.CACHE.put("prodotti", JSON.stringify(results), {
expirationTtl: 300,
});
return Response.json(results);
},
};
Per sviluppare e pubblicare bastano due comandi:
npx wrangler dev # locale, con l'ambiente simulato
npx wrangler deploy # in produzione, su tutta la rete
Il deploy dura pochi secondi e propaga il codice su tutti i punti di presenza. Se vieni dal mondo dei server, il contrasto con configurare un VPS da zero è netto.
Cosa fa perdere ore
Aspettarsi che le variabili globali funzionino come cache. Funzionano finché l'isolate resta vivo, poi spariscono senza preavviso. Se il codice si comporta bene in sviluppo e in produzione perde dati a caso, quasi sempre è questo.
Scrivere su KV e rileggere subito. Consistenza eventuale significa che la lettura immediata può restituire il valore vecchio. Non è un bug e non c'è un'opzione da attivare: è il modello. Se ti serve leggere subito quello che hai appena scritto, usa D1 o un Durable Object.
Dimenticare compatibility_date. Determina quale versione del runtime usi. Se non la aggiorni mai, resti su comportamenti vecchi; se la sposti alla cieca su un progetto in produzione, può cambiare qualcosa sotto i piedi. Impostala e cambiala consapevolmente.
Installare la libreria a cui sei abituato senza verificarla. Prima di costruirci sopra mezza applicazione, controlla che dichiari il supporto ai runtime edge. Un pacchetto che importa crypto di Node o legge un file di configurazione dal disco si romperà.
Fare await su lavoro non necessario alla risposta. Se devi registrare un evento o inviare una notifica, usa ctx.waitUntil(): la risposta parte subito e il resto continua in sottofondo.
export default {
async fetch(request, env, ctx) {
const risposta = new Response("OK");
ctx.waitUntil(registraEvento(request, env));
return risposta;
},
};
Workers o funzione serverless tradizionale?
| Workers | Serverless tradizionale | |
|---|---|---|
| Avvio a freddo | Praticamente assente | Da centinaia di ms a secondi |
| Dove gira | Vicino all'utente, ovunque | Nella regione che scegli |
| Runtime | V8 con API web | Node, Python, Go, Java… |
| Lavoro lungo | Non adatto | Gestibile |
| Librerie native | Spesso incompatibili | Nessun problema |
| Vicinanza al database | Può essere lontano | Puoi metterla accanto |
Scegli i Workers quando la latenza conta, quando le richieste sono tante e brevi, quando il traffico è irregolare e i cold start ti stanno rovinando i tempi di risposta.
Non sceglierli quando ti serve una libreria dell'ecosistema Node che non è compatibile, quando il lavoro è computazionalmente pesante, o quando l'applicazione fa molte query a un database che vive in una regione precisa — in quel caso girare "vicino all'utente" significa girare lontano dai dati, e ci rimetti invece di guadagnarci.
Il confronto approfondito con l'alternativa più diffusa è in Cloudflare Workers o AWS Lambda.
In sintesi
Un Worker è codice che gira sulla rete Cloudflare, vicino a chi fa la richiesta, invece che in un data center scelto da te.
Il modello a isolate V8 è il motivo di tutto: niente container da avviare, quindi cold start irrilevante — ma anche niente Node.js completo, niente filesystem, e un tempo di CPU pensato per lavoro breve.
Da soli non conservano nulla: lo stato arriva da KV (veloce ovunque, consistenza eventuale), D1 (SQL), R2 (file) e Durable Objects (stato coordinato).
Sono la scelta giusta per lavoro leggero, frequente e sensibile alla latenza. Sono la scelta sbagliata per calcoli lunghi e librerie Node pesanti, e dirlo apertamente evita di scoprirlo a metà progetto.
Per il contesto generale della piattaforma vedi cos'è Cloudflare, e per il confronto diretto Workers o AWS Lambda.