Le Lambda que Bubble ne documente pas

Les actions côté serveur d’un plugin tournent sur AWS Lambda. La documentation mentionne Node.js et les modules npm ; elle ne dit pas ce qui se passe à la limite. Voici les chiffres qu’on ne découvre qu’en lisant les logs.

ContrainteValeur réelle
Mémoireenviron 128 Mo, fixe
Délai30 secondes, tout compris
Version de NodeNode 22 (au moment où nous écrivons)
Système de fichiersaucun, tout vit en mémoire
API du navigateurabsentes (pas de document, window...)
Taille de réponseenviron 50 Mo

On code en tenant compte de ces limites dès le départ, au lieu de les découvrir un soir de production.

Le budget de 30 secondes

Votre action a 30 secondes pour tout faire : télécharger une ressource externe, charger ses modules, traiter les données, renvoyer le résultat. Le piège : si un module lourd est lent à charger au démarrage à froid et qu’une ressource externe tarde à répondre, la moitié du budget est partie avant même que la logique métier commence. On répartit ce budget consciemment ; on ne l’improvise pas.

Moins de dépendances, moins de surprises

Quand vous écrivez require("un-module"), Bubble scanne votre code avant l’exécution pour décider quoi installer, et refuse la publication si une dépendance (ou une de ses sous-dépendances) porte une faille connue. Chaque module ajouté est donc un risque de blocage, un poids sur les 128 Mo, et une licence à vérifier. La meilleure dépendance est celle qu’on n’ajoute pas : depuis Node 22, beaucoup de besoins ont une réponse native.

Au lieu deOn utilise (natif Node 22)
uuidcrypto.randomUUID()
axios (requêtes simples)fetch()
momentIntl.DateTimeFormat, Date
bufferBuffer (global)

Détail qui piège : si vous chargez un module ESM avec import(), le parser ne le voit pas. Il faut un require() « mort » pour qu’il l’installe quand même.

// Le parser ne lit que require() : ce require mort force l’installation
if (false) require("mon-module-esm");

// ...tandis que l’import réel tourne au runtime
const mod = await import("mon-module-esm");

C’est le genre de détail qu’on n’apprend qu’en ayant expédié de vrais plugins.

Gérer la mémoire sous 128 Mo

La plupart des plugins n’y pensent jamais : un simple wrapper d’API tient dans 30 à 40 Mo. Mais dès qu’on traite des fichiers ou qu’on charge une bibliothèque lourde, le plafond devient la contrainte d’ingénierie principale. L’ordre de chargement compte plus qu’on ne le croit.

// Télécharger pendant que la mémoire est libre, PUIS charger le module lourd
const buffer = Buffer.from(await (await fetch(url)).arrayBuffer());
const engine = await import("bibliotheque-lourde"); // l’ordre inverse risque l’OOM

À 80 % du plafond, cet ordre fait la différence entre une action qui passe et une action qui plante.

Le contrat de type : renvoyer exactement ce qui est déclaré

Bubble est strict sur le type de chaque valeur de sortie. Une valeur déclarée « booléen » qui renvoie la chaîne "false", ou un champ texte qui renvoie undefined, fait planter l’action avec une erreur déroutante. La parade est le helper fail() (vu dans notre guide des plugins) qui renvoie toutes les sorties, avec le bon type, y compris sur le chemin d’échec.

Un piège fréquent : une case à cocher déclarée par erreur en « texte » affiche « yes » ou « no » au lieu d’un vrai booléen. Ça fonctionne à peu près, jusqu’à ce que le code renvoie un booléen et fasse tout planter. Vérifiez toujours le type de vos champs, surtout success.

Coder sur la défensive

Sur le marketplace, n’importe qui peut mettre n’importe quoi dans n’importe quel champ. Le code doit survivre à toute entrée sans exposer d’erreur interne. On classe chaque champ en trois niveaux : rejet net quand la valeur est fondamentalement fausse (on renvoie une erreur claire), correction automatique quand elle est corrigeable sans ambiguïté (on corrige en silence et on trace), tolérance quand elle est absente mais a un défaut évident (on prend le défaut). Une règle d’or : ne jamais modifier un montant monétaire vers le haut sans le dire. Et chaque message d’erreur devrait répondre à trois questions : ce qui s’est passé, ce que l’utilisateur a fourni, et comment corriger.

Déboguer ce qui échoue en silence

Un élément côté client échoue autrement qu’une action serveur : il n’y a pas de log serveur, l’élément « ne s’affiche juste pas ». La console du navigateur est votre seule fenêtre : préfixez chaque console.error du nom du plugin pour les retrouver vite. Et méfiez-vous des SDK tiers qui interceptent leurs propres erreurs et les relancent sous forme d’objets sans message : e.message vaut alors undefined, et vous loguez « échec : undefined », ce qui n’aide personne.

Notre banc d’essai

On ne théorise pas : ces leçons viennent de plugins réels, expédiés et maintenus. On les conçoit, teste et documente sur notre app de démo publique. Voir le laboratoire.


Un plugin à concevoir, ou à remettre d’aplomb ? Parlons-en.