with one click
fix-broken-next-symlinks
// Fix broken symlinks in .next/node_modules/ — diagnose, decide allowlist vs dependencies, and verify
// Fix broken symlinks in .next/node_modules/ — diagnose, decide allowlist vs dependencies, and verify
Investigate a GitHub issue - fetch info, update labels, analyze code/reproduce, report findings, and optionally fix. Usage: /investigate-issue <issue-url-or-number>
GROWI main application (apps/app) specific commands and scripts. Auto-invoked when working in apps/app.
Auto-invoked when modifying page transition logic, global atom hydration, or the `[[...path]]` dynamic route. Explains the data flow from SSR/client navigation to page rendering, and the hydration-vs-subsequent-sync rule for global atoms (`currentPathnameAtom`, `currentUserAtom`, `isMaintenanceModeAtom`).
Organize and clean up specification documents after implementation completion. Removes implementation details while preserving essential context for future refactoring.
Initialize a new specification with detailed project description
Quick spec generation with interactive or automatic mode
| name | fix-broken-next-symlinks |
| description | Fix broken symlinks in .next/node_modules/ — diagnose, decide allowlist vs dependencies, and verify |
This document is a mandatory step-by-step procedure. When fixing broken symlinks, execute every step in order. In particular, verification always requires the full 3-command sequence: build → assemble-prod.sh → check-next-symlinks.sh. Never skip assemble-prod.sh — the symlink check is only meaningful after production assembly.
Turbopack externalizes packages into .next/node_modules/ as symlinks, even for packages imported only via dynamic import() inside useEffect. After assemble-prod.sh runs pnpm deploy --prod, devDependencies are excluded, breaking those symlinks. check-next-symlinks.sh detects these and fails the build.
turbo run build --filter @growi/app
bash apps/app/bin/assemble-prod.sh
bash apps/app/bin/check-next-symlinks.sh
If the check reports BROKEN: apps/app/.next/node_modules/<package>-<hash>, proceed to Step 2.
Search all import sites of the broken package:
grep -rn "from ['\"]<package-name>['\"]" apps/app/src/
grep -rn "import(['\"]<package-name>['\"])" apps/app/src/
Apply the decision tree:
Is the package imported ONLY via:
- `import type { ... } from 'pkg'` (erased at compile time)
- `await import('pkg')` inside useEffect / event handler (client-side only, never SSR)
YES → Add to ALLOWED_BROKEN in check-next-symlinks.sh (Step 3a)
NO → Move from devDependencies to dependencies (Step 3b)
Edit apps/app/bin/check-next-symlinks.sh:
ALLOWED_BROKEN=(
fslightbox-react
@emoji-mart/data
@emoji-mart/react
socket.io-client
<new-package> # <-- add here
)
Use the bare package name (e.g., socket.io-client), not the hashed symlink name (socket.io-client-46e5ba4d4c848156).
In apps/app/package.json, move the package from devDependencies to dependencies, then run pnpm install.
Re-run the full sequence:
turbo run build --filter @growi/app
bash apps/app/bin/assemble-prod.sh
bash apps/app/bin/check-next-symlinks.sh
Expected output: OK: All apps/app/.next/node_modules symlinks resolve correctly.
socket.io-client is used in two files:
src/states/socket-io/global-socket.ts — import type + await import() inside useEffectsrc/features/admin/states/socket-io.ts — import type + import() inside useEffectBoth are client-only dynamic imports → added to ALLOWED_BROKEN, stays as devDependencies.
check-next-symlinks.sh reports BROKEN: apps/app/.next/node_modules/<package>-<hash>