Qu'est-ce que la synchronisation des données offline-first ?

Mis à jour : septembre 2026

La synchronisation offline-first est une architecture où l’app lit et écrit d’abord en stockage local, puis se réconcilie avec le serveur une fois en ligne. Le réseau cesse d’être une dépendance pour devenir un processus d’arrière-plan : chaque écran s’affiche à partir de l’appareil, chaque tap s’enregistre sur l’appareil, et une couche de synchronisation solde les comptes avec le backend dès que la connectivité le permet. Le plus dur n’a jamais été de stocker des données en local ; c’est ce qui se passe quand deux copies de la vérité ne sont pas d’accord.

Points clés

QuestionRéponse
Le principeLes écrans parlent à un stockage local sur l’appareil ; le réseau synchronise en arrière-plan
Les deux posturesStockage local comme tampon (le serveur détient la vérité) ou comme réplique (la vérité se négocie)
Écritures hors ligneFile d’attente outbox durable → rejeu dans l’ordre à la reconnexion
ConflitsLast-write-wins · fusion par champ · CRDT — choisissez par champ, pas par app
Le coût réelLa mécanique de synchronisation est une complexité permanente — investissez-la là où le hors ligne compte

Épingler, lire, mettre en file : la boucle hors ligne en code

La version SDK mobile du pattern — épinglez ce dont les écrans ont besoin, mettez en file d’attente ce que les utilisateurs modifient :

// JavaScript — Back4app JS SDK with the Local Datastore enabled
Parse.enableLocalDatastore();

// Read: serve the screen from the local store — instant, works offline
const query = new Parse.Query('Task');
query.fromLocalDatastore();
render(await query.find());

// Write: durable locally now, queued for the server automatically
const task = new Parse.Object('Task');
task.set('title', 'Inspect site 14');
task.set('done', false);
await task.pin();          // survives restarts, feeds local reads
task.saveEventually();     // replays when the network returns

Deux appels portent toute l’architecture. Pin rend les objets durables et interrogeables sur l’appareil — la même interface de requête que le serveur, pointée vers le stockage local (Local Datastore dans les SDK ci-dessus). Save-eventually accepte l’écriture tout de suite et prend en charge sa livraison plus tard. Tout le reste de cet article décrit ce qui se passe entre ces deux appels.

Tampon ou source de vérité ? La décision sous la décision

Toute conception offline-first choisit discrètement l’une de deux postures pour le stockage local, et l’essentiel des difficultés de synchronisation vient de ce que ce choix n’a pas été fait délibérément.

Le stockage local comme tampon. Le serveur reste l’autorité ; l’appareil détient une copie de travail plus une outbox d’intentions. Les conflits se résolvent en faveur du serveur ou par une politique simple, et un appareil peut toujours être réparé en récupérant à nouveau les données. C’est le bon choix par défaut pour les apps métier — checklists de CRM, inspections sur le terrain, saisie de commandes — parce que le raisonnement reste simple : la vérité vit à un seul endroit, les appareils sont simplement en retard sur elle.

Le stockage local comme réplique. La vérité est négociée entre pairs et le serveur n’est qu’une réplique parmi d’autres — la posture des éditeurs collaboratifs et des apps de notes qui promettent de tout fusionner. Vous y gagnez en résilience et en confiance des utilisateurs, au prix d’une véritable mécanique de systèmes distribués : des version vectors pour détecter la concurrence, des fonctions de fusion par type de données, et la cohérence à terme comme garantie la plus forte que vous puissiez afficher honnêtement.

La posture se choisit par jeu de données, pas par app : une même app d’intervention terrain peut traiter les ordres de travail du technicien comme une réplique (ils doivent rester modifiables toute la journée en sous-sol) et le catalogue de pièces comme un tampon read-through.

La boucle de synchronisation, de bout en bout

Boucle de synchronisation offline-firstL'app lit et écrit dans un stockage local. Les écritures entrent aussi dans une file d'attente outbox durable. Quand la connectivité revient, la file rejoue les opérations ordonnées vers le serveur, le serveur applique la résolution de conflits face aux modifications concurrentes, et le client récupère dans le stockage local les changements survenus depuis sa dernière synchronisation.

lecture + écriture

chaque écriture

rejeu à la reconnexion

