From 6b3bb6a4ee53c598a26716ddb62243fc685dd257 Mon Sep 17 00:00:00 2001 From: Hyko Date: Sat, 25 Apr 2026 13:08:14 -0400 Subject: [PATCH] fix(storage): make next/headers import lazy in api.js to avoid module resolution failure - 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 --- docs/PROJECT.md | 22 ++++++++++++++++++++++ src/core/storage/api.js | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/PROJECT.md b/docs/PROJECT.md index 9433ebe..f8dffaf 100644 --- a/docs/PROJECT.md +++ b/docs/PROJECT.md @@ -47,3 +47,25 @@ src/ | 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 : + +```js +export async function handleSomething(request) { + const { cookies } = await import('next/headers'); + // ... +} +``` + +**Pourquoi.** [`discover.server.js`](../src/core/modules/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`](../src/features/admin/protect.js), [`features/auth/actions.js`](../src/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 dans `package.json#exports` (ex. `@zen/core/features/admin/protect`). +- Pour les handlers qui restent dans un barrel exposé (ex. [`core/api/router.js`](../src/core/api/router.js), [`core/storage/api.js`](../src/core/storage/api.js)), `cookies` et 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. \ No newline at end of file diff --git a/src/core/storage/api.js b/src/core/storage/api.js index 903ea2e..d0295f1 100644 --- a/src/core/storage/api.js +++ b/src/core/storage/api.js @@ -15,7 +15,6 @@ * registerFeatureRoutes in core/api/runtime.js. */ -import { cookies } from 'next/headers'; import { getSessionCookieName } from '@zen/core/shared/config'; import { getSessionResolver } from '../api/router.js'; import { getFile } from './index.js'; @@ -60,6 +59,7 @@ async function handleGetFile(_request, { wildcard: fileKey }) { } // Require authentication for all other paths. + const { cookies } = await import('next/headers'); const cookieStore = await cookies(); const sessionToken = cookieStore.get(COOKIE_NAME)?.value;