~/mesha-vegas$
$ cd ../

2026-07-24

Offline-first : coder pour une connexion qui n'existe pas

En Afrique, la connexion n'est ni continue ni gratuite. Voici comment je conçois des applications qui fonctionnent sans réseau, et pourquoi le mode hors ligne ne peut pas être ajouté à la fin.

  • #offline-first
  • #react-native
  • #expo
  • #afrique
  • #synchronisation
  • #architecture
$8 min de lecture22 lectures
Offline-first : coder pour une connexion qui n'existe pas

Il y a une phrase que j'entends dans presque chaque cadrage de projet : « on gérera le mode hors ligne plus tard ». C'est la même énergie que « on ajoutera les tests après la V1 ». Ça n'arrive jamais, et quand ça arrive, il faut tout réécrire.

Sur Driivio, une plateforme d'apprentissage du code de la route pour le marché camerounais, j'ai fait le choix inverse : l'application a été conçue hors ligne d'abord, en ligne ensuite. Ce n'était pas un choix esthétique. C'était la seule façon d'avoir un produit utilisable.

Le vrai problème n'est pas l'absence de réseau

Quand un développeur européen pense « offline », il pense au métro, au tunnel, à l'avion. Une coupure nette, courte, et le réseau revient.

Ce n'est pas la réalité de mes utilisateurs.

Un candidat au permis à Bafoussam a du réseau. Il a même de la 4G. Ce qu'il n'a pas, c'est des données illimitées. Il achète un forfait, il le consomme, et à J+3 il navigue en 2G ou plus du tout jusqu'à la prochaine recharge. Entre les deux, il y a une zone grise beaucoup plus pénible que la coupure franche : la connexion qui répond en douze secondes, celle qui ouvre le socket puis abandonne, celle qui télécharge 80 % d'une réponse avant de mourir.

Le mode avion est un cas facile : navigator.onLine vaut false, tu affiches un bandeau, terminé. La connexion moribonde, elle, ment. navigator.onLine vaut true. Ta requête part. Elle ne revient pas. Et ton loading tourne dans le vide pendant que l'utilisateur, lui, ferme l'application.

C'est Hunter x Hunter qui donne la meilleure image de ça. Le Nen le plus puissant du manga n'est pas celui qui a le plus de pouvoir brut : c'est celui qui s'impose des conditions et restrictions. Kurapika devient capable de tuer un membre de la Brigade Fantôme précisément parce qu'il accepte de mourir s'il utilise ses chaînes contre quelqu'un d'autre. La contrainte crée la puissance.

Concevoir hors ligne d'abord, c'est s'imposer une restriction : mon application doit être pleinement utilisable sans qu'aucune requête n'aboutisse. Cette contrainte, tenue dès le premier jour, produit une architecture nettement plus solide que si tu l'ajoutes après coup.

Ce que ça change concrètement

La base locale est la source de vérité

Le réflexe habituel : je requête le serveur, j'affiche le résultat, je mets en cache au cas où. Le cache est une optimisation.

Offline-first, on inverse. L'application lit toujours la base locale. Le réseau ne sert qu'à alimenter cette base, en arrière-plan, quand il le peut. L'interface ne sait même pas si tu es connecté — elle lit ce qu'elle a.

Ça a une conséquence agréable : ton application devient instantanée. Pas de spinner sur les écrans principaux, jamais. Le contenu est déjà là. Les utilisateurs qui ont une bonne connexion en profitent autant que les autres.

Les écritures sont des intentions, pas des requêtes

Quand un candidat termine une série de questions, l'application n'envoie pas un POST. Elle écrit une intention dans une file locale : « série 47 terminée, 18/20, à 14h32 ».

L'interface confirme immédiatement. La file se vide quand le réseau le permet, dans l'ordre, avec des tentatives espacées.

Trois règles rendent ça viable :

  • Chaque intention porte un identifiant généré côté client. Sans ça, une réponse perdue après traitement serveur produit un doublon au réessai.
  • Le serveur est idempotent. Recevoir deux fois la même intention doit produire le même état final. C'est au backend de garantir ça, pas au client de « faire attention ».
  • L'horodatage vient du client, mais on garde celui du serveur. Les deux servent : l'un pour l'ordre perçu par l'utilisateur, l'autre pour arbitrer les conflits.

Les conflits existent, il faut choisir sa règle

Deux appareils, un même compte, deux modifications hors ligne. Que garde-t-on ?

