Comment alléger son JavaScript
lundi 23 octobre 2023
Nous sommes à l’ère des frameworks JavaScript. React, Vue, Svelte, etc. Mais ils viennent avec leur lot de complexité. Notamment, mal utilisés, ils peuvent réellement impacter la performance de votre site.
En effet, si on veut que 75% des personnes qui accèdent à Internet dans le monde aient une expérience correcte, il faudrait que la totalité du JavaScript exécuté sur un site ne dépasse pas 300-350KB compressé (Source). Cela prend en compte le JavaScript que vous écrivez, mais aussi les third parties (Analytics, Tracking, Support, etc.).
Croyez-moi, 300KB, c’est beaucoup par rapport à quelques années en arrière (130-170KB en 2017). Mais très peu comparé à l’état du web, qui charge ~900KB pour 75% des sites.
Mais assez parlé de l’état général du web. Parlons plutôt de votre site et de votre quotidien. Dans cet article, je vais vous montrer comment savoir si c’est une problématique qui vous concerne, et les différentes techniques que vous pouvez mettre en place pour les corriger.
Si vous vous voulez accélérer votre prise en main des sujets performance, sachez que je suis aussi disponible en freelance pour rendre votre site plus rapide et vous former sur ces sujets.
Cet article fait partie d’une série autour de la web performance qui contient :
- Pourquoi améliorer le LCP de vos pages ?
- Analyser le comportement réseau pour améliorer FCP et LCP
- Optimiser les images sur le web : checklist des bonnes pratiques
- Optimiser le chargement des fonts
- Optimiser la gestion des icĂ´nes
- Comment diagnostiquer et corriger du Cumulative Layout Shift (CLS) ?
- Comment alléger son JavaScript (vous êtes ici)
- Quelles métriques suivre en dehors des Core Web Vitals ? (bientôt)
- Et plus encore đź«¶
Comprendre comment le JavaScript impacte votre site
La première chose à faire est de comprendre où vous en êtes. Pour cela, le mieux est de comprendre ce que vit votre audience en mettant en place du monitoring avec des outils tels que SpeedCurve, RUMVision, etc.
Cela vous permettra d’avoir une meilleure vision du genre de périphériques et de connexions qu’utilisent les vraies personnes qui naviguent sur votre site. En effet, chaque audience est différente et aura en tout cas des devices très différents de votre PC de dev qui est sur une connexion fibrée.
Si vous n’avez pas accès à ces outils, le plus simple est de passer par PageSpeed Insights qui, au niveau de l’audit Lighthouse, vous donnera deux informations :
-
C’est le temps passé pendant lequel le navigateur est complètement freeze : vous ne pouvez plus interagir avec le navigateur parce qu’il est occupé à parser et executer du code. Idéalement, il faudrait donc que ce temps soit < 200ms pour offrir une bonne expérience à vos utilisateurices. Cependant, ceci n’est qu’un symptôme et il est plus intéressant de mesurer l’INP qui est la conséquence. Je vous en reparlerai dans un futur article.
-
Des Opportunités et Diagnostics tels que “Réduisiez les ressources JavaScript inutilisées”, “Réduisez le travail du thread principal” ou “Réduisez le temps d’exécution de JavaScript”
Afin de corriger un TBT trop long, généralement le souci se trouve au niveau du JavaScript et c’est pour cette raison que Lighthouse met en avant ces pistes. Ce sont elles que nous allons explorer dans cet article.
Pourquoi est-ce le JavaScript qui est à l’origine d’un TBT élevé ?
Premièrement, ce n’est pas théoriquement vrai : avoir une quantité importante de DOM ou du CSS très complexe peut aussi impacter votre TBT. Mais dans la pratique, sur l’extrême majorité des sites que j’ai audité, c’est à 90% (chiffre sorti du chapeau) lié au JavaScript.
Prenons l’exemple de cette trace d’un site e-commerce en React :
On peut constater que c’est la couleur jaune qui prédomine l’utilisation du CPU dans le graphique du haut : ça veut dire que c’est le Scripting, autrement dit, le JavaScript.
Et les bandes rouges et hachurées que l’on retrouve au niveau du flame chart, montrent que c’est lors de l’exécution du JavaScript qu’il y a des Long Frames.
En effet, en grande partie les navigateurs sont conçus pour exécuter le code relatif à votre page sur un seul thread : cela veut dire que si le navigateur est occupé à quelque chose, alors il ne pourra pas faire autre chose en attendant. Il faudra attendre que la tâche en cours soit finie pour passer à la tâche suivante.
Dans le cas montré ci-dessus, il faut attendre que React ait fini de se charger pour pouvoir faire autre chose. On le repère grâce au t.hydrate dans la zone cyan du flamechart. Cela représente 2s de bloquage (2094ms). Pendant ce temps là , rien d’autre ne peut être fait : si l’utilisateurice clique quelque part, il ne se passera rien, si une image a fini d’être téléchargée, elle ne pourra pas être affichée.
C’est d’autant plus gênant que sur cette trace en particulier, l’affichage du LCP est bloqué tant que le JavaScript ne s’est pas exécuté. Donc non seulement cela gêne l’interactivité mais cela peut aussi gêner l’affichage du contenu.
Avant d’aller plus loin, je vous invite à essayer de voir si vous avez la même problématique sur votre site. Ces fonctionnalités sont disponibles dans la majorité des navigateurs, mais voici comment faire avec les DevTools de Chrome :
- Ouvrez vos DevTools
- Allez dans l’onglet Performance
- Activez le mode CPU: 4x slowdown afin d’être plus proche du comportement des mobiles milieu/bas de gamme
- Lancez l’audit de performance en déclenchant un rafraîchissement de page
Vous aussi vous avez trop de JavaScript ? Alors voyons comment régler cela.
Choisir la bonne solution en fonction de votre contexte
Nous savons donc que nous avons trop de JavaScript. Il nous reste donc à choisir entre plusieurs solutions. En matière de performance vous avez souvent trois pistes à explorer :
- optimiser automatiquement
- optimiser en faisant moins
- optimiser en faisant au bon moment
Optimiser automatiquement
Optimiser en compressant et en mettant en cache vos assets
La première chose à faire, parce que c’est le meilleur ratio effort/performance, est de vérifier que vos assets sont correctement servis par votre serveur :
-
Sont-ils correctement encodés ?
En effet, il existe des méthodes de compression qui permettent de diminuer drastiquement le bande passante utilisée sur votre réseau. Généralement on va parler de GZip ou de Brotli. Pour cela, vous pouvez aller dans l’onglet Network de votre site en production et vérifier que dans les
Response Headers, vous avezContent-Encodingavec la valeurgzipoubr. Dans le cas de mon fichierapp.f6bd4147.jsci-dessous, cela permet à l’utilisateur de ne télécharger que 4.2kB alors que la taille réelle du fichier est de 28.4kB, soit -85% ! -
Sont-ils mis en cache ?
Les fichiers JavaScript sont souvent réutilisés de page en page. Il est donc pertinent d’indiquer au navigateur qu’il peut les mettre en cache pour éviter de les retélécharger à chaque nouvelle page. Pour cela, vous pouvez contacter votre admin sys pour ajouter le header
Cache-Control: public, max-age=31536000, immutablesur vos assets (CSS, JS, fonts, images). Attention cependant, cela requiert d’avoir une stratégie de cache busting. Vérifiez donc que si votre JS change, le nom de vos fichiers aussi.
Optimiser en utilisant un bundler
Lorsque l’on écrit du JavaScript, nous allons écrire notre code de sorte qu’il soit facilement maintenable. Cela veut dire que l’on va utiliser des noms de variables compréhensibles par un·e humain·e, séparer notre code en plusieurs fichiers, etc.
Mais le navigateur, lui, n’a pas besoin de tout ça. Si votre variable s’appelle a ou firstnameFormLabel, le code fonctionnera tout aussi bien. Si votre code est rassemblé dans un seul fichier plutôt que de devoir les télécharger un par un, idem.
Nous avons donc des outils qui nous permettent de faire cela pour nous. Les plus connus à ce jour sont webpack, Vite ou Parcel. De plus, si vous utilisez un Meta Framework JS tel que Next.js, Nuxt, SvelteKit, cette partie sera déjà gérée pour vous.
Ces outils vont avoir pour responsabilité de :
- minifier automatiquement votre code en utilisant des variables plus courtes, supprimer les espaces inutiles, etc.
- fusionner vos fichiers (ou bundler) pour optimiser la manière dont ils sont chargés dans le navigateur
- gérer automatiquement le cache busting en ajoutant un
hashau nom de vos fichiers - transformer votre JS moderne en une version comprise par les navigateurs en transpilant vos fichiers via @babel/preset-env
- fournir un environnement de dev pratique, tout en s’assurant qu’il ne déborde pas sur l’environnement de production
Pour s’assurer que vous avez un tel outil en place, vous pouvez faire les vérifications suivantes sur votre site en production :
-
ouvrir l’un des fichiers JS en production dans son propre onglet : il devrait ressembler à ceci
-
vérifier que les fichiers JS ne sont pas chargés en cascade (leur téléchargement commence au même moment) et qu’il y en a relativement peu par rapport à votre nombre de fichiers (une analyse de l’équipe de Next.js a montré que 25 fichiers max est une bonne règle à avoir en tête)
💡 Ne regardez pas le temps de chargement sur les catpures ci-dessous. Elles ne sont pas effectués sur le même réseau. Ce qui est important de voir, c’est la quantité de fichiers différents et l’effet d’escalier visible quand il y a cascade. En production, cela aura un effet désastreux.
Avec Cascade
Sans Cascade
Si vous constatez que l’un de ces points n’est pas respecté, cela vaut le coup de vous pencher dessus parce que c’est certainement ce qui aura le plus fort impact à l’échelle globale de votre site. Mon conseil aujourd’hui est de partir sur Vite qui a l’avantage d’être beaucoup plus performant tout en étant suffisamment mature pour répondre à l’extrême majorité de vos besoins. N’hésitez pas à me contacter si vous avez besoin d’aide à ce sujet.
Optimiser en faisant moins
La prochaine étape, quand vous aurez mis en place les optimisations automatiques, est de faire un état des lieux de votre code. En effet, si votre site existe depuis plus d’un an, il y a de grandes chances que son contenu ait évolué au cours des mois et des années.
Vous vous rappelez ce carousel que vous aviez mis en place pour Noël dernier ? Il a peut être disparu de votre site. Mais pour autant il n’est pas impossible que le code nécessaire à son fonctionnement soit toujours disponible.
Dans cette situation, il est pertinent de passer par 2 étapes :
-
Faire un audit de Coverage dans Chrome : au chargement de la page le navigateur est en mesure de savoir quelle partie du code a été réellement exécutée ou non.
Si vous constatez une trop forte concentration de rouge (Unused Bytes), c’est certainement que vous avez des choses à retirer. N’essayez toutefois pas de viser à tout prix 100% d’utilisation : en effet, vous pourriez dégrader la performance ressentie à l’interaction. Mais plus le pourcentage de code inutilisé est grand, plus il y aura quelque chose à explorer. Les valeurs absolues sont aussi un bon indicateur. 20kB de JS non utilisé n’aura pas le même impact que 500kB.
💡 Certains frameworks JS sont optimisés pour répondre à cette métrique en particulier. Si cela vous intéresse, n’hésitez pas à creuser du côté de Qwik et notamment son concept de “Lazy execution”.
-
Analyser les fichiers générés pour comprendre de quoi ils sont composés via
source-map-explorer :npx source-map-explorer build/*.js
💡 Attention à bien vérifier que vous avez généré des source maps au moment du build de vos dépendances, sinon cela sera illisible et il sera plus pertinent de passer par les bundle analyzer mentionnés plus bas.
Cet outil permet de refaire une carte de vos fichiers à partir des sources maps. Cela permettra de faire ressortir certaines dépendances particulièrement imposantes. C’est par exemple le cas dans mon screenshot avec le fichier en haut à gauche (
nodes/25.bbfa73a6.js) qui est composé de 344kB de moment.js. C’est gigantesque.Pourtant, aujourd’hui, ce n’est utilisé qu’à un seul endroit de mon code et ça peut maintenant être remplacé par des alternatives plus modernes (exemple : Intl.RelativeTimeFormat).
Au début cela vous demandera un peu de temps pour fouiller dans les dépendances, mais avec l’habitude ce sera un excellent moyen de découvrir certaines problématiques en un coup d’œil.
Si vous utilisez, webpack n’hésitez pas à creuser du côté de webpack-bundle-analyzer ou du côté de vite-bundle-visualizer si vous êtes sur Vite. Ce sont des outils qui font la même chose mais de manière simplifiée. Cependant, ils sont moins versatiles et ne m’ont pas permis d’afficher le bundle client avec SvelteKit par exemple.
Ainsi, grâce aux deux étapes sus-mentionnées, vous aurez une vision par page (grâce au Coverage) et globale (grâce aux source-map-explorer). Gardez toutefois en tête qu’on parle de code inutilisé ou trop lourd ici. Mais parfois la solution pourrait être différente :
- analyser le comportement de vos utilisateurices afin de savoir quelle partie de la page pourrait être supprimée (ex : la 8ème section de votre page d’accueil qui n’existe que pour des raisons politiques (Loi de Conway) et non parce qu’elle est utile à vos utilisateurices)
- Trouver des alternatives qui demandent moins de JS (ex : CSS Scroll Snap vs une librairie de Carousel)
- mettre à jour un framework pour profiter de ses dernières optimisations (ex : les React Server Components et le dossier app de Next.js)
Optimiser en faisant au bon moment
Enfin, parfois il n’est pas réellement possible de faire moins : la 8ème section de la page d’accueil a en fait de la valeur et est importante. Cependant, il est important de se demander : quand est-elle importante ?
En effet, quand on parle de performance, on peut diminuer les ressources nécessaires, mais on peut aussi les prioriser : choisir ce que le navigateur doit faire en premier et différer ce qui peut être fait plus tard.
Pour cela, nous aurons globalement 2 options :
- séparer le JavaScript par page
- importer le JavaScript au moment oĂą on interagit avec la page
Séparer le JavaScript par pages
De plus en plus de sites aujourd’hui sont des SPA, cela veut dire que les pages ne sont pas rechargées complètement à chaque fois qu’on clique sur un lien. Le risque est alors de charger tout votre JavaScript d’un seul coup, y compris pour la page de FAQ cachée derrière trois liens.
La plupart des meta frameworks (Next, Nuxt, SvelteKit, etc.) le gèrent pour vous. Je ne rentrerai donc pas plus en détail sur ce sujet. Mais si votre site n’en utilise aucun, il est bon de vérifier le taux de Coverage de votre site.
Importer le JavaSript à l’interaction
Une fois le code séparé de page en page, il est bon de vérifier le comportement à l’intérieur d’une page.
Prenons l’exemple d’une modale : de quoi avons-nous besoin pour qu’elle puisse fonctionner ? Dans quelques temps, quand le support navigateur sera suffisamment répandu, nous pourrons utiliser <dialog>. Mais en attendant il nous faut toujours le gérer manuellement :
- tout d’abord il faut écouter le clic sur le bouton d’ouverture
button.addEventListener('click', () => { openModal(modal); }); - puis il faut initialiser le code nécessaire à la gestion de la modale
function openModal() { const modal = document.querySelector('.modal'); modal.focus(); modal.querySelector('.close').addEventListener('click', () => { closeModal(); }); // etc. }
Mais cette deuxième partie n’est pas utile tant que la modale n’est pas affichée. C’est d’autant plus vrai que le pattern des modales est généralement utilisé pour des informations secondaires. Ainsi, pour 100 personnes qui affichent votre page, combien ouvrent la modale ? 5% ? Si oui, alors 95% des visites n’ont pas besoin de télécharger openModal (ou tout code nécessaire à son affichage).
Nous allons donc charger celle-ci de manière asynchrone en séparant notre code en 2 fichiers et en utilisant un import() dynamique :
-
openModal.js : qui contient et export la fonction openModalexport function openModal(modal) { // reste du code qui manipule la modale } -
listenModal.js : qui va écouter le click du bouton, et faire l’import dynamique à l’intérieur du listener-import openModal from './openModal.js'; button.addEventListener('click', async () => { + const {openModal} = await import('./openModal.js'); openModal(modal); });
La différence est donc qu’on va lancer l’import non pas au chargement du fichier, mais uniquement au moment où on en a besoin. Ainsi plutôt que de faire beaucoup de choses au début, on diffère l’execution à plus tard pour laisser la place à d’autres choses plus importantes.
đź’ˇ Vous remarquerez que je parle de
chunk : en fait, votre bundler (webpack, Vite, etc.) va être responsable de rassembler vos fichiers pour en diminuer le nombre. En utilisantimport x from './x.js'(import statique), vous indiquez au bundler que tout le javascript peut être dans un seul fichier, un seul chunk. En utilisant unimport('./x.js')(import dynamique), vous lui indiquez où se trouve la limite et comment il doit séparer les fichiers.
⚠️ Attention toutefois, ce n’est pas une recette magique, cela vient notamment avec 2 problématiques :
-
que se passe-t-il si, au moment du click, le navigateur a perdu sa connexion internet ? Il va être pertinent de gérer le cas d’erreur. Cela peut être en affichant une erreur ou tout simplement en s’assurant que la personne puisse relancer l’action plus tard.
button.addEventListener('click', async () => { let openModal; try { const module = await import('./openModal.js'); openModal = module.openModal; } catch (e) { // gestion en cas d'erreur de connexion ici // puis early return pour éviter d'ouvrir la modale return; } // Je ne mets pas `openModal` dans le try/catch parce que si // l'erreur vient d'openModal et non de l'import alors il faudra // la traiter différemment. Pensez donc à adapter le code à // votre usage. openModal(modal); }); -
que se passe-t-il pendant le chargement du fichier openModal.js ? Potentiellement, il peut prendre quelques millisecondes, ou quelques secondes s’il y a des ralentissements réseau. Il est alors pertinent de gérer des états de chargement (spinner, skeletons, etc.). En effet, rien de plus énervant qu’un bouton qui ne fait rien et se réveille au bout de quelques secondes.
Comment je fais ça dans mon framework front ? (React, Svelte)
Dans l’exemple ci-dessus, je suis revenu au concept initial, en Vanilla, mais comment ça marche pour les stacks modernes ?
-
React : l’idée va être d’utiliser
lazypour importer son composant plutôt que de l’importer statiquement. Cela peut ensuite se gérer soit avecuseTransition, soit avecSuspense. Ici j’ai utiliséuseTransitionparce que je veux changer l’affichage de mon bouton pendant qu’il est en train de charger.isPendingsera ainsitruetant que le fichier Modal.jsx sera en cours de téléchargement. J’ai ajouté par ailleurs react-error-boundary pour pouvoir réagir en cas d’erreur réseau au chargement de la Modal.import { lazy, useState, useTransition } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; const Modal = lazy(() => import('./Modal.jsx')); export default function App() { const [isPending, startTransition] = useTransition(); const [opened, setOpened] = useState(false); function openModal() { startTransition(() => { setOpened(true); }); } function onLoadError() { // gestion en cas d'erreur de connexion ici } return ( <> <button onClick={openModal}>{isPending ? 'Loading' : 'Open'}</button> {opened && ( <ErrorBoundary onError={onLoadError}> <Modal /> </ErrorBoundary> )} </> ); } -
Svelte : Svelte n’a pas de concept de lazy component à ce jour, mais gère les promesses nativement. Nous allons donc pouvoir mettre à disposition une promesse qui contiendra le composant et qu’on pourra utiliser à l’intérieur de
{#await}.<script> let lazyModalComponent; let isPending = false; function openModal() { isPending = true; lazyModalComponent = import(`./Modal.svelte`); lazyModalComponent.catch((error) => { // gestion en cas d'erreur de connexion ici }).finally(() => { isPending = false; }); } </script> <button on:click={openModal}> {#if isPending} Loading {:else} Open {/if} </button> {#if lazyModalComponent} {#await lazyModalComponent then { default: Modal }} <Modal /> {/await} {/if}
Importer le JavaScript quand le composant est visible
Nous avons vu comment charger une partie du code au click. Mais les interactions peuvent être bien plus variées. Reprenons l’exemple de la 8ème section de la page d’accueil. A priori, son JavaScript pourrait être téléchargé qu’au scroll : quand on va arriver sur la 8ème section.
Pour cela, nous pourrons utiliser un IntersectionObserver.
Ainsi, plutôt que d’écouter le click, nous observerons la visibilité :
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(() => {
if (entry.isIntersecting) {
import('./initSection')
.then((module) => {
// Code d'initialisation du composant
module.initSection();
})
.catch((error) => {
// Toujours penser à gérer le cas d'erreur ici
});
// Vu qu'on a géré le chargement de la section, il n'y en
// aura plus besoin la prochaine fois que l'élément
// devient visible
observer.remove(section);
}
});
},
{
// Définir une marge suffisamment grande pour que le
// composant ait le temps de s'initialiser avant
// d'arriver sur la section
margin: '500px'
}
);
observer.observe(section);
Cela peut être intéressant à faire notamment pour tout calcul coûteux que vous auriez à faire (calcul de positions, initialisation d’une vidéo, etc.).
Preload
L’IntersectionObserver peut aussi être utilisé pour faire du preloading. En effet, dans celui-ci vous n’êtes pas obligé de déclencher une action. Vous pouvez uniquement déclencher l’import.
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(() => {
if (entry.isIntersecting) {
import('./openModal');
observer.remove(button);
}
});
},
{ margin: '500px' }
);
observer.observe(button);
Ainsi, dans cet exemple, on ne déclenche l’import de openModal qu’à partir du moment où le bouton est visible. Cela donne le temps au navigateur de télécharger le JS afin qu’au moment du click, celui-ci soit déjà en mémoire et n’ait plus qu’à s’exécuter.
C’est un excellent moyen de diminuer les temps de chargement.
💡 Si vous utilisez un framework, sachez qu’il existe de nombreuses librairies sur npm qui l’encapsuleront pour vous. Mais il peut être un bon exercice de le coder vous même, car cela vous permettra de mieux comprendre son fonctionnement et vous retirera une dépendance externe. N’hésitez pas à m’envoyer un message si vous avez besoin d’un coup de main.
Veillez cela dit à prendre en compte le paramètre navigator.connection.saveData afin que les personnes qui préfèrent économiser leur bande passante ne téléchargent pas plus que nécessaire.
De plus, en voulant précharger trop de choses, on peut tomber dans l’effet inverse : le navigateur se retrouve surchargé à préparer l’avenir sans pouvoir exécuter ce dont il a besoin maintenant. Soyez donc sélectif sur ce que vous voulez précharger. Une autre piste est de creuser du côté de la différence entre preload et prefetch.
Enfin, sachez que la visibilité n’est pas la seule solution pour savoir quand faire un preload. Vous pourriez par exemple imaginer utiliser le mouseenter (bien que celui-ci ne soit pas disponible sur mobile). N’hésitez donc pas à chercher la solution la plus adaptée à votre contexte.
Récapitulatif
Si vous avez réussi à tout lire, bravo ! 🎉 Si j’ai bien fait mon boulot, vous devriez maintenant être paré à commencer vos travaux d’optimisation.
Pour cela, gardez en tête les étapes suivantes :
- Le JS est souvent coupable de lenteurs de chargement et de freezes sur vos pages. Vérifiez cela en exécutant un audit
Performancedans vos DevTools. S’il y a beaucoup de jaune/scripting, c’est mauvais signe. - Vérifiez que vos fichiers JS ont été correctement optimisés : compressés, mis en cache et rassemblés en un minimum de fichiers.
- Vérifiez que vous n’avez pas de code mort/inutilisé avec l’audit de Coverage et en vérifiant le poids de vos dépendances via
source-map-explorer. Essayez de toujours préférer l’utilisation du natif quand c’est possible. - Utilisez des
import()dynamiques afin d’organiser votre code et ne télécharger vos fichiers que lorsqu’ils sont réellement nécessaires. Cela peut dépendre d’une page, d’une interaction (click) ou de la visibilité (IntersectionObserver).
Dans tous les cas, c’est un travail long et fastidieux notamment parce qu’il faut y réfléchir au cas par cas. De plus, une régression est vite arrivée. Si vous avez donc besoin d’aide sur ces sujets, n’hésitez pas à me contacter. Peut-être puis-je vous aider, vous et votre équipe, à mettre en place ces bonnes pratiques en matière de performance.
Enfin, si cet article vous a intéressé, n’hésitez pas à le partager autour de vous. Je publie régulièrement des articles autour des performances et du développement web. Par exemple, semaine prochaine, je devrais vous parler d’astuces sur les Scroll Driven Animations. Stay tuned! 👀
Si vous voulez suivre mes publications, il paraît que j'ai un feed RSS, Mastodon et un Twitter.
Si vous pensez à d'autres méthodes que vous voudriez que je mette en place (pigeon voyageur, avion en papier, etc.), n'hésitez pas à me les proposer :)