#!/usr/bin/env node /** * Standalone 模式启动脚本 * 自动复制 .next/static 和 public 到 standalone 目录后启动服务器 */ const fs = require('fs'); const path = require('path'); const { spawn } = require('child_process'); const rootDir = path.join(__dirname, '..'); const standaloneDir = path.join(rootDir, '.next', 'standalone'); const staticSrc = path.join(rootDir, '.next', 'static'); const staticDst = path.join(standaloneDir, '.next', 'static'); const publicSrc = path.join(rootDir, 'public'); const publicDst = path.join(standaloneDir, 'public'); /** * 递归复制目录 */ function copyDir(src, dst) { if (!fs.existsSync(src)) { console.warn(`⚠️ 跳过复制:${src} 不存在`); return; } // 删除旧目录 if (fs.existsSync(dst)) { fs.rmSync(dst, { recursive: true, force: true }); } // 创建目标目录 fs.mkdirSync(dst, { recursive: true }); // 复制文件 const entries = fs.readdirSync(src, { withFileTypes: true }); for (const entry of entries) { const srcPath = path.join(src, entry.name); const dstPath = path.join(dst, entry.name); if (entry.isDirectory()) { copyDir(srcPath, dstPath); } else { fs.copyFileSync(srcPath, dstPath); } } } console.log('🚀 准备启动 Standalone 服务器...'); console.log(' (请勿在 .next/standalone 下直接 node server.js,否则会 404,须在项目根用 pnpm start)\n'); // 检查 standalone 目录 if (!fs.existsSync(standaloneDir)) { console.error('❌ 错误:未找到 .next/standalone 目录'); console.error(' 请先运行: pnpm build'); process.exit(1); } // 复制静态资源(缺一不可,否则部署到线上也会 404) console.log('📦 复制静态资源...'); if (!fs.existsSync(staticSrc)) { console.error('❌ 错误:.next/static 不存在,请先执行 pnpm build'); process.exit(1); } console.log(' .next/static → .next/standalone/.next/static'); copyDir(staticSrc, staticDst); const chunksDir = path.join(staticDst, 'chunks'); if (!fs.existsSync(chunksDir)) { console.error('❌ 错误:复制后 .next/standalone/.next/static/chunks 不存在,本地会 404,部署线上也会报错'); process.exit(1); } console.log(' public → .next/standalone/public'); copyDir(publicSrc, publicDst); // 同步构建索引(与 devlop 打包一致),避免本地/宝塔 server 用错导致页面空白 404 const nextRoot = path.join(rootDir, '.next'); const nextStandalone = path.join(standaloneDir, '.next'); const indexFiles = [ 'BUILD_ID', 'build-manifest.json', 'app-path-routes-manifest.json', 'routes-manifest.json', 'prerender-manifest.json', 'required-server-files.json', 'fallback-build-manifest.json', ]; for (const name of indexFiles) { const src = path.join(nextRoot, name); const dst = path.join(nextStandalone, name); if (fs.existsSync(src)) { try { fs.copyFileSync(src, dst); } catch (e) { console.warn(' [警告] 复制索引失败 %s: %s', name, e.message); } } } console.log('✅ 静态资源与构建索引已同步\n'); // 启动服务器(必须在 standalone 目录下运行,否则 _next/static 会 404) const serverPath = path.join(standaloneDir, 'server.js'); const DEFAULT_PORT = 30006; const port = process.env.PORT || String(DEFAULT_PORT); console.log(`🌐 启动服务器: http://localhost:${port}`); console.log(' (静态资源已复制到 .next/standalone,请勿直接 cd 到 standalone 用 node server.js)\n'); const server = spawn('node', ['server.js'], { cwd: standaloneDir, // 关键:工作目录必须是 standalone,否则找不到 .next/static stdio: 'inherit', env: { ...process.env, PORT: port } }); server.on('error', (err) => { console.error('❌ 启动失败:', err); process.exit(1); }); server.on('exit', (code) => { if (code !== 0) { console.error(`\n❌ 服务器异常退出,退出码: ${code}`); } process.exit(code); }); // 处理 Ctrl+C process.on('SIGINT', () => { console.log('\n\n👋 停止服务器...'); server.kill('SIGINT'); });