You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .agents/skills/smbcloud-deploy-monorepo/SKILL.md
+22-6Lines changed: 22 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -180,7 +180,7 @@ smb deploy --project <name>
180
180
181
181
### Strategy: `nextjs-ssr`
182
182
183
-
Local build, rsync three directories, then SSH delete + fresh PM2 start (prefer server `ecosystem.config.js` when present). See `smbcloud-deploy-nextjs` skill for details.
183
+
Local build, rsync three directories, then SSH delete + fresh PM2 start (prefer server `ecosystem.config.cjs` when present; falls back to legacy `.js`). The `.next/standalone/` upload should use `--copy-links` so `pnpm` symlinked dependencies land as real files in the deployed tree. If `outputFileTracingRoot` points above the sub-project directory, Next may preserve the sub-project path inside standalone output, and the deploy path must place `.next/static` and `public` under that nested runtime directory. See `smbcloud-deploy-nextjs` skill for details.
- fall back to `pm2 start ecosystem.config.js --only <app> --env production` if only the legacy `.js` file exists
455
463
- otherwise fall back to `PORT=<port> HOSTNAME=127.0.0.1 pm2 start node --name <app> -- server.js`
456
464
- `pm2 save`
457
465
466
+
467
+
When a monorepo root `package.json` has `"type": "module"`, PM2 cannot load a `.js` ecosystem config because Node treats it as ESM. The CLI auto-migrates `.js` → `.cjs` on the server during deploy and checks `.cjs` first when starting PM2.
468
+
458
469
This matches the standalone Next.js deploy model and avoids stale PM2 command state from older deploy methods.
459
470
460
471
## Deployment tracking
@@ -527,12 +538,16 @@ Two `nextjs-ssr` apps have the same `port` value. PM2 will start both, but only
527
538
528
539
The Nginx `alias` directive for `/_next/static/` or the `root` for a Vite SPA points to the wrong directory or a stale deploy path. Verify the Nginx config matches the `path` field in `.smb/config.toml`.
529
540
541
+
### `Cannot find module 'next'` after Next.js SSR deploy
542
+
543
+
The remote app directory still contains broken `pnpm` symlinks from `.next/standalone/`, or the standalone output itself is missing traced runtime files. Use the current CLI or CI flow with `rsync --copy-links` for the standalone upload, verify `node .next/standalone/server.js` works locally, then redeploy and inspect `/home/git/apps/web/<app>/node_modules/next` on the server.
544
+
530
545
### PM2 not picking up new environment variables
531
546
532
547
Current CLI parity is delete + fresh start. Check these in order:
533
548
534
-
1. the server's `ecosystem.config.js` was edited, not just the repo copy
535
-
2. the deploy script is actually starting from `ecosystem.config.js`
549
+
1. the server's `ecosystem.config.cjs` (or legacy `.js`) was edited, not just the repo copy
550
+
2. the deploy script is actually starting from `ecosystem.config.cjs` — if only a `.js` file exists and `"type": "module"` is set in `package.json`, PM2 will fail; rename to `.cjs`
536
551
3. `pm2 save` ran after the fresh start
537
552
538
553
If you are changing env manually outside the deploy flow, `pm2 restart <app> --update-env && pm2 save` is still a valid operator command.
@@ -567,6 +582,7 @@ If you are changing env manually outside the deploy flow, `pm2 restart <app> --u
567
582
- using `kind = "nextjs-ssr"` on a project without `output: "standalone"` in its Next.js config
568
583
- running `smb deploy` from a subdirectory instead of the monorepo root — `source` paths will not resolve
569
584
- forgetting `--mkpath` on rsync in CI workflows — first deploys fail because the remote directory does not exist
585
+
- forgetting `--copy-links` on `.next/standalone/` uploads — the server receives broken `pnpm` symlinks and PM2 crashes with `Cannot find module 'next'`
570
586
- keeping stale Nginx configs pointing at old deploy paths after migrating to a new directory structure
571
587
- mixing up `path` (remote destination) and `source` (local directory) — they serve different purposes
572
588
- treating the root workspace id as the deployable app id
Copy file name to clipboardExpand all lines: .agents/skills/smbcloud-deploy-nextjs/SKILL.md
+89-27Lines changed: 89 additions & 27 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,10 +36,12 @@ The CLI route in `process_deploy.rs` short-circuits on:
36
36
That path builds locally, then:
37
37
38
38
- rsyncs `.next/standalone/` into the remote app directory root
39
-
- rsyncs `.next/static/` into `remote/.next/static/`
40
-
- rsyncs `public/` into `remote/public/`
39
+
- rsyncs `.next/static/` into the runtime directory's `.next/static/`
40
+
- rsyncs `public/` into the runtime directory's `public/`
41
41
- runs `ssh` and restarts the app with PM2
42
42
43
+
When `outputFileTracingRoot` points above the app directory, Next may preserve the source path inside `.next/standalone/` (for example `.next/standalone/web/server.js`). The deploy path must keep that nested runtime layout intact.
44
+
43
45
Do not describe this flow as git based unless the project is actually using the generic git deploy path.
44
46
45
47
## Required app configuration
@@ -97,18 +99,26 @@ Important rules:
97
99
98
100
## Expected remote layout
99
101
100
-
After a successful deploy, the remote directory should look like:
102
+
After a successful deploy, the remote directory should look like one of these layouts:
101
103
102
-
-`/home/git/apps/web/<app>/server.js`
103
-
-`/home/git/apps/web/<app>/.next/static/...`
104
-
-`/home/git/apps/web/<app>/public/...`
104
+
- flat runtime
105
+
-`/home/git/apps/web/<app>/server.js`
106
+
-`/home/git/apps/web/<app>/.next/static/...`
107
+
-`/home/git/apps/web/<app>/public/...`
108
+
- nested runtime when standalone preserves the source path
109
+
-`/home/git/apps/web/<app>/node_modules/...`
110
+
-`/home/git/apps/web/<app>/web/server.js`
111
+
-`/home/git/apps/web/<app>/web/.next/static/...`
112
+
-`/home/git/apps/web/<app>/web/public/...`
105
113
106
-
Note that `server.js` is expected at the remote root because the CLI uploads the contents of `.next/standalone/`, not the folder itself.
114
+
The CLI uploads the contents of `.next/standalone/`, not the folder itself. In nested-runtime mode it also needs to keep PM2 compatibility for old root-level `server.js` and `.next/standalone/server.js` entrypoints.
107
115
108
116
## PM2 behavior
109
117
110
118
The CLI's deploy path now deletes and starts fresh, preferring the server's
111
-
`ecosystem.config.js` when it exists.
119
+
ecosystem config file when it exists. The CLI checks for `ecosystem.config.cjs`
120
+
first, then falls back to `ecosystem.config.js`. During deploy, the server-side
121
+
migration auto-renames `.js` to `.cjs` when applicable.
112
122
113
123
Conceptually it behaves like:
114
124
@@ -120,7 +130,9 @@ if pm2 describe "$PM2_APP" > /dev/null 2>&1; then
120
130
pm2 delete "$PM2_APP"
121
131
fi
122
132
123
-
if [ -f ecosystem.config.js ];then
133
+
if [ -f ecosystem.config.cjs ];then
134
+
pm2 start ecosystem.config.cjs --only "$PM2_APP" --env production
135
+
elif [ -f ecosystem.config.js ];then
124
136
pm2 start ecosystem.config.js --only "$PM2_APP" --env production
The server runs in a **multi-tenant setup**: multiple Next.js apps share one server, each managed as a separate PM2 process. The standard way to manage per-app environment variables is an `ecosystem.config.cjs` file kept **on the server only** (never committed to the app repo).
139
153
140
-
The server runs in a **multi-tenant setup**: multiple Next.js apps share one server, each managed as a separate PM2 process. The standard way to manage per-app environment variables is an `ecosystem.config.js`file kept **on the server only** (never committed to the app repo).
154
+
Use the `.cjs` extension. This is required when the project's `package.json` has `"type": "module"`, because Node treats `.js`files as ESM in that case and `module.exports` syntax will fail.
141
155
142
156
The flow works like this:
143
157
@@ -147,7 +161,7 @@ The flow works like this:
147
161
ssh git@<server>
148
162
cd /home/git/apps/web/<app>
149
163
150
-
cat > ecosystem.config.js<< 'EOF'
164
+
cat > ecosystem.config.cjs<< 'EOF'
151
165
module.exports = {
152
166
apps: [{
153
167
name: "<pm2_app>",
@@ -163,20 +177,20 @@ The flow works like this:
163
177
}
164
178
EOF
165
179
166
-
pm2 start ecosystem.config.js --env production
180
+
pm2 start ecosystem.config.cjs --env production
167
181
pm2 save
168
182
```
169
183
170
-
2. Every subsequent CLI deploy deletes the PM2 process and starts it fresh. If the server has `ecosystem.config.js`, the CLI starts PM2 from that file again. If not, it falls back to `node server.js` with inline `NODE_ENV`, `PORT`, and `HOSTNAME`.
184
+
2. Every subsequent CLI deploy deletes the PM2 process and starts it fresh. The CLI checks for `ecosystem.config.cjs` first, then `ecosystem.config.js`. If neither exists, it falls back to `node server.js` with inline `NODE_ENV`, `PORT`, and `HOSTNAME`.
171
185
172
-
3. To change or add env vars, SSH in, edit `ecosystem.config.js`, then:
186
+
3. To change or add env vars, SSH in, edit `ecosystem.config.cjs`, then:
173
187
174
188
```sh
175
189
pm2 restart <pm2_app> --update-env
176
190
pm2 save
177
191
```
178
192
179
-
The ecosystem file is the server-side source of truth for each app's runtime config. The CLI preserves the server copy during rsync and must not delete it.
193
+
The ecosystem file is the server-side source of truth for each app's runtime config. The CLI preserves both `ecosystem.config.cjs` and `ecosystem.config.js` on the server during rsync and must not delete either.
If an older `ecosystem.config.js` exists and contains both:
220
+
If an older ecosystemconfig (`.cjs` or `.js`) exists and contains both:
207
221
208
222
- `args: "start -p 3026"`
209
223
- `env_production.PORT = 3025`
@@ -213,7 +227,7 @@ that is a broken configuration. Standardize on one port value.
213
227
Current rules:
214
228
215
229
- fallback fresh-start path uses `port` from `.smb/config.toml`
216
-
- ecosystem-managed apps should set the same port in `env_production.PORT`
230
+
- ecosystem-managed apps should set the same port in `env_production.PORT` (in `ecosystem.config.cjs`)
217
231
- nginx upstream must match that port
218
232
219
233
## Nginx for SSR
@@ -273,24 +287,67 @@ Check:
273
287
274
288
### Missing `public/`
275
289
276
-
The current deploy code tries to rsync `public/` unconditionally.
290
+
`public/` is optional. The current deploy code skips it when the directory does not exist.
291
+
292
+
If assets are missing in production, check whether the app actually has a `public/` directory locally and whether the expected files were copied into the remote `public/` directory.
293
+
294
+
### `Cannot find module 'next'` or `502 Bad Gateway`
- rsync preserved the symlinks instead of copying their targets
300
+
- or the standalone output itself is incomplete and missing traced runtime files
301
+
- PM2 starts `server.js`, Node cannot resolve `next`, and nginx returns `502 Bad Gateway`
302
+
303
+
Fix:
304
+
305
+
- use the dedicated `nextjs-ssr` CLI path in current `smbcloud-cli`
306
+
- make sure the standalone upload uses `rsync --copy-links`
307
+
- verify `node .next/standalone/server.js` works locally before deploy
308
+
- if local standalone fails too, add `outputFileTracingIncludes` for the missing runtime files and rebuild
309
+
- then restart PM2 and recheck `pm2 logs <pm2_app>`
310
+
311
+
### `Cannot find module 'styled-jsx'`, `@swc/helpers`, or other pnpm peer deps
277
312
278
-
If the project has no `public/` directory, deploy may fail. Either:
313
+
Cause:
314
+
315
+
- pnpm stores peer dependencies inside `node_modules/.pnpm/<pkg>@<version>/node_modules/<pkg>` with internal symlinks that Node's standard `require.resolve()` cannot follow
316
+
- Next.js standalone traces the files into `.pnpm/` correctly, but does not create the flat `node_modules/<pkg>` entries needed for resolution
317
+
- this affects `styled-jsx`, `@swc/helpers`, `@next/env`, and other packages that `next` requires at startup
318
+
319
+
Fix:
279
320
280
-
- create an empty `public/`
281
-
- or patch the CLI to skip missing directories
321
+
- the CLI deploy script now auto-hoists: after uploading standalone output, it scans `node_modules/.pnpm/*/node_modules/*` and creates symlinks at `node_modules/<pkg>` for any packages not already present
322
+
- this restores the resolution structure that pnpm normally provides via its `.pnpm/node_modules/` virtual directory
323
+
- no manual intervention needed for new deploys
324
+
- to fix a server manually: `find node_modules/.pnpm -mindepth 2 -maxdepth 2 -type d -name node_modules` and symlink each child into the root `node_modules/`
282
325
283
326
### Wrong port after deploy
284
327
285
328
Cause:
286
329
287
-
- `port` in `.smb/config.toml`, server `ecosystem.config.js`, and nginx upstream disagree
330
+
- `port` in `.smb/config.toml`, server ecosystemconfig (`.cjs` or `.js`), and nginx upstream disagree
288
331
289
332
Fix:
290
333
291
334
- standardize the same port in all three places
292
335
- if using ecosystem config, make sure PM2 is actually starting from that file
293
336
337
+
### PM2 "module is not defined" or "require is not defined"
338
+
339
+
Cause:
340
+
341
+
- the project's `package.json` has `"type": "module"`, which makes Node treat `.js` files as ESM
342
+
- `ecosystem.config.js` uses `module.exports` (CommonJS), which is invalid under ESM
343
+
- deploy shims that use `require()` will also fail under ESM
344
+
345
+
Fix:
346
+
347
+
- rename `ecosystem.config.js` to `ecosystem.config.cjs` on the server
348
+
- deploy shims must use `import()` not `require()`
349
+
- the CLI now handles both automatically: it checks for `.cjs` first and the server-side migration auto-renames `.js` to `.cjs` during deploy
350
+
294
351
### Misleading deploy output
295
352
296
353
The CLI may print generic deploy messages such as:
@@ -336,7 +393,9 @@ Use the smallest checks that prove the contract.
336
393
### Next.js app
337
394
338
395
- `pnpm build`
339
-
- verify `.next/standalone/server.js` exists
396
+
- verify standalone contains a runnable server entrypoint
397
+
- if `.next/standalone/server.js` exists, verify `node .next/standalone/server.js` starts locally
398
+
- if standalone preserves the source path, verify `node .next/standalone/<source>/server.js` starts locally
340
399
341
400
### smbcloud-cli
342
401
@@ -348,6 +407,7 @@ Use the smallest checks that prove the contract.
@@ -356,8 +416,10 @@ Use the smallest checks that prove the contract.
356
416
- forgetting `kind = "nextjs-ssr"`
357
417
- keeping an old git `post-receive` hook and expecting it to manage SSR deploys
358
418
- using static-file Nginx config for an SSR app
359
-
- editing the repo copy of `ecosystem.config.js` and expecting a normal CLI deploy to overwrite the server copy — it is preserved intentionally
360
-
- editing the server `ecosystem.config.js` but the deploy script falling back to inline `node server.js` because the file path check is wrong
419
+
- rsyncing `.next/standalone/` without `--copy-links` and shipping broken `pnpm` symlinks to the server
420
+
- editing the repo copy of `ecosystem.config.cjs` and expecting a normal CLI deploy to overwrite the server copy — it is preserved intentionally
421
+
- editing the server `ecosystem.config.cjs` but the deploy script falling back to inline `node server.js` because the file path check is wrong
422
+
- keeping an `ecosystem.config.js` when the project uses ESM (`"type": "module"` in `package.json`) — rename it to `.cjs` or let the CLI migration handle it
361
423
- leaving port values inconsistent across PM2, Nginx, and `.smb/config.toml`
362
424
- forgetting to set `frontend_app_id` on the deploy target once app-level deployment tracking is available
363
425
- treating browser CORS failures as real internet outages
0 commit comments