Il n'existe pas de bonne réponse universelle, seulement une décision à assumer par type de donnée :

  • Progression d'apprentissage : on fusionne, on garde le meilleur score. Personne ne se plaint qu'on lui rende sa meilleure note.
  • Profil utilisateur : le dernier écrit gagne. Le risque est faible, le coût d'une fusion élevé.
  • Paiement : jamais hors ligne. Un paiement se fait en ligne ou ne se fait pas. Il n'existe pas de version locale d'un débit Mobile Money.

Cette dernière ligne est celle que les développeurs oublient le plus souvent. Tout ne mérite pas d'être disponible hors ligne. Décider ce qui ne l'est pas fait partie du travail.

Le piège du téléchargement initial

Une application offline-first doit remplir sa base locale. Quand ? Comment ?

La solution naïve — tout télécharger au premier lancement — est la pire. Ton utilisateur installe l'app, elle réclame 80 Mo, il est sur un forfait compté, il désinstalle. Tu l'as perdu avant le premier écran.

Ce qui marche :

  1. Un noyau minimal embarqué dans le binaire. Les premières séries de questions sont dans l'APK. L'utilisateur peut travailler dès la seconde d'après.
  2. Le reste par lots, à la demande. Il télécharge le module suivant quand il en a besoin, en connaissant le poids annoncé.
  3. Un indicateur de ce qui est disponible hors ligne. Un simple badge sur chaque module. L'utilisateur devient acteur de sa consommation de données.

Ce troisième point est passé de « détail d'interface » à fonctionnalité réclamée. Les gens veulent savoir ce qu'ils vont dépenser. Le respect du forfait de l'utilisateur, c'est du respect tout court.

Le problème du milieu : personne ne teste ça

Voilà où le bât blesse pour la profession.

Ton environnement de développement a la fibre. Ton émulateur a la fibre. Ta CI a la fibre. Ton chef de produit teste depuis un bureau qui a la fibre. Toute la chaîne de fabrication ment sur les conditions réelles d'utilisation.

Les outils ne sauvent pas : le mode « Slow 3G » des devtools simule une lenteur régulière. La réalité, c'est l'irrégularité — 4G pendant huit secondes, rien pendant vingt, puis un pic. La lenteur uniforme est facile à gérer ; c'est l'imprévisibilité qui casse les applications.

Ce que je fais, faute de mieux :

  • Un mode chaos dans les préférences de développement : perte aléatoire de X % des requêtes, latence entre 200 ms et 30 s. Activé par défaut en développement.
  • Un vrai téléphone d'entrée de gamme, pas un flagship, avec un forfait presque épuisé. Une heure par sprint. Ça remet les idées en place.
  • Le test du mode avion à chaud : couper le réseau au milieu d'un parcours, pas avant de le lancer. C'est là que les bugs se trouvent.

Le coût honnête

Je ne vais pas vendre ça comme gratuit. L'offline-first coûte cher :

  • 30 à 40 % de développement en plus sur les fonctionnalités concernées.
  • Une complexité réelle : file d'attente, résolution de conflits, migrations de schéma local.
  • Un débogage plus difficile : l'état est réparti entre client et serveur.

En échange, tu obtiens une application qui fonctionne dans le monde réel de tes utilisateurs. Sur un marché où la concurrence livre des interfaces qui affichent « Vérifiez votre connexion » trois fois par session, c'est un avantage qui se voit à l'usage, pas dans une fiche produit.

Et il y a un bénéfice de bord qu'on sous-estime : les applications offline-first sont plus rapides pour tout le monde. Ton utilisateur à Paris avec la fibre profite lui aussi d'une interface qui ne bloque jamais sur un appel réseau.

Par où commencer, si tu pars d'existant

Tu ne réécris pas tout. Tu procèdes par ordre d'utilité :

  1. Identifie le parcours principal. Celui que 80 % des utilisateurs empruntent. Rien d'autre pour l'instant.
  2. Rends-le lisible hors ligne. Lecture seule, base locale, pas de synchronisation d'écriture. C'est déjà 60 % de la valeur perçue.
  3. Ajoute la file d'écriture sur une seule action, la plus fréquente.
  4. Instrumente. Combien de requêtes échouent réellement chez tes utilisateurs ? Le chiffre surprend toujours.

Ce dernier point est le vrai déclencheur. Tant que tu n'as pas mesuré le taux d'échec réseau réel de tes utilisateurs, tu débats d'opinions. Une fois que tu vois que 12 % des requêtes n'aboutissent pas, la discussion est close.


Ce mode de conception est celui que j'applique sur Driivio, et les mêmes principes irriguent Pla'nif. Si tu construis un produit pour un marché où la connexion se paie au mégaoctet, écris-moi — le premier échange est gratuit.