Introduction

Comme vous le savez, depuis 2018 je ne cesse de prôner l'utilisation de Flutter pour tous vos développements d'applications mobiles et mêmes desktop.

Dès les premières versions de Flutter Web en 2019, je me suis demandé quand je pourrais l'utiliser pour réaliser un site web professionnel. Il était donc naturel que mon site web soit écrit en Flutter Web.

Cet article est un retour d'expérience concret et neutre dans lequel je fais part des points positifs, des points qui requièrent une attention toute particulière mais également de points négatifs.

Je terminerai par une série de recommendations afin de vous permettre de prendre une décision quant à l'utilisation ou non de Flutter Web pour un site web.


Code unique?

Bien entendu, la première question qui vient naturellement à l'esprît est "Puis-je réutiliser mon code Flutter initialement conçu pour mon application mobile tel quel?".

La réponse est oui et non. Aujourd'hui la plupart des librairies supportent assez bien les particularités Web vs Desktop vs Mobile et vous pouvez généralement les utiliser telles quelles sans vous préoccuper de la plateforme. Dès lors, vous pouvez facilement compter sur une compatibilité de code proche de 95%.

Néanmoins, une application Web n'est pas une application mobile et dès lors, vous aurez besoin de considérer certaines particularités propres aux navigateurs et la disponibilité de certains features.

Commençons à parcourir ces différences...


PWA vs Site Web

Le premier point que je tenais à mentionner est que Flutter Web ne génère PAS (naturellement) un site web mais une Web App!

Quelle différence me direz-vous?

Sans considérer le WebAssembly qui est toujours expérimental à ce jour (Avril 2024)[on le verra plus tard], la différence est que lorsque vous accédez à un site, développé en Flutter Web, votre browser doit télécharger une application entière avant de l'exécuter.

Suivant la taille et la complexité de votre application web et de la qualité du réseau de l'utilisateur, cela peut entraîner un temps de chargement non négligeable avant l'affichage de quoi que ce soit. Ceci peut entraîner une mauvaise image/réputation de votre site.

Afin de faire patienter les visiteurs, vous pouvez toujours afficher une petite animation qui est directement lancée dès que le premier code HTML est chargé. N'empêche que votre visiteur devra toujours attendre...

Une fois que l'application est lancée, vous n'avez quasi plus à accéder à votre serveur SAUF si vous entrez une URL directement dans la barre du navigateur. A ce moment, cela revient à retélécharger l'application et à la réexécuter. Grâce au cache de votre navigateur, ce sera plus rapide.


WebAssembly (Wasm)

L'introduction du support du WebAssembly par Flutter, ouvre la porte à de nouvelles perspectives et améliorations.

Pour résumer, le WebAssembly (Wasm) est une norme qui permet de compiler des langages de programmation de haut niveau, y compris Dart, pour s'exécuter dans un navigateur web avec des performances élevées.

Cela permet également de compiler des parties de code en Dart, de les télécharger à la demande et de les exécuter lorsque l'on en a besoin.

Une des améliorations notables est que l'application web à télécharger initialement est très fortement réduite (donc plus rapide à télécharger). Au fur et à mesure des besoins de l'application web, des modules Wasm peuvent être téléchargés dynamiquement et exécutés.

Alors c'est la solution?

