- replace top-level `import { cookies } from 'next/headers'` with lazy `await import('next/headers')` inside handler
- document the constraint in PROJECT.md: no top-level next/headers or next/navigation imports reachable from module register() chains
3.9 KiB
ZEN — Plan du projet
Une plateforme multi-usage construite sur l'essentiel, rien de plus, rien de moins.
ZEN est une plateforme admin Next.js qui sert de base à tout type d'application : site vitrine, système de facturation, espace cloud, boutique en ligne, ou n'importe quoi d'autre. L'admin est extensible, on y greffe ce qu'on veut, sans modifier le core.
Elle s'installe automatiquement dans n'importe quel projet Next.js via :
npx @zen/start
Le package principal est @zen/core. Il fournit toute l'infrastructure fondamentale : authentification, base de données, stockage, courriels, paiements, PDF, tâches planifiées, notifications. Chaque fonctionnalité est indépendante — les cores ne se connaissent pas entre eux. Le reste de la plateforme s'appuie sur chacun d'eux sans les coupler.
Principes directeurs
Core purity — Chaque core contient uniquement du code propre à son domaine. Aucune logique métier, aucune dépendance vers un autre core.
Minimal by default — Pas de fonctionnalité superflue. Si ce n'est pas nécessaire, ça n'existe pas.
Sécuritaire par défaut — Requêtes paramétrées, protection CSRF, limitation de débit, validation en entrée, erreurs opaques vers le client.
Performant — Connexions en pool, cache HTTP, génération différée des services.
Structure du projet
src/
core/ # Infrastructure fondamentale — la base de tout
features/ # Fonctionnalités de la palteforme utilisant les cores
shared/ # Utilitaires, composants et styles partagés
Résumé des règles absolues
| Domaine | Règle |
|---|---|
| API | Toutes les routes HTTP passent par core/api. Aucune autre route API. |
| Base de données | Tout accès DB passe par core/database. Jamais de requêtes directes. |
| Courriels | Tout envoi de courriel passe par core/email. |
| Cron | Toutes les tâches planifiées s'enregistrent dans core/cron. |
| Stockage | Tout accès fichier passe par core/storage. |
| Notifications | Un seul système de toast dans toute l'app : core/toast. |
| Authentification | Toute auth de site passe par features/auth. |
Pas de next/headers ni next/navigation au top-level dans la chaîne register() des modules
Tout fichier qui peut être atteint depuis le register() d'un module externe (directement ou transitivement, via un barrel @zen/core/* qu'il importe) ne doit pas importer next/headers ou next/navigation au top-level. L'import doit être lazy à l'intérieur du handler :
export async function handleSomething(request) {
const { cookies } = await import('next/headers');
// ...
}
Pourquoi. discover.server.js charge chaque module via import(/* turbopackIgnore */ name) pour empêcher Turbopack/Webpack de bundler un nom dynamique. Conséquence : Node résout tout l'arbre d'imports transitifs en ESM natif, sans la condition react-server. Or next/headers (Next.js 15+) n'est exposé que via cette condition — un import top-level échoue alors avec Cannot find module 'next/headers'.
Conséquences pratiques.
- Les fichiers qui ont besoin de Next.js au top-level (par ex.
features/admin/protect.js,features/auth/actions.js) ne sont jamais réexportés par les barrels accessibles aux modules ; ils sont exposés via des sous-chemins dédiés danspackage.json#exports(ex.@zen/core/features/admin/protect). - Pour les handlers qui restent dans un barrel exposé (ex.
core/api/router.js,core/storage/api.js),cookieset compagnie sont importés en lazy à l'intérieur du handler.
Avant d'ajouter un import { cookies } from 'next/headers' à un fichier core, vérifier qu'aucun barrel exposé aux modules ne le tire transitivement. Sinon, garder l'import lazy.