Require Next.js standalone output in website AI prompt and deploy.
Document mandatory output: "standalone" for shared-VM RAM, and align websites-agent deploy to prefer .next/standalone/server.js. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b1dcecc468
commit
4823cf97f6
@@ -31,6 +31,23 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
|||||||
9. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
9. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
||||||
10. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
10. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
||||||
11. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
11. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
||||||
|
12. **Production runtime (required):** Meshkee hosts many Next.js storefronts on one shared websites VM. Every Next site **must** set `output: "standalone"` in `next.config` (`.ts` / `.mjs` / `.js`). Deploy detects `.next/standalone/server.js`, points PM2 at that `server.js`, and **deletes the full `node_modules`**. Target RSS is **~80–120 MB**. Do **not** ship `next start` with a full `node_modules` runtime (that uses ~150–500+ MB and OOMs the host). Do **not** use `output: "export"` unless the project explicitly asks for a static export. Vinext apps (`vinext` in `package.json` / `vinext start`) are a separate intentional stack — do not pretend they use Next standalone.
|
||||||
|
|
||||||
|
### Production runtime (Next.js)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
// Leaner production runtime: deploy uses .next/standalone and drops node_modules.
|
||||||
|
output: "standalone",
|
||||||
|
// ...images, rewrites, etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
|
```
|
||||||
|
|
||||||
|
After changing `next.config`, commit, push, and redeploy so PM2 switches to standalone. A correct deploy log says `standalone build detected` / `deploy ok (standalone)`; PM2 script is `.next/standalone/server.js`, not `node_modules/next/dist/bin/next`.
|
||||||
|
|
||||||
### Typical bootstrap sequence
|
### Typical bootstrap sequence
|
||||||
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
||||||
@@ -311,4 +328,4 @@ If OpenAPI and this brief conflict, **OpenAPI wins**.
|
|||||||
|
|
||||||
## What to tell each website team
|
## What to tell each website team
|
||||||
|
|
||||||
Replace `<WEBSITE_DOMAIN>` once per project. Everything else is global — same Postman, same OpenAPI, same base URL.
|
Replace `<WEBSITE_DOMAIN>` once per project. Everything else is global — same Postman, same OpenAPI, same base URL. Remind them: **`output: "standalone"` is mandatory** for Meshkee Next deploys (shared VM RAM).
|
||||||
|
|||||||
@@ -23,3 +23,15 @@
|
|||||||
# deploy.sh checks out origin/HEAD (falls back to main, then master).
|
# deploy.sh checks out origin/HEAD (falls back to main, then master).
|
||||||
# If the site builds with Next `output: 'standalone'`, deploy copies static/public
|
# If the site builds with Next `output: 'standalone'`, deploy copies static/public
|
||||||
# into `.next/standalone`, points PM2 at `server.js`, and removes full `node_modules`.
|
# into `.next/standalone`, points PM2 at `server.js`, and removes full `node_modules`.
|
||||||
|
# Vinext apps (`vinext` in package.json / `vinext start`) have no `.next` production
|
||||||
|
# build — deploy runs `vinext start` against `dist/` instead of `next start`.
|
||||||
|
# A build that produces neither standalone, `.next/BUILD_ID`, nor Vinext `dist/`
|
||||||
|
# is marked failed (avoids nginx 502 from a crash-looping `next start`).
|
||||||
|
#
|
||||||
|
# REQUIRED for all Meshkee Next storefronts: next.config must include
|
||||||
|
# output: "standalone"
|
||||||
|
# Without it, PM2 runs `next start` + full node_modules (~150–500MB RSS each) and
|
||||||
|
# the shared websites VM OOMs. Target ~80–120MB RSS per site via standalone.
|
||||||
|
# Paste for website-building agents:
|
||||||
|
# "Always set output: 'standalone' in next.config. Deploy expects .next/standalone
|
||||||
|
# and deletes node_modules. Never ship next start / full node_modules runtime."
|
||||||
|
|||||||
@@ -212,16 +212,45 @@ NODE
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "no standalone output — using next start (legacy)"
|
# Vinext (Vite production server) or legacy `next start`.
|
||||||
|
# Detect Vinext from package.json / CLI — do not assume every Next-looking
|
||||||
|
# app has a `.next` production build (vinext writes `dist/` instead).
|
||||||
|
uses_vinext=false
|
||||||
|
if node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const p = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||||
|
const d = Object.assign({}, p.dependencies || {}, p.devDependencies || {});
|
||||||
|
const s = String((p.scripts || {}).start || "");
|
||||||
|
const ok = Boolean(d.vinext) || s.includes("vinext") || fs.existsSync("node_modules/vinext/dist/cli.js");
|
||||||
|
process.exit(ok ? 0 : 1);
|
||||||
|
'; then
|
||||||
|
uses_vinext=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $uses_vinext; then
|
||||||
|
if [[ ! -d dist ]]; then
|
||||||
|
echo "vinext project but dist/ is missing — build did not produce a production bundle"
|
||||||
|
write_status "failed" "vinext build produced no dist/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "no Next standalone output — using vinext start"
|
||||||
|
elif [[ -f .next/BUILD_ID ]]; then
|
||||||
|
echo "no standalone output — using next start (legacy)"
|
||||||
|
else
|
||||||
|
echo "build produced neither .next/standalone, Vinext dist/, nor .next/BUILD_ID"
|
||||||
|
write_status "failed" "no production server output (need Next standalone, BUILD_ID, or vinext dist/)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Reset cwd/script in case a previous deploy used standalone. Do not
|
# Reset cwd/script in case a previous deploy used standalone. Do not
|
||||||
# `pm2 restart --update-env`: the deploy agent has PORT=9050 and that
|
# `pm2 restart --update-env`: the deploy agent has PORT=9050 and that
|
||||||
# leaks into the site, so nginx (site port) gets 502.
|
# leaks into the site, so nginx (site port) gets 502.
|
||||||
SLUG="$SLUG" ROOT="$ROOT" ECOSYSTEM="$ECOSYSTEM" node <<'NODE'
|
VINEXT="$uses_vinext" SLUG="$SLUG" ROOT="$ROOT" ECOSYSTEM="$ECOSYSTEM" node <<'NODE'
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = process.env.ECOSYSTEM;
|
const path = process.env.ECOSYSTEM;
|
||||||
const slug = process.env.SLUG;
|
const slug = process.env.SLUG;
|
||||||
const root = process.env.ROOT;
|
const root = process.env.ROOT;
|
||||||
|
const usesVinext = process.env.VINEXT === 'true';
|
||||||
let cfg = { apps: [] };
|
let cfg = { apps: [] };
|
||||||
try {
|
try {
|
||||||
delete require.cache[require.resolve(path)];
|
delete require.cache[require.resolve(path)];
|
||||||
@@ -243,7 +272,9 @@ const port =
|
|||||||
[fromEnv, fromArgs].find((p) => Number.isFinite(p) && p > 0 && p !== 9050) || 3005;
|
[fromEnv, fromArgs].find((p) => Number.isFinite(p) && p > 0 && p !== 9050) || 3005;
|
||||||
|
|
||||||
app.cwd = root;
|
app.cwd = root;
|
||||||
app.script = 'node_modules/next/dist/bin/next';
|
app.script = usesVinext
|
||||||
|
? 'node_modules/vinext/dist/cli.js'
|
||||||
|
: 'node_modules/next/dist/bin/next';
|
||||||
app.args = 'start --hostname 127.0.0.1 --port ' + port;
|
app.args = 'start --hostname 127.0.0.1 --port ' + port;
|
||||||
app.env = {
|
app.env = {
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
@@ -276,13 +307,25 @@ lines.push(' ],');
|
|||||||
lines.push('};');
|
lines.push('};');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
fs.writeFileSync(path, lines.join('\n'));
|
fs.writeFileSync(path, lines.join('\n'));
|
||||||
console.log('ecosystem updated for next start:', slug, 'port', port);
|
console.log(
|
||||||
|
'ecosystem updated for',
|
||||||
|
usesVinext ? 'vinext start' : 'next start',
|
||||||
|
':',
|
||||||
|
slug,
|
||||||
|
'port',
|
||||||
|
port,
|
||||||
|
);
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
pm2 delete "$SLUG" || true
|
pm2 delete "$SLUG" || true
|
||||||
pm2 start "$ECOSYSTEM" --only "$SLUG"
|
pm2 start "$ECOSYSTEM" --only "$SLUG"
|
||||||
pm2 save || true
|
pm2 save || true
|
||||||
|
|
||||||
echo "==== $(date -u +%Y-%m-%dT%H:%M:%SZ) deploy ok: $SLUG ===="
|
if $uses_vinext; then
|
||||||
write_status "success" "Deployed origin/$BRANCH"
|
echo "==== $(date -u +%Y-%m-%dT%H:%M:%SZ) deploy ok (vinext): $SLUG ===="
|
||||||
|
write_status "success" "Deployed origin/$BRANCH (vinext)"
|
||||||
|
else
|
||||||
|
echo "==== $(date -u +%Y-%m-%dT%H:%M:%SZ) deploy ok: $SLUG ===="
|
||||||
|
write_status "success" "Deployed origin/$BRANCH"
|
||||||
|
fi
|
||||||
trap - ERR
|
trap - ERR
|
||||||
|
|||||||
@@ -31,6 +31,23 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
|||||||
9. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
9. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
||||||
10. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
10. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
||||||
11. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
11. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
||||||
|
12. **Production runtime (required):** Meshkee hosts many Next.js storefronts on one shared websites VM. Every Next site **must** set `output: "standalone"` in `next.config` (`.ts` / `.mjs` / `.js`). Deploy detects `.next/standalone/server.js`, points PM2 at that `server.js`, and **deletes the full `node_modules`**. Target RSS is **~80–120 MB**. Do **not** ship `next start` with a full `node_modules` runtime (that uses ~150–500+ MB and OOMs the host). Do **not** use `output: "export"` unless the project explicitly asks for a static export. Vinext apps (`vinext` in `package.json` / `vinext start`) are a separate intentional stack — do not pretend they use Next standalone.
|
||||||
|
|
||||||
|
### Production runtime (Next.js)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
// Leaner production runtime: deploy uses .next/standalone and drops node_modules.
|
||||||
|
output: "standalone",
|
||||||
|
// ...images, rewrites, etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
|
```
|
||||||
|
|
||||||
|
After changing `next.config`, commit, push, and redeploy so PM2 switches to standalone. A correct deploy log says `standalone build detected` / `deploy ok (standalone)`; PM2 script is `.next/standalone/server.js`, not `node_modules/next/dist/bin/next`.
|
||||||
|
|
||||||
### Typical bootstrap sequence
|
### Typical bootstrap sequence
|
||||||
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
||||||
@@ -311,4 +328,4 @@ If OpenAPI and this brief conflict, **OpenAPI wins**.
|
|||||||
|
|
||||||
## What to tell each website team
|
## What to tell each website team
|
||||||
|
|
||||||
Replace `<WEBSITE_DOMAIN>` once per project. Everything else is global — same Postman, same OpenAPI, same base URL.
|
Replace `<WEBSITE_DOMAIN>` once per project. Everything else is global — same Postman, same OpenAPI, same base URL. Remind them: **`output: "standalone"` is mandatory** for Meshkee Next deploys (shared VM RAM).
|
||||||
|
|||||||
Reference in New Issue
Block a user