1 ---
2 name: smbcloud-deploy-nextjs
3 description: Use when deploying or debugging Next.js apps on smbCloud, especially the dedicated `nextjs-ssr` flow that builds locally, uploads `.next/standalone` via rsync, restarts PM2 over SSH, and serves traffic behind Nginx.
4 ---
5
6 # smbCloud Deploy Next.js
7
8 Use this skill when work touches Next.js deployment on smbCloud.
9
10 Applies to:
11
12 - `kind = "nextjs-ssr"` projects
13 - `.smb/config.toml` deploy configuration
14 - `process_deploy_nextjs_ssr.rs`
15 - PM2 process management for standalone builds
16 - Nginx proxying for SSR apps
17 - production-only issues caused by CORS, ports, or runtime mismatch
18
19 ## Deployment model
20
21 For `nextjs-ssr`, smbCloud does not use git-push deploy.
22
23 The CLI route in `process_deploy.rs` short-circuits on:
24
25 - `kind = "nextjs-ssr"`
26
27 That path builds locally, then:
28
29 - rsyncs `.next/standalone/` into the remote app directory root
30 - rsyncs `.next/static/` into `remote/.next/static/`
31 - rsyncs `public/` into `remote/public/`
32 - runs `ssh` and restarts the app with PM2
33
34 Do not describe this flow as git based unless the project is actually using the generic git deploy path.
35
36 ## Required app configuration
37
38 The Next.js app must produce a standalone server bundle.
39
40 Create `next.config.mjs` or equivalent with:
41
42 ```js
43 /** @type {import('next').NextConfig} */
44 const nextConfig = {
45 output: "standalone",
46 };
47
48 export default nextConfig;
49 ```
50
51 Without `output: "standalone"`, the deploy code fails because `.next/standalone/` is missing.
52
53 The app package scripts should support:
54
55 - `build`: `next build`
56
57 `pnpm` is the safe package manager for the current CLI implementation.
58
59 ## Required smbCloud config
60
61 Minimum `.smb/config.toml` for SSR:
62
63 ```toml
64 [project]
65 deployment_method = 1
66 kind = "nextjs-ssr"
67 source = "."
68 package_manager = "pnpm"
69 pm2_app = "my-app"
70 path = "apps/web/my-app"
71 ```
72
73 Important rules:
74
75 - `kind = "nextjs-ssr"` is what activates the SSR deploy path
76 - `source` is the local Next.js app directory
77 - `path` is the remote destination directory on the server
78 - `pm2_app` is mandatory because the CLI restarts PM2 by name
79 - `deployment_method = 1` may still be present, but the `kind` routing is what matters most
80
81 ## Expected remote layout
82
83 After a successful deploy, the remote directory should look like:
84
85 - `/home/git/apps/web/<app>/server.js`
86 - `/home/git/apps/web/<app>/.next/static/...`
87 - `/home/git/apps/web/<app>/public/...`
88
89 Note that `server.js` is expected at the remote root because the CLI uploads the contents of `.next/standalone/`, not the folder itself.
90
91 ## PM2 behavior
92
93 The current SSR deploy implementation does not use `ecosystem.config.js`.
94
95 It runs logic equivalent to:
96
97 ```sh
98 cd "$APP_PATH"
99
100 if pm2 describe "$PM2_APP" > /dev/null 2>&1; then
101 pm2 restart "$PM2_APP"
102 else
103 PORT=3010 HOSTNAME=127.0.0.1 pm2 start node --name "$PM2_APP" -- server.js
104 fi
105
106 pm2 save
107 ```
108
109 This has two consequences:
110
111 - old git-based `post-receive` hooks are irrelevant for the SSR path
112 - the port is currently hardcoded to `3010` in the CLI unless the code is patched
113
114 ### Manual PM2 operations
115
116 If the app is already deployed and `server.js` exists:
117
118 ```sh
119 cd /home/git/apps/web/<app>
120 pm2 restart <pm2_app> --update-env
121 pm2 save
122 ```
123
124 If starting from scratch:
125
126 ```sh
127 cd /home/git/apps/web/<app>
128 PORT=<port> HOSTNAME=127.0.0.1 pm2 start node --name <pm2_app> -- server.js
129 pm2 save
130 ```
131
132 ## Port management
133
134 Use one source of truth for the runtime port.
135
136 If an older `ecosystem.config.js` exists and contains both:
137
138 - `args: "start -p 3026"`
139 - `env_production.PORT = 3025`
140
141 that is a broken configuration. Standardize on one port value.
142
143 For the current SSR deploy code, the real runtime is controlled by the environment passed to `node server.js`, not by `next start` or an ecosystem file.
144
145 If the app must run on a port other than `3010`, patch the CLI or restart PM2 manually with the correct port.
146
147 ## Nginx for SSR
148
149 Do not use a static-site Nginx config like:
150
151 ```nginx
152 root /home/git/apps/web/my-app;
153 location / {
154 try_files $uri $uri/ =404;
155 }
156 ```
157
158 That is for static files, not Next.js SSR.
159
160 Use reverse proxying to the local Node process instead:
161
162 ```nginx
163 location / {
164 proxy_pass http://127.0.0.1:3026;
165 proxy_http_version 1.1;
166
167 proxy_set_header Host $host;
168 proxy_set_header X-Real-IP $remote_addr;
169 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
170 proxy_set_header X-Forwarded-Proto $scheme;
171
172 proxy_set_header Upgrade $http_upgrade;
173 proxy_set_header Connection "upgrade";
174
175 proxy_cache_bypass $http_upgrade;
176 }
177 ```
178
179 Nginx must proxy to the actual PM2 port.
180
181 ## Common failure modes
182
183 ### `.next/standalone` missing
184
185 Cause:
186
187 - no `output: "standalone"` in Next config
188
189 Fix:
190
191 - add standalone output
192 - rebuild locally
193
194 ### `server.js` missing on the server
195
196 Check:
197
198 - whether the SSR deploy path actually ran
199 - whether `path` points to the directory you inspected
200 - whether `rsync` uploaded to a different location
201
202 ### Missing `public/`
203
204 The current deploy code tries to rsync `public/` unconditionally.
205
206 If the project has no `public/` directory, deploy may fail. Either:
207
208 - create an empty `public/`
209 - or patch the CLI to skip missing directories
210
211 ### Wrong port after deploy
212
213 Cause:
214
215 - CLI hardcodes `PORT=3010` for fresh starts
216
217 Fix:
218
219 - manually restart with the correct port
220 - or patch the CLI to read a configurable port from `.smb/config.toml`
221
222 ### Misleading deploy output
223
224 The CLI may print generic deploy messages such as:
225
226 - `Deploying > Building the app`
227 - `App is running`
228
229 These messages do not mean git push happened. Confirm behavior from the code path, not the spinner text.
230
231 ## Auth and production-only web issues
232
233 If the Next.js app uses the wasm auth client from `smbcloud-auth-wasm`, production failures that show:
234
235 - `Network error. Please check your internet connection and try again.`
236
237 are often not actual connectivity failures.
238
239 Check these first:
240
241 - Rails CORS allows the production origin
242 - `Authorization` response header is exposed over CORS
243 - backend auth status codes match the Rust parser logic
244
245 ### Rails requirement
246
247 For browser clients using `POST /v1/client/users/sign_in`, if the token is returned in the `Authorization` response header, CORS must include:
248
249 - production origins such as `https://ondeinference.com`
250 - `expose: %w[... Authorization]`
251
252 Without that, browser fetch in wasm can surface as a fake network error.
253
254 ### Rust requirement
255
256 The shared Rust networking layer must not collapse all non-200 auth responses into `NetworkError`.
257
258 If backend returns meaningful auth errors such as `401` invalid password, update the parser in `request_login` to preserve the real failure.
259
260 ## Recommended validation
261
262 Use the smallest checks that prove the contract.
263
264 ### Next.js app
265
266 - `pnpm build`
267 - verify `.next/standalone/server.js` exists
268
269 ### smbcloud-cli
270
271 - `cargo check -p smbcloud-network`
272 - `cargo check -p cli` when deploy code changed
273
274 ### Server
275
276 - `pm2 list`
277 - `pm2 logs <pm2_app>`
278 - `ls -la /home/git/apps/web/<app>`
279 - `sudo nginx -t`
280
281 ## Common mistakes
282
283 - assuming `deployment_method = 1` alone activates SSR deploy
284 - forgetting `kind = "nextjs-ssr"`
285 - keeping an old git `post-receive` hook and expecting it to manage SSR deploys
286 - using static-file Nginx config for an SSR app
287 - trusting an old `ecosystem.config.js` when the CLI actually runs `node server.js`
288 - leaving port values inconsistent across PM2, Nginx, and CLI
289 - treating browser CORS failures as real internet outages