Comment implémenter AriaML côté serveur?
Permettre à un serveur de générer un flux compatible avec le la navigation fluide AriaML, tout en restant rétrocompatible avec les clients Html.
Cette documentation fournit une stratégie standard d'implémentation d'un Polyfill SSR AriaML. L'objectif est de permettre à un serveur de générer un flux compatible avec la navigation fluide AriaML, tout en restant rétrocompatible avec les clients Html.
IMPORTANT Une implémentation PHP existe déjà.
1. Architecture Conceptuelle
Il est recommandée d'opter pour une stratégie découplée en trois objets :
- Request Factory : Analyse l'intention du client (Headers).
- AriaML Document : Structure les données sémantiques (JSON-LD) et gère le balisage de transport.
- Response Factory : Synchronise l'état du document et les headers de sortie.
2. Le Protocole de Communication (Headers)
Le serveur doit réagir spécifiquement aux headers suivants envoyés par le client :
| Header | Valeur / Type | Rôle |
|---|---|---|
Accept |
text/aria-ml-fragment |
Priorité maximale : demande un rendu natif (sans <html>). |
X-AriaML-Fragment |
flag |
Fallback facultatif pour forcer le mode fragment (en cas de corruption du header accept). |
nav-cache |
JSON Array |
Liste des clés d'éléments DOM déjà présents dans le cache client. |
ariaml-force-html |
boolean |
Si true, le serveur doit répondre en text/html (Web Extension). |
3. Spécifications des Classes
A. AriaMLRequestFactory
Cette classe est un analyseur de contexte.
isFragment(): RetournetruesiAcceptcommence partext/aria-ml-fragmentou siX-AriaML-Fragmentest présent.expectsHtmlWrapper(): Retournefalsesi un fragment est demandé OU si le client accepte nativementtext/aria-ml. Retournetruepour un navigateur standard (SEO).expectedStatus(): Retourne206 Partial ContentsiisFragment()est vrai, sinon200 OK.expectedContentType():- Si
ariaml-force-html == true: toujourstext/html. - Sinon :
text/aria-ml-fragment(pour fragment) outext/aria-ml(pour document complet).
- Si
B. AriaMLDocument
Le générateur de structure.
- Attributs :
isFragment(bool),expectedHtml(bool). startTag():- Si
expectedHtml, génère<!DOCTYPE html><html...><head>...[SSR Head]...</head><body>. - Ouvre
<aria-ml-fragment>siisFragment, sinon<aria-ml>.
- Si
endTag():- Ferme
</aria-ml-fragment>ou</aria-ml>. - Si
expectedHtml, injecte<script src="standalone.js"></script></body></html>.
- Ferme
consumeDefinition(keys): Retourne une portion du JSON-LD en évitant de renvoyer les clés déjà marquées comme "consommées". Il peut être interessant de découper le JSON-LD, généralement en deux blocs. L'un d'eux est dynamique et actualisé à chaque changement de contexte, l'autre est statique et s'adresse aux robots d'indexation des moteurs de recherches. (voire plus bas)
C. AriaMLResponseFactory
Le synchroniseur (Middleware).
applyTo(request, document):
- Injecte l'état de la requête dans le document (
isFragment,expectedHtml). - Définit le code HTTP (
206ou200). - Applique le header
Vary: Accept, X-AriaML-Fragment, nav-cache(crucial pour le cache navigateur). - Définit le
Content-Type.
4. Algorithme de "Deep Restoration" (SSR side)
Lors de la génération du HTML à l'intérieur des slots, le serveur doit vérifier le cache client pour économiser la bande passante.
Logique de rendu d'un composant :
- Récupérer la
cacheKeydu composant. - Vérifier si
cacheKeyexiste dans l'arraynav-cachede la requête. - Si présente : Envoyer uniquement
<div nav-cache="ma-clef"></div>(vide). - Si absente : Envoyer le contenu complet
<div nav-cache="ma-clef">...contenu...</div>.
AriaML détectera l'élément vide avec l'attribut nav-cache et réinjectera automatiquement le DOM vivant stocké en local.
5. Exemple d'implémentation (Pseudo-Code / Node.js)
1. Approche par Processus (Séquentielle)
Cette approche détaille comment les objets AriaML interagissent avec les objets natifs req et res d'un serveur (ici type Express/Node.js).
// --- Étape 1 : Analyse de l'intention client ---
const reqFactory = new AriaMLRequestFactory(req.headers);
// --- Étape 2 : Préparation du Document ---
const doc = new AriaMLDocument({
"name": "Page Produit",
"inLanguage": "fr-FR",
"direction": "ltr",
"url": "[https://monsite.com/chaussures](https://monsite.com/chaussures)"
});
// --- Étape 3 : Synchronisation (La ResponseFactory configure le tout) ---
const respFactory = new AriaMLResponseFactory();
// Cette méthode configure res.status, res.setHeader
// et définit doc.isFragment / doc.expectedHtml
respFactory.applyTo(reqFactory, doc, res);
// --- Étape 4 : Rendu du flux ---
res.write(doc.startTag());
// Injection des définitions consommables
res.write(`
<script type="application/ld+json" nav-slot="dynamic-definition">
${doc.consumeDefinition(['name', 'inLanguage', 'direction'])} <!-- dynamique, actualisé à chaque changement de contexte -->
</script>
`);
if(!reqFactory.isFragment())
res.write(`
<script type="application/ld+json">
${doc.consumeDefinition()} <!-- tout le reste : s'adresse aux robots d'indexation des moteurs de recherches -->
</script>
`);
// Logique de Slot avec Deep Restoration
res.write('<main nav-slot="content">');
if (reqFactory.clientHasCache('main-view')) {
res.write('<div nav-cache="main-view"></div>');
} else {
res.write('<div nav-cache="main-view"><h1>Contenu complet</h1></div>');
}
res.write('</main>');
res.write(doc.endTag());
res.end();
2. Approche Générique (Handler / Middleware)
Une fois le process compris, on l'isole pour permettre aux contrôleurs de ne gérer que la donnée métier.
/**
* AriaML Handler générique
* Cette fonction encapsule la plomberie AriaML.
*/
async function ariaMLHandler(req, res, data, renderContent) {
const reqFactory = new AriaMLRequestFactory(req.headers);
const doc = new AriaMLDocument(data);
const respFactory = new AriaMLResponseFactory();
// Synchronisation automatique
respFactory.applyTo(reqFactory, doc, res);
// Début du stream
res.write(doc.startTag());
// Rendu des métadonnées système
res.write(`
<script type="application/ld+json" nav-slot="dynamic-definition">
${doc.consumeDefinition(['name', 'inLanguage', 'direction'])}
</script>
<script type="application/ld+json">
${doc.consumeDefinition()}
</script>
`);
// Délégation du contenu au contrôleur
// Le contrôleur reçoit doc et reqFactory pour gérer ses propres slots/cache
await renderContent(doc, reqFactory);
res.write(doc.endTag());
res.end();
}
6. Points de vigilance pour l'implémentation
- Immuabilité : Si vous utilisez des objets de réponse immuables (comme en PHP PSR-7 ou avec certains frameworks Node), assurez-vous que
applyToretourne une nouvelle instance de la réponse. - Encodage : Le JSON-LD injecté via
consumeDefinitiondoit être échappé pour éviter les injections XSS, tout en restant lisible parJSON.parsecôté client. - Ordre des Headers : Le header
Varypermet d'éviter que les CDN ou les caches navigateurs ne servent un fragment à la place d'une page complète.
Pour porter ce moteur dans un nouveau langage, gardez ces règles en tête :
A. La Négociation (ResponseFactory)
Le serveur doit produire les headers suivant selon l'état de AriaMLRequestFactory :
- Vary: Accept, X-AriaML-Fragment, nav-cache
- Content-Type: [text/aria-ml | text/aria-ml-fragment | text/html]
- Status: [200 | 206]
B. La Consommation (AriaMLDocument)
La méthode consumeDefinition(keys) doit être à état (stateful) :
- Elle stocke une liste interne des clés déjà envoyées.
- Si
keysest vide, elle itère sur le JSON-LD original et ne renvoie que ce qui n'est pas dans la liste des clés consommées. - Elle doit produire un JSON valide, déstiné à être wrappé dans un bloc
<script type="application/ld+json">.
C. Le Rendu Conditionnel (Deep Restoration)
Afin de permettre l'économie de bande passante AriaML :
- TOUJOURS vérifier
reqFactory.clientHasCache(key)avant de générer le HTML d'un bloc possédant un attributnav-cache. - Si
true, le serveur ne devrait renvoyer que la "coquille" vide :<tag nav-cache="key"></tag>. - Notons qu'un élémént peut avoir à la fois l'attribut nav-cache et l'attribut nav-slot.