changements depuis la dernière synchro

Écrans de l'app

Stockage local

File outbox
(durable, ordonnée)

Serveur
(résolution de conflits)

Base de données du backend

L'app lit et écrit dans un stockage local. Les écritures entrent aussi dans une file d'attente outbox durable. Quand la connectivité revient, la file rejoue les opérations ordonnées vers le serveur, le serveur applique la résolution de conflits face aux modifications concurrentes, et le client récupère dans le stockage local les changements survenus depuis sa dernière synchronisation.

La boucle a une moitié sortante et une moitié entrante, qui se rejoignent au niveau de la résolution de conflits.

Sortante : écritures en file d’attente et rejeu. Les écritures hors ligne sont validées en local et ajoutées à une outbox durable — l’interface reflète l’intention immédiatement, avec un libellé « en attente » honnête là où cela compte. À la reconnexion, la file est rejouée dans l’ordre. Toute l’ingénierie réside dans les modes de défaillance : les erreurs transitoires sont réessayées avec backoff ; les refus de permission et de validation doivent remonter jusqu’à l’utilisateur, pas être réessayés indéfiniment ; et les opérations doivent être idempotentes, car un accusé de réception perdu signifie que la même opération peut arriver deux fois. Rejouer deux fois « passer le statut à terminé » est inoffensif ; rejouer deux fois « incrémenter le stock de 3 » est un bug.

Entrante : récupération des deltas. Tout re-télécharger à la reconnexion gaspille bande passante et batterie ; une synchronisation de production récupère les changements depuis un point de reprise — un repère updated-at dans les conceptions simples, un numéro de séquence serveur ou un change feed dans les plus robustes. Les mêmes événements qui alimentent les live queries quand l’app est en ligne servent aussi de flux de deltas entrants, et c’est pourquoi la synchronisation hors ligne et la synchronisation en temps réel forment un continuum, pas deux features.

Last-write-wins vs. fusion vs. CRDT

StratégieComment elle résoutPerte de données ?CoûtAdaptée à
Last-write-winsLe timestamp ou la version la plus récente conserve l’enregistrementOui — en silenceTrivialEnregistrements à auteur unique, interrupteurs, statuts
Fusion par champLes modifications sur des champs distincts survivent toutes ; les conflits sur un même champ relèvent d’une politiqueSeulement en cas de collision sur un même champModéréFormulaires et enregistrements modifiés par peu de personnes
Résolution manuelleLes deux versions sont conservées ; un humain trancheNonInterface + workflowEnregistrements à fort enjeu, consoles de synchronisation
CRDT / CRDT-liteTypes de données dont les opérations fusionnent de façon déterministeNonDiscipline de modélisation, poids de la bibliothèqueCompteurs, ensembles, structures collaboratives

Deux remarques honnêtes sur ce tableau. D’abord, les timestamps sont plus fragiles qu’ils n’en ont l’air — les horloges des appareils dérivent, si bien qu’un last-write-wins sérieux utilise l’heure de réception côté serveur ou des versions logiques (version vectors quand les modifications concurrentes doivent être détectées plutôt que devinées). Ensuite, le juste milieu pratique pour les apps mobiles, c’est le CRDT-lite : les bibliothèques CRDT complètes sont une lourde mécanique, mais emprunter l’idée par champ ne coûte presque rien — modélisez une quantité comme des opérations d’incrément plutôt qu’un total stocké, des tags comme des ensembles add/remove plutôt qu’un tableau, et ces champs cessent tout simplement d’entrer en conflit. La stratégie se choisit par champ ; les apps qui adoptent une politique globale unique se sont presque toujours trompées pour l’un de leurs champs.

Cas d’usage courants

  • Opérations terrain — inspections, livraisons, interventions sur les réseaux : le chantier est précisément l’endroit où la couverture disparaît, donc la saisie doit être locale et la synchronisation opportuniste.
  • Prise de notes et productivité personnelle — un seul utilisateur, plusieurs appareils : les conflits sont rares et le plus souvent auto-infligés, ce qui rend les politiques de fusion maîtrisables.
  • Apps de point de vente et de borne — la file de clients doit continuer d’avancer pendant le redémarrage du routeur ; les transactions sont rejouées quand la liaison revient.
  • Apps de voyage — itinéraires, cartes et billets consultés exactement là où la connectivité est la pire : avions, trains, itinérance.
  • Produits pour marchés émergents et faible bande passante — des connexions intermittentes et facturées au volume font de la synchronisation différentielle en arrière-plan la seule conception respectueuse, quelle que soit la combinaison de plateformes.

