Support Next.js standalone runtime in websites deploy agent.

Copy static/public into the standalone bundle, point PM2 at server.js, and drop full node_modules after a successful start.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-13 20:31:39 +03:30
co-authored by Cursor
parent 62f75c3a75
commit f201a6408c
+92 -1
View File
@@ -10,6 +10,7 @@ fi
ROOT="/var/www/sites/$SLUG"
LOG="/var/log/websites/deploy-$SLUG.log"
STATUS="/var/log/websites/deploy-$SLUG.status.json"
ECOSYSTEM="/var/www/sites/ecosystem.config.cjs"
exec >>"$LOG" 2>&1
write_status() {
@@ -78,7 +79,97 @@ fi
npm ci
npm run build
pm2 restart "$SLUG" --update-env || pm2 start /var/www/sites/ecosystem.config.cjs --only "$SLUG"
# Next.js standalone: copy static assets Next does not place in standalone/, then
# point PM2 at server.js and drop the full node_modules tree.
if [[ -f .next/standalone/server.js ]]; then
echo "standalone build detected — preparing runtime bundle"
mkdir -p .next/standalone/.next
rm -rf .next/standalone/.next/static
cp -a .next/static .next/standalone/.next/static
if [[ -d public ]]; then
rm -rf .next/standalone/public
cp -a public .next/standalone/public
fi
SLUG="$SLUG" ROOT="$ROOT" ECOSYSTEM="$ECOSYSTEM" node <<'NODE'
const fs = require('fs');
const path = process.env.ECOSYSTEM;
const slug = process.env.SLUG;
const root = process.env.ROOT;
let cfg = { apps: [] };
try {
delete require.cache[require.resolve(path)];
cfg = require(path);
if (!Array.isArray(cfg.apps)) cfg.apps = [];
} catch (_) {
cfg = { apps: [] };
}
const app = cfg.apps.find((a) => a.name === slug);
if (!app) {
console.error('ecosystem entry missing for', slug);
process.exit(1);
}
const port =
(app.env && Number(app.env.PORT)) ||
(String(app.args || '').match(/--port\s+(\d+)/) || [])[1] ||
3005;
app.cwd = root + '/.next/standalone';
app.script = 'server.js';
app.args = '';
app.env = {
NODE_ENV: 'production',
PORT: String(port),
HOSTNAME: '127.0.0.1',
};
const lines = ['module.exports = {', ' apps: ['];
cfg.apps.forEach((entry, idx) => {
lines.push(' {');
lines.push(` name: ${JSON.stringify(entry.name)},`);
lines.push(` cwd: ${JSON.stringify(entry.cwd)},`);
lines.push(` script: ${JSON.stringify(entry.script)},`);
if (entry.args) {
lines.push(` args: ${JSON.stringify(entry.args)},`);
}
lines.push(' env: {');
lines.push(` NODE_ENV: ${JSON.stringify(entry.env.NODE_ENV)},`);
lines.push(` PORT: ${JSON.stringify(entry.env.PORT)},`);
if (entry.env.HOSTNAME) {
lines.push(` HOSTNAME: ${JSON.stringify(entry.env.HOSTNAME)},`);
}
lines.push(' },');
lines.push(` error_file: ${JSON.stringify(entry.error_file)},`);
lines.push(` out_file: ${JSON.stringify(entry.out_file)},`);
lines.push(' time: true,');
lines.push(idx === cfg.apps.length - 1 ? ' }' : ' },');
});
lines.push(' ],');
lines.push('};');
lines.push('');
fs.writeFileSync(path, lines.join('\n'));
console.log('ecosystem updated for standalone:', slug, 'port', port);
NODE
# Full delete+start so PM2 picks up new cwd/script (restart alone keeps old path).
pm2 delete "$SLUG" || true
pm2 start "$ECOSYSTEM" --only "$SLUG"
pm2 save || true
echo "removing full node_modules (standalone runtime keeps its own minimal copy)"
rm -rf node_modules
echo "==== $(date -u +%Y-%m-%dT%H:%M:%SZ) deploy ok (standalone): $SLUG ===="
write_status "success" "Deployed origin/$BRANCH (standalone)"
trap - ERR
exit 0
fi
echo "no standalone output — using next start (legacy)"
pm2 restart "$SLUG" --update-env || pm2 start "$ECOSYSTEM" --only "$SLUG"
pm2 save || true
echo "==== $(date -u +%Y-%m-%dT%H:%M:%SZ) deploy ok: $SLUG ===="