Développer une application mobile ou web pour des agents de terrain en zone rurale (collecteurs de données, techniciens réseau, livreurs) nécessite une approche radicalement différente lorsque la connectivité réseau chute régulièrement sous les 60% de disponibilité. La plupart des documentations traitent le “offline” comme un état temporaire. En réalité, le mode déconnecté est l'état par défaut.
L'anti-pattern du Loader Bloquant
L'erreur classique est de concevoir l'application comme un client HTTP standard qui affiche un indicateur de chargement (spinner) à chaque action et renvoie une erreur si l'API ne répond pas dans les 10 secondes. Sur le terrain, cela rend l'application inutilisable. L'application doit lire et écrire instantanément dans une base de données locale, et déléguer la synchronisation à un processus d'arrière-plan complètement asynchrone.
L'architecture de synchronisation en 3 piliers
Voici les composants indispensables pour faire tourner une application offline-first résiliente :
1. Base de données locale indexée (SQLite / WatermelonDB)
Toutes les données nécessaires pour travailler (catalogue de produits, fiches clients, formulaires vides) doivent être stockées localement. Nous utilisons des identifiants uniques universels (UUID) générés par le client pour éviter toute collision d'ID lors de l'insertion ultérieure en base centrale.
2. La file d'attente d'actions (Outbox Queue)
Toute action utilisateur modifiant les données (créer un rapport, modifier un statut) n'appelle pas l'API. Elle est enregistrée sous forme d'action sérialisée dans une table locale outbox_mutations.
// Exemple de structure d'une mutation stockée localement
interface OutboxMutation {
id: string; // UUID local
actionType: 'CREATE_REPORT' | 'UPDATE_STATUS';
payload: Record<string, any>;
createdAt: string; // timestamp ISO
status: 'PENDING' | 'SYNCING' | 'FAILED';
retryCount: number;
}3. Le synchroniseur d'arrière-plan (Sync Worker)
Un worker écoute l'état du réseau. Dès qu'une connexion internet stable est détectée, il dépile les mutations de la table outbox_mutations dans l'ordre chronologique strict (FIFO) et les envoie à l'API.
La résolution des conflits : La réalité du terrain
Que se passe-t-il si l'agent A modifie la fiche d'un client hors-ligne, tandis que l'agent B la modifie en ligne au même moment ?
La solution simpliste “Last-Write-Wins” (le dernier qui écrit écrase tout) détruit des données. Une approche plus robuste consiste à implémenter un versionnage logique des entités (Optimistic Concurrency Control) :
// Payload envoyé à l'API pour mise à jour
{
"client_id": "uuid-1234",
"version": 4, // La version lue localement par l'agent
"changes": {
"phone": "+33614093987"
}
}
// Si la version en BDD centrale est passée à 5 (modifiée par un autre agent entre-temps),
// l'API renvoie un code d'erreur 409 (Conflict).
// L'application doit alors télécharger la version 5 et demander à l'agent de fusionner
// ou appliquer une règle métier automatique.Conclusion
L'offline-first n'est pas une surcouche cosmétique ; c'est une décision d'architecture système structurante. Elle demande un stockage local robuste, un système d'ID décentralisé (UUID), une file d'attente d'actions idempotentes et une gestion fine des conflits de synchronisation. C'est le prix à payer pour offrir une expérience fluide là où les réseaux vacillent.