Devriez-vous construire en offline-first ? Matrice de décision

Construisez en offline-first quand…Restez en online-first quand…
Les utilisateurs travaillent là où la couverture fait défaut — terrain, transports, sous-solsLes utilisateurs sont en pratique toujours connectés (web desktop, outils de bureau)
Un tap bloqué coûte de l’argent ou de la confiance (point de vente, saisie sur le terrain)L’exactitude exige un état unique qui fait autorité maintenant — paiements, réservations, stock
Les données sont des enregistrements propres à l’utilisateur, avec peu d’auteurs concurrentsLes données sont calculées côté serveur ou partagées et très sollicitées (fils, dashboards) — mettez les lectures en cache, gardez les écritures en ligne
Les écritures tolèrent une réconciliation ultérieureDeux appareils agissant sur une vérité périmée représentent un risque bien réel
Vous pouvez financer la maintenance permanente de la couche de synchronisationUn état de chargement est honnêtement suffisant

La voie intermédiaire est légitime et courante : mettez les lectures en cache pour des écrans instantanés, autorisez les écritures hors ligne uniquement pour les quelques jeux de données qui en ont vraiment besoin, et gardez le reste en online-first derrière des indicateurs de connectivité honnêtes.

Limites et trade-offs

  • La résolution de conflits est une décision produit déguisée en question d’ingénierie. « Quelle modification survit ? » n’a pas de réponse techniquement correcte — quelqu’un doit porter la politique champ par champ, et « garder la plus récente en silence » est un choix qui fait une victime.
  • L’outbox est une file de passifs. Des écritures en attente périmées, venues d’un appareil qui réapparaît au bout de deux semaines, peuvent être rejouées dans un monde qui a changé ; politiques d’expiration et validation au rejeu sont obligatoires, pas paranoïaques.
  • Les bugs de synchronisation sont la pire catégorie de bugs qui soit. Ils ne se reproduisent que sous des entrelacements précis de modifications et de connectivité — investissez dans des tests de rejeu déterministes et dans un affichage visible de l’état de synchronisation, ou déboguez à l’anecdote pour toujours.
  • La vérité locale vieillit. Les écrans doivent être honnêtes sur l’obsolescence là où elle compte (prix, disponibilité) — « il y a 2 heures » est une feature, pas une excuse.
  • Stockage et identité ajoutent de la friction. Les stockages sur l’appareil ont besoin de migrations comme n’importe quelle base de données, et les enregistrements créés hors ligne ont besoin d’identifiants générés côté client qui survivent au voyage jusqu’au serveur sans se dupliquer.

Synchronisation offline-first sur Back4app

Back4app est une plateforme open-source de Backend as a Service (BaaS) qui combine une base de données gérée, des API REST et GraphQL générées automatiquement, l’authentification, le stockage de fichiers et des fonctions serverless avec Cloud Code. Ses SDK mobiles livrent la posture tampon clé en main : le Local Datastore épingle les résultats de n’importe quelle requête pour les lectures hors ligne via la même API de requête, saveEventually dote chaque écriture d’une outbox durable avec rejeu ordonné, et les triggers beforeSave de Cloud Code sont l’emplacement naturel de la politique de conflits côté serveur — contrôles de version, règles de fusion, validation au rejeu — appliquée sur chaque chemin d’écriture. Ajoutez le flux Live Query pour les deltas entrants tant que l’app est en ligne, et la boucle de synchronisation du diagramme ci-dessus devient une affaire de configuration et de politique, pas un sous-système à construire.

Questions fréquentes

Qu'est-ce qu'une app offline-first ?

Une app dont les écrans lisent et écrivent dans une base de données présente sur l'appareil, le réseau étant relégué au rang de détail de synchronisation. Chaque tap fonctionne en mode avion, car rien sur le chemin de l'interaction n'attend un serveur ; une couche de synchronisation en arrière-plan réconcilie le stockage local avec le backend dès que la connectivité le permet. À l'inverse, les apps online-first affichent des spinners jusqu'à ce que le serveur réponde.

