Hydratation SSR
SsrHydrator lit le snapshot du cache rendu côté serveur depuis window.__QORA_STATE__ et l'injecte dans QoraClient avant le rendu de la première frame.
SsrHydrator est uniquement disponible sur Flutter Web. Sur les cibles natives et serveur, l'import se résout en stub sans opération, de sorte que le même code compile partout sans gardes #if.
Fonctionnement
- Le serveur précharge les données et les intègre sous forme d'objet JSON dans une balise
<script>avant le chargement demain.dart.js. SsrHydrator.hydrate()litwindow.__QORA_STATE__, valide le payload et appelleclient.queueHydration()pour chaque entrée.- Au premier appel
fetchQuery<T>ouwatchQuery<T>pour une clé, la valeur en file est castée enTet injectée comme étatSuccess. - Si
updatedAtMsest omis ou si les données sont plus anciennes questaleTime, Qora déclenche automatiquement une revalidation en arrière-plan (SWR).
Configuration serveur
Intégrer les données préchargées dans une balise <script> avant main.dart.js :
<script>
window.__QORA_STATE__ = {
'["users",1]': {
"data": { "id": 1, "name": "Ada", "email": "ada@example.com" },
"updatedAtMs": 1700000000000
},
'["posts"]': {
"data": [
{ "id": 10, "title": "Bonjour le monde" },
{ "id": 11, "title": "Flutter Web" }
]
}
};
</script>
<script src="main.dart.js" defer></script>
Les clés sont des clés de requête normalisées encodées en JSON : le même format qu'utilise Qora en interne (ex. '["users",1]' pour la clé ['users', 1]).
updatedAtMs est optionnel. Quand omis, Qora traite les données comme datant de l'epoch (toujours périmées) : elles sont servies immédiatement mais une revalidation en arrière-plan se déclenche au premier montage.
Configuration Flutter
Appeler SsrHydrator.hydrate() avant runApp :
import 'package:flutter/material.dart';
import 'package:qora/qora.dart';
import 'package:qora_flutter/qora_flutter.dart';
void main() {
final client = QoraClient();
// Hydrater depuis window.__QORA_STATE__ (sans opération sur non-web)
SsrHydrator(client).hydrate(
deserializers: {
'["users",1]': (json) => User.fromJson(json as Map<String, dynamic>),
'["posts"]': (json) => (json as List).map(Post.fromJson).toList(),
},
);
runApp(QoraScope(client: client, child: const MyApp()));
}
Les clés de window.__QORA_STATE__ absentes de deserializers sont silencieusement ignorées. Si deserializers est entièrement omis, les valeurs dynamic brutes de dartify() sont mises en file directement : adapté quand les données sont déjà des Map/List simples.
Sécurité
SsrHydrator est sûr contre les XSS par conception :
- Utilise
dartify()pour la conversion JS → Dart : produit des maps/listes/chaînes simples, jamais de HTML. Pas d'innerHTMLni d'eval. - Valide le type de niveau supérieur (
Map) et chaque entrée (Mapavec une clé'data') avant traitement. - Encapsule chaque appel au désérialiseur dans un
try/catch: une entrée corrompue ne peut pas planter l'application.
Responsabilité : encoder en JSON les données contrôlées par l'utilisateur côté serveur avant de les intégrer dans __QORA_STATE__. L'interpolation HTML brute contourne les protections de Qora et permet des XSS.
Fraîcheur et SWR
L'horodatage updatedAtMs est stocké comme updatedAt sur l'état Success injecté. La logique stale-while-revalidate de Qora l'utilise pour décider de déclencher un fetch en arrière-plan :
// staleTime: 5 minutes
// updatedAtMs: il y a 4 minutes → frais, pas de fetch en arrière-plan
// updatedAtMs: il y a 6 minutes → périmé, SWR se déclenche immédiatement au montage
// updatedAtMs: absent → toujours périmé
client.watchQuery<User>(
key: ['users', 1],
fetcher: () => api.getUser(1),
options: const QoraOptions(staleTime: Duration(minutes: 5)),
);
API SsrHydrator
Constructeur
const SsrHydrator(QoraClient client)
hydrate
void hydrate({Map<String, dynamic Function(dynamic)>? deserializers})
Lire window.__QORA_STATE__ et mettre en file les entrées d'hydratation.
| Paramètre | Type | Description |
|---|---|---|
deserializers | Map<String, dynamic Function(dynamic)>? | Map de clé encodée en chaîne vers une fonction convertissant le JSON brut en Dart typé. Les clés absentes de cette map sont ignorées. Passer null pour mettre en file les valeurs brutes. |
Avec PersistQoraClient
SsrHydrator et PersistQoraClient utilisent tous deux queueHydration() en interne. Ils se composent sans conflit : la dernière écriture pour une clé donnée gagne (le premier accès typé la récupère) :
final client = PersistQoraClient(storage: storage);
client.registerSerializer<User>(...);
await storage.init();
await client.hydrate(); // depuis le disque
SsrHydrator(client).hydrate(...); // depuis le serveur (écrase l'entrée disque si la clé correspond)
runApp(QoraScope(client: client, child: const MyApp()));
Voir aussi
- Guide de persistance : Cache sur disque avec
PersistQoraClient - API QoraClient :
queueHydration,hydrateQuery - Démarrage rapide
Annulation
Utiliser CancelToken pour interrompre coopérativement les fetchs en cours : lors de la navigation, d'un timeout ou d'une action utilisateur.
Gestion des erreurs
Comprendre comment Qora remonte les erreurs, configure le comportement de retry et active la dégradation gracieuse avec previousData.