Pas tout à fait, malheureusement (je rappelle que nous sommes en avril 2024 au moment où j'écris ces lignes...). Alors pourquoi?

  • La plupart des principaux browsers (Chromium: Chrome, Opera, Edge) supportent Wasm, néanmoins Safari ne supporte pas encore WasmGC (WebAssembly Garbage Collection)[voir le ticket];

  • Beaucoup de packages utilisent soit dart:html ou package:js. Ces packages ne peuvent pas être compilés vers Wasm. Ceci restreint l'utilisation de beaucoup de packages;

  • Surtout: pour le moment ni flutter run, ni DevTools ne supportent Wasm. Ceci rend le développement et débogage assez complexes;

Néanmoins, si vous réussissez à n'utiliser que des packages compatibles et que la compilation réussit, il faut avouer qu'il n'y a aucun doute, votre application est nettement plus petite, charge très vite et tourne beaucoup plus vite. Vous pouvez voir un exemple de site qui utilise Wasm https://flutterweb-wasm.web.app

Bref... à suivre.

Revenons maintenant à mon retour d'expérience...


Réactivité (responsiveness)

La première différence directement visible est que votre application doive supporter un layout dynamique dû au redimensionnement du navigateur.

Ceci peut-être réalisé en utilisant des Widgets de base, tels que Flexible, Wrap, ... mais dans la plupart des cas, cela revient à modifier le comportement de l'application via du code source. L'utilisation de packages spécialisés tels que flutter_bootstrap ou équivalent qui fournissent des solutions basées sur des grilles et tendent à mimiquer des frameworks javascript/CSS connus, est déjà une grande aide.

L'utilisation de MediaQuery, LayoutBuilder,... vous permettent également de manipuler l'affichage en fonction des dimensions et/ou orientation, tel que @media vous le permet en CSS.

Mon expérience est que, tout comme vous devez le faire pour un site web normal, cela requiert pas mal de temps afin que votre layout réponde correctement aux variations des dimensions de votre browser.

Une des plus grosses difficultés que j'ai rencontrées a résidé dans le redimensionnement des textes de manière dynamique car je désirais les animer et les positionner de manière précise, or, comme vous le savez, le dimensionnement d'un Widget child (et en particulier la détermination de la taille de la police de caractères d'un texte qui peut ou non passer à la ligne suivant toutes les contraintes ...), par rapport aux dimensions de son parent direct ou indirect, n'est pas toujours quelque chose de facile à réaliser.

Bref, bien que cela prenne davantage de temps par rapport au développement d'une application mobile, c'est très faisable.

Néanmoins, comme j'y reviendrai plus tard, vous devez faire beaucoup plus attention à optimiser votre code et limiter le nombre de build!


Hot Reload

Une des plus grandes fonctionnalités de Flutter est le Hot Reload qui vous permet de modifier votre code source et de voir directement la modification sans devoir tout relancer.

Flutter Web n'a pas de fonction de Hot Reload (Avril 2024) mais supporte le Hot Restart, ce qui est déjà pas mal mais à chaque fois, l'application redémarre.

J'ai utilisé quelques astuces afin de gagner le plus de temps possible telles que faire en sorte que la page sur laquelle je travaillais soit affichée en tant que "home" et de simuler des données, le temps que son layout soit plus ou moins terminé. Néanmoins, il y a toujours des modifications à effectuer par la suite et il n'est pas toujours possible d'utiliser cette astuce.

Bien que cela puisse ne pas sembler être très important, la pratique dit tout le contraire et la conséquence directe est un allongement du temps de développement et de maintenance.


Performance des Navigateurs sur smartphone vs desktop

Lors du développement initial, j'ai bien naturellement utilisé mon browser habituel (Chrome) sur mon gros ordinateur et tout fonctionnait impeccablement. Tout était fluide. J'utilisait les "developers tools" afin de simuler les différents modèles et dimensions.

Quelle surprise lorsque j'ai effectué une pre-release et ai utilisé pour la première fois mon iPhone... Les animations étaient saccadées, les scrollings ultra lents, certaines images ne s'affichaient pas... J'ai alors utilisé un modèle Android et c'était encore pire...

Quelle désillusion ! C'est à ce moment que j'ai pleinement réalisé qu'il existait un monde de différence entre la puissance entre mon ordinateur de développement et celle des smartphones et que l'interprétation du Javascript était beaucoup plus lente sur les téléphones...

J'ai dès lors été obligé de revoir une grosse partie du code et d'optimiser davantage les rebuilds et notamment les animations.

Le premier Widget qui m'a été d'une très grande aide est le méconnu RepaintBoundary. Ce widget permet de limiter un repaint à une partie limitée de l'écran, ce qui est idéal dans le cadre d'une animation très localisée ou d'un scrolling.

Le second Widget est le Offstage. L'avantage de ce widget est qu'il n'est pas repeint s'il n'est pas visible (offstage: false). Cela est particulièrement intéresssant lorsque l'on doit empiler les Routes, Overlay, ... Néanmoins, même s'il n'est pas affiché, n'oubliez pas que les animations qu'il pourrait contenir continuent à tourner, ce qui utilise du CPU inutilement. Dès lors, une bonne pratique est d'également stopper les animations lorsqu'il n'est pas visible. Par contre, ceci n'est pas toujours évident à effectuer si vous devez faire une pause dans une animation qui aurait lieu au niveau de l'un des inombrables Widgets de la sous-arborescence... Comment le prévenir et lui demander de stopper/redémarrer? Cela requière une architecture prévue à l'avance.

Depuis cette surprise, je n'ai eu de cesse d'utiliser des DevTools ainsi que différents modèles de smartphones afin de vérifier les repaints et de les optimiser au plus haut point, ce qui devrait être systématiquement fait dans tous les cas de développements.

Cette différence de puissance entre les desktops et smartphones, mais également entre les téléphones iOS et Android, m'ont obligé à adapter les contenus, animations (complexité et quantité) suivant les modèles... un peu dommage.


Pas de HTML, rien que du Graphisme

Le fait que tout ne soit que du Bitmap et non du HTML/CSS est également une donnée importante et une différence essentielle avec le développement d'un site web.

Cela peut paraître évident mais combien de fois lorsque l'on développe un site web, n'utilise-t-on pas le Developers Tools du browser afin de modifier dynamiquement certaines propriétés CSS pour vérifier l'impact sur l'affichage, avant de modifier le code SCSS lui-même?

Comme je le mentionnais précédemment, ceci est tout à fait faisable grâce au Hot Reload mais en développement Flutter Web, le Hot Reload n'existe pas (encore) et les DevTools que nous aimons utiliser pour les développements mobiles ne rendent malheureusement pas les mêmes services pour le web.


Dépendance au rendering HTML

Suite au fait que tout ne soit que graphisme, certaines fonctionnalités habituellement disponibles sur un site web, sont beaucoup plus difficiles à effectuer en Flutter Web.

Pour illustrer ce point, je prendrai 2 exemples concrets.

Cas 1. href "local"

Dans certains cas, vous pouvez avoir besoin de lier certaines parties ensemble. Par exemple, si l'utilisateur clique sur un élément de la page (menu, lien, ...), vous désirez positionner le browser sur l'élément référencé, comme illustré ci-dessous.

<a href="#reference">Appuyez ici</a>
...
<div id="reference">Ceci est le contenu référencé</div>

Pour permettre ceci en Flutter Web, sans utiliser de subterfuges, vous devez connaître la position exacte du Widget référencé dans un Scrollable afin de forcer un scrolling jusqu'à ce Widget.

Dès lors, si votre page est très longue, vous êtes obligé de tout afficher afin d'obtenir la position et dès lors, vous perdez l'avantage d'utiliser des Slivers et devez forcer un cacheExtent très grand. Vous augmentez alors l'utilisation de la mémoire.

Cas 2. rendu dynamique d'un HTML au milieu d'une page Flutter

Concrètement, considérez avoir à afficher un Gist au milieu d'un contenu purement écrit en Flutter.

Pour effectuer cela, vous allez insérer du code HTML via un HtmlElementView et une fois inséré dans la page, un code javascript effectuera un appel vers Github pour récupérer le contenu et effectuer le rendering.

Comme ce rendu est asynchrone et la hauteur du contenu HTML est également dynamique, vous devez trouver une astuce afin d'adapter votre page Flutter en conséquence, provoquant des redimensionnements. Cependant, durant toute cette opération, votre page est visible et peut donner une mauvaise impression aux visiteurs (overlap, ...).

De plus, afin d'augmenter la difficulté, l'utilisateur peut décider de redimensionner son navigateur, ce qui peut entraîner une modification de la hauteur de votre HTML dynamique et dès lors demander à redimensionner le container en Flutter, ... Bref, pas toujours très facile à réaliser ni à synchroniser.


Memory Leak

Ma plus grosse surprise a été liée au problème de memory leak relatif aux images.

Initialement, mon site utilisait pas mal d'images qui s'affichaient en séquence, utilisant des animations. Quelle que soit la méthode, les paramètres (cacheWidth/cacheHeight, enableMemoryCache, ...) et/ou packages utilisés, j'avais systématiquement un problème de memory leak au niveau des images.

Au lancement de l'application dans le browser, cela ne s'aperçoit pas mais après avoir laissé tourner l'application un certain temps, je remarquais une baisse de fluidité des animations jusqu'à obtenir le fameux écran "aïe", le browser a crashé. Ceci était d'autant plus systématique et rapide à reprenduire sur Firefox.

Sur Android, c'était très facile à visualiser tant la fluidité des animations se dégradait assez vite.

J'en suis arrivé à devoir créer mon propre cache et à chaque fois que j'avais besoin d'une image, à pointer vers l'unique instance de mon cache. Afin de permettre la libération ressources des images non nécessaires, j'utilise alors des WeakReference et Finalizer. Ceci m'a permis d'améliorer grandement les choses. Cependant, je ne pouvais plus réellement utiliser les Widgets de base "tels quels". Dommage.


Une autre source de memory leak est liée à une mauvaise utilisation du BuildContext. Pour rappel, évitez de passer un context en tant qu'argument SAUF si vous êtes à 100% certain qu'un Widget auquel vous passez le context ne survivra PAS au Widget dont le context appartient.


Browser Security & CORS

Lorsque vous développez une application web qui nécessite effectuer des appels vers d'autres URLs, vous devez prévoir des refus de type: CORS policy.

Durant la phase de développement, la solution est très simple et consiste à effectuer les opérations suivantes sur votre poste de travail:

Dans le répertoire "%FLUTTER%\bin\cache", supprimez le fichier "flutter_tools.stamp".
Dans le répertoire "%FLUTTER%\packages\flutter_tools\lib\src\web", éditez le fichier "chrome.dart" comme ceci:
  1. Localisez la ligne "'--disable-extensions',"
  2. Ajoutez la ligne suivante directement en-dessous: "'--disable-web-security',"

Par contre, une fois que votre serveur tourne en production, c'est un peu plus difficile et pour résoudre ce problème, j'ai utilisé un serveur nginx en tant que proxy mais j'y reviendrai un peu plus tard.


SEO

Et voici le plus gros problème... Comment référencer un site web lorsque celui-ci n'est que bitmap?

Il existe quelques packages qui tentent de résoudre ce problème et qui permettent d'ajouter dynamiquement du code HTML invisible à votre page.

Bien que cela fonctionne, c'est une pratique à ne pas recommander. Cela s'appelle du Cloacking et c'est considéré comme une tentative de fraude par la plupart des Crawlers.

Pourquoi?

Tout simplement, parce que les Crawlers n'ont aucun moyen de valider que ce HTML ajouté est effectivement le véritable contenu, visible par vos visiteurs.

La conséquence directe est que fréquemment, votre "ranking" en sera, au mieux, pénalisé si pas banni par les moteurs de recherche, au pire.


Alors comment faire?


Nginx (ou autre) à la rescousse

Pour résoudre les problèmes CORS et SEO, j'ai utilisé comme serveur web: Nginx.

Note: J'aurais tout aussi bien pu utiliser: Apache, Caddy, Ligttpd, HAProxy, .... mais je connaissais mieux Nginx.

Résolution du problème CORS

Pour permettre l'accès à une URL pointant vers https://gist.github.com, j'ai défini une règle de type "proxy inverse" (Reverse Proxy) comme suit:

1location /github-proxy/ {
2  rewrite "^/github-proxy/(.*)" /$1 break;
3  proxy_pass https://gist.github.com;
4  proxy_set_header Access-Control-Allow-Origin *;
5  proxy_set_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
6  proxy_set_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
7  proxy_set_header Access-Control-Expose-Headers 'Content-Length,Content-Range';
8}

Ainsi, toute invocation d'une URL de type: https://www.flutteris.com/github-proxy/... est relayée vers https://gist.github.com/... en ajoutant à la requète tous les headers corrects afin de ne pas être refusée par GitHub.

Résolution du problème SEO

Pour permettre à mon site d'être correctement référencé par Google, Bing, ... j'ai dû être davantage créatif.

La solution réside en 2 étapes:

  • Etape 1: Génération des fichiers HTML statiques.
  • Etape 2: Stockage des fichiers HTML dans un repository statique sur votre serveur web.
  • Etape 3: Détecter les Bots et leur retourner les fichiers HTML statiques au lieu de l'application... via le serveur web.
  • Etape 4: Génération d'un fichier sitemap.xml et l'envoyer aux Bots.
  • Pour l'étape 1, rien de plus simple...

    Un simple générateur écrit en Dart qui, pour chaque page du site, génère un fichier .html sur base d'un fichier de définition.

    Le résultat est un fichier .html qui n'a pas besoin d'être parfaitement formatté (car ne sera jamais affiché aux utilisateurs) mais qui à la forme suivante:

    <!DOCTYPE html>
    <html lang="${language}">
      <head>
        <base href="/">
        <meta charset="UTF-8">
        <meta content="IE=Edge" http-equiv="X-UA-Compatible">
        <title>${seoMetaData.title}</title>
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        <meta name="apple-mobile-web-app-title" content="Flutteris">
        <meta name="author" content="Didier Boelens">
        <meta name="keywords" content="${seoMetaData.keywords}">
        <meta name="description" content="${seoMetaData.description}">
        <meta property="og:title" content="${seoMetaData.title}">
        <meta property="og:type" content="${seoMetaData.type}">
        <meta property="og:url" content="${seoMetaData.url}">
        <meta property="og:site_name" content="${seoMetaData.siteName}">
        <meta property="og:description" content="${seoMetaData.description}">
        <meta property="og:image" content="${seoMetaData.imageUrl}">
        <meta property="twitter:card" content="summary">
        <meta property="twitter:creator" content="${seoMetaData.author}">
        <meta property="twitter:title" content="${seoMetaData.title}">
        <meta property="twitter:description" content="${seoMetaData.description}">
        <meta property="twitter:image" content="${seoMetaData.imageUrl}">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <style>
          ${cssContent}
        </style>
      </head>
      <body>
        <h1>${seoData.h1}</h1>
        ${seoData.html}
      </body>
    </html>

  • L'étape 2 est la plus simple de toutes. Vous transférez tous vos fichiers HTML générés vers votre serveur web dans un folder. Pour la suite des explications, appelons ce folder: /snapshot


  • L'étape 3 consiste à configurer les règles de redirection pour les Bots & Crawlers.

    Pour illustrer ceci, la première chose à effectuer est de déterminer si la requête provient d'un véritable utilisateur ou d'un Bot/Crawler. Si la requête provient d'un Crawler, on redirige la requête vers la page correspondante stockée en tant que ressource statique dans le folder /snapshot

    La solution que j'ai trouvée est la suivante (basée sur Nginx):

    
    server {
      listen 443 ssl;
      listen [::]:443 ssl;
      server_name www.flutteris.com _;
    
      #
      # Check if request has been issued by a Bot, Crawler (SEO indexing)
      #
      set $is_bot 0;
      if ($http_user_agent ~* "Googlebot|Bingbot|Yandex|Baidu|WhatsApp|DiscordBot|facebookexternalhit|Twitterbot|LinkedInBot|Slackbot|TelegramBot|redditbot|coccocbot") {
          set $is_bot 1;
      }
    
      location / {
          set $root_path /xxxxxxxxxxxxxxxxxxxx/_data;
    
    
          if ($is_bot = 1) {
              set $root_path /xxxxxxxxxxxxxxxxx/www;
    
              # Remove the trailing slash from the URLs
              rewrite ^/(.*)/$ /$1 permanent;
    
              # For URL without any languages
              rewrite "^/$" /snapshot/en/index.html break;
              rewrite "^/blog/?$" /snapshot/blog/en/index.html break;
              rewrite "^/flutter/?$" /snapshot/flutter/en/index.html break;
              rewrite "^/privacy/?$" /snapshot/privacy/en/index.html break;
              rewrite "^/terms/?$" /snapshot/terms/en/index.html break;
    
    
              # For URL without languages
              rewrite "^(/(fr|en)?)?$" /snapshot/$1/index.html break;
              rewrite "^/blog(/(fr|en)?)?$" /snapshot/blog/$1/index.html break;
              rewrite "^/flutter(/(fr|en)?)?$" /snapshot/flutter/$1/index.html break;
              rewrite "^/privacy(/(fr|en)?)?$" /snapshot/privacy/$1/index.html break;
              rewrite "^/terms(/(fr|en)?)?$" /snapshot/terms/$1/index.html break;
    
              # For the URL /blog
              rewrite "^/blog/([^/]+)$" /snapshot/blog/en/$1.html break;
              rewrite "^/blog/(fr|en)/([^/]+)$" /snapshot/blog/$1/$2.html break;
    
              # For the URL /flutter
              rewrite "^/flutter/([^/]+)$" /snapshot/flutter/en/$1.html break;
              rewrite "^/flutter/(fr|en)/([^/]+)$" /snapshot/flutter/$1/$2.html break;
          }
    
          # If this is not a Bot, serve the normal Flutter content
          root $root_path;
          try_files $uri $uri/ /index.html;
      }
      ...
    

  • Pour l'étape 4, toujours via un générateur, on compose un fichier sitemap.xml qui aura la forme suivante:

    <?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      <url>
        <loc>https://www.flutteris.com/fr</loc>
        <lastmod>2023-10-30</lastmod>
      </url>
      <url>
        <loc>https://www.flutteris.com/blog/fr</loc>
        <lastmod>2023-10-30</lastmod>
      </url>
      <url>
        <loc>https://www.flutteris.com/flutter/fr</loc>
        <lastmod>2023-10-30</lastmod>
      </url>
      <url>
        <loc>https://www.flutteris.com/terms/fr</loc>
        <lastmod>2023-10-30</lastmod>
      </url>
      <url>
        <loc>https://www.flutteris.com</loc>
        <lastmod>2023-10-30</lastmod>
      </url>
      ...
    </urlset>

    Maintenant, il nous suffit de fournir ce fichier sitemap.xml afin que le site soit correctement référencé.


    Même si cela fonctionne, cela reste tout de même du bidouillage...


Pourquoi mon site n'est-il plus en Flutter?

Je suppose que vous avez remarqué que mon site n'était plus en Flutter mais que j'en étais revenu à une technologie plus appropriée aux sites web.

Les raisons principales sont les suivantes:

  1. Le temps de chargement était trop lent.

    Comme je l'ai signalé précédemment, ceci pourrait très certainement être résolu grâce à Wasm mais ce n'est pas encore tout à fait prêt ni compatible pour tous les browsers.


  2. J'ai reçu quelques commentaires d'utilisateurs qui me disaient avoir expérimenté des problèmes de Crashes sur certaines versions de browsers.

    Je n'ai jamais moi-même été confronté à ce problème mais je suppose que cela pouvait provenir d'un problème de complexité dans les animations, transitions, ... et mémoire.


  3. L'affichage des contenus hybrides (HTML + Flutter).

    Comme je le signalais plus haut, l'adaptation dynamique les hauteurs des containers Flutter en fonction des dimensions dynamiques réelles de contenu(s) HTML occasionnait des troubles d'affichage et ne donnait pas une impression très professionnelle.


  4. Référencement SEO non naturel.

    Bien que l'astuce que j'ai mise en place fonctionnait, cela n'était qu'une astuce de longévité limitée.



Cela veut-il dire que Flutter Web n'est pas une solution pour un site web officiel et professionnel?

Si l'on se réfère au site officiel de Flutter, vous lirez que Flutter recommande l'utilisation de HTML pour des sites statiques, tout comme le site de Flutter est lui-même écrit de manière conventionnelle.


Conclusion

Je reste convaincu qu'à ce jour, Flutter est le meilleur framework qui permette d'écrire des applications cross-plateformes.

Avec très peu de variations dans le code, vous pouvez obtenir une même application qui tournera sur 99% des smartphones et tablettes, les desktops (Microsoft, Mac, Linux) et même sur Raspberry PI, cela fonctionne très bien.

L'avantage existe véritable.

Au niveau de la notion de web, il est nécessaire de faire la différence entre une WebApp (PWA) et un Site Web.

Si vous avez besoin d'une application web où la notion de référencement et vitesse de chargement ne sont pas critiques, alors Flutter reste une solution à sérieusement considérer.

Par contre, si vous voulez générer un site web professionnel qui sera votre vitrine sur Internet, tout comme mentionné par l'équipe Flutter, je ne vous recommande pas Flutter mais plutôt une solution conventionnelle.

Si maintenant, vous vous lancez dans la conception d'un PWA, la différence de performances entre les browsers qui tournent sur les smartphones et desktop est un facteur à prendre hautement en considération. Cela pourrait entraîner à revoir les ambitions de votre application en termes de complexité et/ou animations.


J'attends cependant avec impatience que le support de WebAssembly soit plus mature...


J'espère que ce retour d'expérience aura pu vous éclairer un peu et répondre à certaines questions.


Restez à l'écoute pour de prochains articles et d'ici-là, happy coding!