Aller au contenu

Persistance

La persistance permet à Presage de conserver les traits utilisateur et les signaux comportementaux entre les rechargements de page et les sessions navigateur.

Le driver recommandé pour les applications SaaS dans le navigateur :

import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
const client = createAdaptiveClient({
persistence: {
driver: createLocalStorageDriver('my-app'),
},
// ...rules
})

Les données sont stockées dans localStorage sous des clés namespacées :

  • my-app:traits — Traits utilisateur (rôle, plan, etc.)
  • my-app:signals — Signaux comportementaux

Le préfixe évite les collisions avec d’autres bibliothèques ou plusieurs instances d’Presage sur le même domaine.

Un driver en mémoire pour les environnements SSR et les tests :

import { createAdaptiveClient, createMemoryDriver } from '@presage-kit/core'
const client = createAdaptiveClient({
persistence: {
driver: createMemoryDriver(),
},
// ...rules
})

Les données vivent uniquement en mémoire et sont perdues quand le processus se termine. C’est utile pour :

  • Le rendu côté serveurlocalStorage n’est pas disponible sur le serveur
  • Les tests unitaires — Pas d’effets de bord entre les exécutions de tests
  • Storybook — Des stories isolées qui ne polluent pas le stockage du navigateur
CléDonnéesQuand mis à jour
traitsObjet UserTraitsSur identify(), updateTraits() et tout changement d’état
signalsObjet BehavioralSignalsAprès le recalcul des signaux (100ms après track())

Implémentez l’interface StorageDriver pour stocker les données où vous voulez :

interface StorageDriver {
get<T>(key: string): T | null
set<T>(key: string, value: T): void
remove(key: string): void
clear(): void
}
import type { StorageDriver } from '@presage-kit/core'
function createSessionStorageDriver(prefix: string): StorageDriver {
function prefixed(key: string): string {
return `${prefix}:${key}`
}
return {
get<T>(key: string): T | null {
const raw = sessionStorage.getItem(prefixed(key))
if (raw === null) return null
try {
return JSON.parse(raw) as T
} catch {
return null
}
},
set<T>(key: string, value: T): void {
sessionStorage.setItem(prefixed(key), JSON.stringify(value))
},
remove(key: string): void {
sessionStorage.removeItem(prefixed(key))
},
clear(): void {
const toRemove: string[] = []
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i)
if (key?.startsWith(`${prefix}:`)) {
toRemove.push(key)
}
}
for (const key of toRemove) {
sessionStorage.removeItem(key)
}
},
}
}
import type { StorageDriver } from '@presage-kit/core'
function createCookieDriver(prefix: string): StorageDriver {
return {
get<T>(key: string): T | null {
const name = `${prefix}_${key}`
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`))
if (!match) return null
try {
return JSON.parse(decodeURIComponent(match[1])) as T
} catch {
return null
}
},
set<T>(key: string, value: T): void {
const name = `${prefix}_${key}`
const encoded = encodeURIComponent(JSON.stringify(value))
document.cookie = `${name}=${encoded}; path=/; max-age=31536000; SameSite=Lax`
},
remove(key: string): void {
const name = `${prefix}_${key}`
document.cookie = `${name}=; path=/; max-age=0`
},
clear(): void {
// Clear all cookies with the prefix
const cookies = document.cookie.split('; ')
for (const cookie of cookies) {
const name = cookie.split('=')[0]
if (name.startsWith(`${prefix}_`)) {
document.cookie = `${name}=; path=/; max-age=0`
}
}
},
}
}

Un problème courant avec l’état persisté est le flash de contenu non conditionné (FOUC) : l’interface se rend avec les valeurs par défaut, puis clignote vers l’état personnalisé une fois les données chargées.

Presage évite cela car la méthode StorageDriver.get() est synchrone. Quand createAdaptiveClient() est appelé :

  1. Le driver lit les traits et signaux en cache de manière synchrone
  2. La maturité est calculée à partir des signaux en cache
  3. Le UserContext initial est prêt avant le premier rendu
  4. Les composants reçoivent la bonne variante dès le tout premier affichage
// This is synchronous — no loading state needed
const client = createAdaptiveClient({
persistence: {
driver: createLocalStorageDriver('my-app'),
},
rules: [...],
})
// client.getContext() already has cached data
console.log(client.getContext().traits) // { userId: 'user-123', role: 'admin', ... }

Ce choix de conception est intentionnel. localStorage et sessionStorage sont tous deux des API synchrones, ce qui les rend idéaux pour l’anti-FOUC. Si vous construisez un driver asynchrone personnalisé (ex. IndexedDB), vous devrez gérer vous-même l’état de chargement.

Si vous ne configurez pas de driver de persistance, Presage fonctionne quand même — mais le contexte utilisateur est réinitialisé à chaque rechargement de page. Cela peut être utile pour :

  • Le prototypage et les démonstrations
  • Les applications rendues côté serveur où le contexte vient du serveur
  • Les scénarios où vous faites toujours un identify() depuis votre système d’authentification au montage
// No persistence — context resets on reload
const client = createAdaptiveClient({
rules: [...],
})