Tous les articles
PWAIndexedDBReact

Offline-first : une PWA qui continue de fonctionner sans réseau

Quand la connexion est instable, une application qui affiche un écran blanc perd la confiance de ses utilisateurs. Voici l'architecture qui garde Faciloop utilisable hors ligne.

FMFallou Mbacké Ndiaye6 min de lecture

Les équipes commerciales qui utilisent Faciloop travaillent souvent sur le terrain, avec une connexion mobile qui va et vient. Une application qui bloque à chaque coupure n'est pas utilisable. L'objectif : que l'interface reste rapide et fonctionnelle, que le réseau soit là ou non.

Trois couches qui travaillent ensemble

  • Workbox met en cache l'application elle-même (HTML, JS, CSS, polices) : elle s'ouvre même sans réseau.
  • Dexie.js garde une copie locale des données dans IndexedDB : listes et fiches s'affichent instantanément.
  • TanStack Query synchronise cette copie avec le serveur quand la connexion le permet.

Lire d'abord en local

L'interface lit toujours la base locale. Le réseau sert à la rafraîchir en arrière-plan, pas à afficher la page. Résultat : pas d'écran de chargement à chaque navigation, et une application qui reste consultable en mode avion.

ts
import Dexie, { type Table } from "dexie";

export class LocalDB extends Dexie {
  prospects!: Table<Prospect, string>;
  outbox!: Table<PendingChange, number>;

  constructor() {
    super("faciloop");
    this.version(1).stores({
      prospects: "id, organization_id, updated_at",
      outbox: "++seq, created_at",
    });
  }
}

export const db = new LocalDB();

Écrire hors ligne : une file d'attente

Une modification faite hors ligne est appliquée tout de suite en local, puis enregistrée dans une table outbox. Au retour du réseau, la file est rejouée dans l'ordre vers le serveur. Si une requête échoue, elle reste dans la file et l'utilisateur ne perd rien.

ts
export async function flushOutbox() {
  const pending = await db.outbox.orderBy("seq").toArray();
  for (const change of pending) {
    await sendToServer(change); // throws if offline: we retry later
    await db.outbox.delete(change.seq!);
  }
}

window.addEventListener("online", () => void flushOutbox());

Ce que j'ai appris

  • Afficher clairement l'état (en ligne, hors ligne, synchronisation en cours) rassure plus que n'importe quelle animation.
  • Décider tôt qui gagne en cas de conflit : ici, le serveur reste la source de vérité.
  • Tester en conditions réelles : le mode avion du téléphone révèle plus de bugs que l'onglet réseau du navigateur.
Projet associéFaciloopSaaS B2B & CRM Commercial Multi-Tenant
Lire l'articleIsoler les données de chaque entreprise avec PostgreSQL RLS