Comment ça fonctionne
Le démarrage du serveur, le rôle des registres et le chemin d'une requête, du navigateur jusqu'au composant rendu.
Une application LRVO tient en deux moments. Au démarrage du serveur, les modules et le projet déclarent ce qu'ils apportent. À chaque requête, le core relit ces déclarations pour savoir quoi rendre. Cette page suit les deux, sans entrer dans les signatures.
1. Le démarrage du serveur
Tout part d'instrumentation.js, à la racine du projet. Next.js l'exécute une fois, avant la première requête.
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { initializeLrvo } = await import('@lrvo/core');
const { modules } = await import('./app/.lrvo/modules.generated.js');
await import('./app/.lrvo/blocks.server.js');
await import('./app/.lrvo/templates.server.js');
const { default: theme } = await import('./theme.js');
await initializeLrvo({ modules, theme });
}
}theme.js décrit l'apparence du projet, voir Personnaliser l'apparence. Les fichiers de app/.lrvo/ ne sont pas écrits à la main. Ils sont générés par lrvo-modules sync, qui scanne les dépendances du projet à la recherche des paquets @lrvo/module-*. Installer un module suffit donc à le faire entrer ici, sans ligne de câblage.
L'ordre des opérations compte.
- L'import de
modules.generated.jsdéclenche leregister()de chaque module, en parallèle et sans ordre garanti. - Les blocs et les templates sont chargés ensuite, avant l'initialisation, pour être disponibles si un module les consulte.
initializeLrvo()câble les features aux cores : le résolveur de session au routeur, les routes et les policies de stockage de l'authentification, les permissions du core puis celles de chaque module.- En dernier, les rappels posés par
onModulesReady()sont exécutés. C'est le premier instant où un module peut observer de façon fiable ce que les autres ont enregistré.
initializeLrvo() ne crée aucune table. Le schéma est la responsabilité de lrvo-db init, une commande explicite : un démarrage de serveur ne modifie jamais la base.
2. Les registres
Chaque déclaration atterrit dans un registre, une simple table en mémoire. Il y en a une quinzaine : pages d'admin, widgets, entrées de navigation, permissions, routes, jobs cron, templates, blocs, résolveurs de contenu public.
Ils vivent sur globalThis, sous une clé produite par Symbol.for. Deux raisons à cette forme, et aucune n'est cosmétique.
Survivre au rechargement à chaud. En développement, Next.js recharge les modules à chaque sauvegarde. Un registre rangé dans une variable ordinaire serait vidé à chaque fois : les widgets disparaîtraient, les jobs cron se réenregistreraient en double.
Survivre au double chargement. Un module externe est chargé par Node, le code du core par le compilateur. Les deux peuvent voir deux copies du même fichier. Symbol.for garantit que ces deux copies désignent la même case mémoire.
Le détail est dans Registres et singletons.
3. Le chemin d'une requête
Le projet généré n'expose qu'une poignée de routes, toutes déléguant à un registre. C'est ce qui permet à une mise à jour du paquet de faire évoluer l'application sans toucher au projet.
| URL | Ce qui répond |
|---|---|
/ | La page d'accueil du projet, app/(public)/page.js. Elle vous appartient, aucun module ne la remplace. |
/<slug> et au-delà | Le registre de contenu public. Chaque module de contenu y a inscrit un résolveur avec une priorité, et le premier qui reconnaît l'URL la sert. |
/auth/… | Les pages d'authentification, servies par une seule route. |
/admin/… | Le shell d'admin, qui résout la page à partir du registre. La navigation et le fil d'Ariane sont calculés à partir du même registre, jamais écrits en dur. |
/lrvo/api/… | Le routeur de core/api, qui dispatche vers les routes enregistrées, après session et permission. |
/lrvo/<module>/… | Les pages publiques réservées d'un module, comme un lien de partage ou une page de paiement. |
La résolution du contenu public mérite un mot, parce que c'est elle qui fait cohabiter plusieurs modules sur le même domaine. Les résolveurs sont essayés par priorité décroissante, et un résolveur qui ne reconnaît pas l'URL renonce pour laisser passer le suivant. Le module Posts s'inscrit à la priorité 10 et ne répond que sur les préfixes de type qu'il connaît. Le module Pages s'inscrit à 0 et sert de repli. Quand aucun ne répond, le projet rend sa page 404.
4. Le navigateur
Une partie des registres doit exister des deux côtés. Une page d'admin et un widget sont des composants React : le serveur doit connaître leur titre pour les métadonnées, le navigateur doit connaître leur code pour les afficher.
D'où deux fichiers dans app/layout.js, importés et rendus, qui rejouent les enregistrements côté client. Un widget se déclare donc en deux moitiés, une qui charge ses données sur le serveur et une qui les dessine. C'est le point qui surprend le plus au premier module écrit, et Frontières serveur et client l'explique en entier.
Étape suivante
Que peut-on construire applique tout ceci à trois projets concrets.