fix(cli): export ZenModulesClient component from client manifest to ensure side-effects execute in browser

- update `renderClientManifest` to export a `ZenModulesClient` React component instead of `export {}`
- update docs to explain why rendering the component is required under Next.js 15+/Turbopack and add usage example in `app/layout.js`
This commit is contained in:
2026-04-25 15:15:27 -04:00
parent 9cdc945639
commit f14731e554
2 changed files with 38 additions and 14 deletions
+21 -3
View File
@@ -18,9 +18,9 @@ Aucun fichier de configuration manuelle. La plateforme découvre les modules par
Les modules sont activés via deux **manifestes statiques** générés par la CLI `zen-modules sync` dans le projet consommateur :
- `app/.zen/modules.generated.js` — manifeste serveur (importé par `instrumentation.js`).
- `app/.zen/modules.client.js` — manifeste client (`'use client'`, importé par `app/layout.js`).
- `app/.zen/modules.client.js` — manifeste client (`'use client'`, exporte un Component à rendre dans `app/layout.js`).
Les deux fichiers contiennent les mêmes `import * as ... from '@zen/module-X'` mais déclenchent `register()` dans leur bundle respectif. La séparation est nécessaire parce que `app/layout.js` est un Server Component : son graphe d'imports tourne dans le bundle serveur. Pour que les `registerPage()` / `registerWidget()` peuplent aussi le registre côté browser au moment de l'hydratation, il faut un fichier `'use client'` dédié.
Le manifeste serveur peuple le registre côté Node via le top-level await à l'import. Le manifeste client exporte `ZenModulesClient` — un Component React — que `app/layout.js` doit **rendre** dans son tree (pas seulement importer). Sous Next.js 15+/Turbopack, un `import` purement side-effect d'un fichier `'use client'` orphelin (sans Component utilisé) inclut bien le fichier dans le bundle browser mais n'en exécute pas le code top-level — `registerPage()` / `registerWidget()` ne tourneraient jamais côté client. Rendre le Component force l'exécution.
```js
// app/.zen/modules.generated.js — AUTO-GÉNÉRÉ (serveur)
@@ -35,7 +35,25 @@ await Promise.all(modules.map(m => m.exports.register?.()));
// app/.zen/modules.client.js — AUTO-GÉNÉRÉ (client)
'use client';
import '@zen/module-posts/client';
export {};
export default function ZenModulesClient() {
return null;
}
```
```js
// app/layout.js — projet consommateur
import ZenModulesClient from './.zen/modules.client.js';
export default function RootLayout({ children }) {
return (
<html>
<body>
<ZenModulesClient />
{children}
</body>
</html>
);
}
```
Le manifeste client importe la **sous-entrée `./client`** de chaque module (et non son main entry). C'est essentiel : le main entry tire `createTables` / `registerApiRoutes` / la chaîne `register-server.js` qui dépend de `pg`, `fs`, `next/headers`, etc. — code serveur incompatible avec le bundle browser. Seuls les modules qui exposent `./client` dans leur `package.json#exports` sont inclus dans le manifeste client ; les modules purement back-end (API/DB) sont absents du bundle browser.