Développer localement avec Cloudflare Workers est l'une des expériences les plus fluides que le sans serveur puisse offrir aujourd'hui. Le wrangler dev monte en quelques secondes, les logs apparaissent dans le terminal et tout semble fonctionner exactement comme prévu. Le problème est que ce confort masque des différences fondamentales qui n’apparaissent que lorsque le code atteint véritablement la limite. De nombreuses équipes découvrent tardivement que console.log en production ne mène nulle part à moins que quelqu'un fasse activement wrangler tail, que la mise en mémoire tampon d'une réponse de 50 Mo tue l'isolement en silence et que ce gestionnaire qui effectue 15 requêtes en D1 plus 10 lectures en KV est une bombe à retardement contre la limite de sous-requêtes.
Qu'arrive-t-il à vos logs en production
Localement, wrangler dev affiche tous les console.log sur le terminal en temps réel. En production, les isolats V8 de Cloudflare ne disposent pas de processus persistant pour capturer cette sortie : chaque appel s'exécute de manière isolée, s'exécute et disparaît. Les journaux ne sont accessibles que via wrangler tail, qui ouvre une session de streaming avec les travailleurs en production et relaie les métadonnées des demandes, la sortie console.log, les exceptions non interceptées et la durée du processeur.
Le point critique : cette séance ne persiste pas. Si personne ne suit lorsqu'une erreur se produit, le journal est perdu. Pour la rétention, la solution est Logpush, qui exporte les journaux vers R2, S3, Datadog ou Splunk à 0,05 $ par million de lignes pour R2. Mais Logpush fonctionne avec les champs structurés émis par le runtime Worker, pas avec le texte libre console.log. La conséquence pratique est que les journaux utiles en production doivent être structurés dès le début : JSON avec des champs comme requestId, duration, statusCode, error — des champs que wrangler tail peuvent filtrer et que Logpush peut exporter fidèlement.
Le modèle qui fonctionne consiste à créer un wrapper de journalisation minimal au début du projet, avant que vous en ayez besoin. Quelque chose qui sérialise en JSON et écrit en console.log, avec un champ level différenciant info de error. Lorsque Logpush arrive, ces champs sont disponibles pour les filtres et les alertes sur la cible.
Mémoire : 128 Mo, c'est plus petit qu'il n'y paraît
La limite de 128 Mo par isolat inclut le script non compressé dans la version V8, toutes les fermetures de modules et l'intégralité du tas de l'invocation en cours. Ce n'est pas 128 Mo pour « vos données », c'est 128 Mo pour tout, y compris le runtime lui-même.
L'erreur la plus courante est d'appeler response.arrayBuffer() dans les grandes réponses. Un fichier de 30 Mo téléchargé à partir d'un autre service pour être traité et transféré occupe instantanément 30 Mo de tas. Si le traitement crée davantage d'allocations intermédiaires, l'isolat dépasse 128 Mo et est tué : aucune exception captable, aucune réponse au client, juste une erreur 1101 à l'extérieur.
La solution est d'utiliser response.body comme ReadableStream et de traiter via TransformStream. Au lieu de mettre en mémoire tampon et de transformation, vous créez un pipeline dans lequel circulent les morceaux : lus depuis l'amont, transformés en transit, écrits en réponse sans jamais exister d'entiers en mémoire. Pour les réponses supérieures à 1 Mo, l’hypothèse doit être la diffusion en continu ; La mise en mémoire tampon doit être un choix explicite et justifié, et non le chemin par défaut.
Le même raisonnement s'applique aux téléchargements. Le corps maximum de la requête est de 100 Mo, mais request.arrayBuffer() essaie de tout allouer en même temps. Pour les téléchargements volumineux, le traitement doit également être effectué en flux, ou le fichier doit aller directement vers R2 via put() qui accepte un ReadableStream.
Sous-requêtes : la limite qui apparaît au pire moment
Chaque fetch(), chaque opération sur KV, chaque requête sur D1, chaque lecture sur R2 compte comme une sous-requête. Sur le forfait payant, la limite est de 1 000 par invocation. Gratuitement, 50.
Un gestionnaire qui semble raisonnable - récupère l'utilisateur dans D1, lit ses préférences dans KV, extrait le document de R2, appelle une API externe, enregistre le résultat dans D1 - est déjà à 5 sous-requêtes sur la bonne voie. Si ce gestionnaire entre dans une boucle car il traite une liste d'éléments, le compteur monte rapidement. Cinquante éléments avec deux opérations chacun sont déjà proches de 100. Un bug qui fait des requêtes N+1 dans D1 — recherchant chaque enregistrement enfant individuellement au lieu d'un JOIN — peut facilement déborder de 1 000 avant la fin d'une seule requête complexe.
L'erreur lorsque la limite est atteinte n'est pas évidente : le Worker reçoit une erreur réseau sur la sous-requête qui a dépassé la limite, ce qui peut être confondu avec une instabilité du réseau ou du service en aval. Un diagnostic correct nécessite de consulter les journaux d'exceptions via wrangler tail et de les mettre en corrélation avec le modèle d'appel du gestionnaire.
Atténuation : utilisez db.batch() dans D1 pour regrouper plusieurs requêtes en une seule sous-requête. Lisez les configurations KV une fois lors de l'initialisation du module et du cache dans la portée globale : l'isolat peut être réutilisé entre les requêtes dans le même PoP, et la lecture KV effectuée lors du premier appel n'a pas besoin d'être répétée lors des invocations suivantes tant que l'isolat est en vie.
Secrets, environnements et wrangler.toml qui va dans le référentiel
Les variables déclarées en wrangler.toml sous [vars] sont du texte clair dans le fichier de configuration, qui va normalement dans le référentiel. Pour toute valeur sensible (clés API, jetons bancaires, secrets de webhook) la seule option correcte est Workers Secrets, déclarée sous la forme [secrets] dans wrangler.toml et stockée via wrangler secret put. La valeur est chiffrée au repos et injectée au moment de l'exécution en tant que propriété de env, sans apparaître dans les journaux de construction ou les artefacts.
Le wrangler.toml d'un service passant en production doit avoir des environnements explicites : [env.staging] et [env.production] avec leurs propres liaisons, routes et secrets séparés. Mélanger la préparation et la production dans le même ensemble de liaisons est un accident qui attend de se produire, surtout lorsque D1 et KV disposent de données réelles en production et de données de test en préparation. La séparation des environnements dans wrangler.toml permet également au pipeline CI de se déployer automatiquement vers la zone intermédiaire et de nécessiter une approbation manuelle pour la production, sans aucune logique supplémentaire dans le script de déploiement.
À quoi ressemble une configuration de production minimale
Un wrangler.toml prêt pour la production fait explicitement référence à compatibility_date (afin de ne pas recevoir de modifications importantes du runtime sans avertissement), définit [observability] avec enabled = true pour exposer les métriques de base sur le tableau de bord et sépare [env.staging] de [env.production] avec des itinéraires différents. Les secrets sont répertoriés par nom sans valeur : la valeur n'existe que dans Cloudflare, jamais dans le dépôt.
Le gestionnaire principal a un try/catch au niveau le plus élevé qui détecte toutes les exceptions non gérées, les enregistre au format JSON structuré avec console.error et renvoie une réponse HTTP 500 avec un requestId traçable. Sans cette limite, une exception inattendue pourrait renvoyer un 200 avec un corps tronqué au client alors que la véritable erreur n'apparaît que dans la queue - et seulement si quelqu'un regarde.
A lire aussi
- Cloudflare Workers : Guide pratique de l'informatique de périphérie sans serveur
- D1 en production : performances, limites et ce qui n'évolue pas seul -WebAssembly à la périphérie : Pourquoi démarrer rapidement et de manière isolée
- Workers : débogage, journaux et Workers Tail — observabilité à la périphérie sans serveur de journaux
- Workers : limites de CPU et de mémoire — que la documentation n'explique pas bien -Développement d'applications sans serveur avec AWS Lambda et Cloudflare Workers en 2025