Comment fonctionne la synchronisation des données hors ligne ?

Deux boucles. Sortante : les écritures locales atterrissent dans le stockage de l'appareil et dans une file d'attente outbox durable ; quand le réseau revient, la file est rejouée contre le serveur, dans l'ordre. Entrante : le client récupère les changements survenus depuis sa dernière synchronisation — par timestamp, numéro de séquence ou change feed — et les applique localement. La résolution de conflits se situe à la jonction des deux boucles et décide de ce qui se passe quand les deux côtés ont modifié le même enregistrement.

Qu'est-ce que le last-write-wins et quand est-il sûr ?

La politique de conflit la plus simple : comparer timestamps ou versions et conserver l'écriture la plus récente, en écartant l'autre en silence. Elle est sûre quand les modifications portent sur l'enregistrement entier et que l'enjeu est faible — un réglage activé, un indicateur de statut — ou quand un seul auteur par enregistrement est la norme. Elle est dangereuse pour les documents partagés et les compteurs, où « écarter l'autre modification » signifie perdre le travail de quelqu'un.

Comment les CRDT résolvent-ils les conflits ?

En rendant les conflits impossibles à représenter : un CRDT (conflict-free replicated data type) restreint chaque champ à des opérations qui fusionnent de façon déterministe, quel que soit l'ordre d'arrivée — compteurs croissants, ensembles add/remove, registres au dernier écrivain. Deux répliques qui échangent leurs états convergent toujours, sans coordination. Le prix, c'est une discipline de modélisation : vos données doivent s'exprimer dans le vocabulaire CRDT, et les bibliothèques CRDT complètes sont lourdes pour l'app mobile typique faite de formulaires et de listes.

Que deviennent les écritures effectuées hors ligne ?

Elles sont mises en file d'attente. Une outbox durable enregistre chaque opération en attente, et le stockage local reflète l'écriture immédiatement pour que l'interface reste fidèle à l'intention de l'utilisateur. À la reconnexion, la file est rejouée dans l'ordre ; les échecs exigent une politique — réessayer les erreurs transitoires, remonter à l'interface les refus de permission et de validation plutôt que de réessayer indéfiniment. Des opérations idempotentes rendent le rejeu sûr quand des accusés de réception se perdent.

L'offline-first, est-ce la même chose que la mise en cache ?

Non — c'est le sens qui fait la différence. Un cache accélère les lectures : le serveur reste la source de vérité et les entrées périmées sont simplement récupérées à nouveau. L'offline-first accepte aussi les écritures en local, et c'est précisément ce qui crée une divergence entre l'appareil et le serveur et impose la mécanique difficile — files outbox, rejeu, résolution de conflits. La mise en cache des lectures est une optimisation de performance ; l'offline-first est un engagement d'architecture de données.

Quand ne faut-il pas construire en offline-first ?

Quand l'exactitude dépend d'un état unique qui fait autorité : paiements, réservations, décréments de stock, tout cas où deux appareils agissant sur des données périmées produisent de vraies doubles dépenses. Également quand les données sont calculées côté serveur et surtout lues — un fil d'actualités se dégrade avec élégance derrière un simple cache. La mécanique de synchronisation est un impôt permanent ; payez-le là où les conditions du terrain ou les attentes des utilisateurs l'exigent.

Comment les SDK mobiles épinglent-ils les données en local ?

L'épinglage (pin) marque des objets pour un stockage durable sur l'appareil : les enregistrements épinglés survivent aux redémarrages, s'interrogent hors ligne avec la même interface de requête que le serveur et restent liés à leur identité côté serveur, de sorte que les synchronisations suivantes les mettent à jour sur place. Associé aux écritures en file d'attente — la sémantique save-eventually — l'épinglage fournit une couche hors ligne opérationnelle sans bricoler à la main un schéma de base de données sur l'appareil.

Termes associés

À comparer avec

Lectures recommandées

Prêt à construire votre backend ?

Lancez votre projet sur Back4app en quelques minutes — base de données, authentification, API et Cloud Code inclus. Sans carte bancaire.

Écrit et révisé par Back4app Engineering, Back4app Engineering · Publié le 2026-09-16