#!/usr/bin/env node /** * 构建后准备 standalone 目录: * - 复制 .next/static 和 public 到 .next/standalone * - 同步 BUILD_ID / manifest 等索引文件 * * 用途: * - 本地:pnpm build 之后,可直接 node .next/standalone/server.js * - 宝塔:若项目路径指向 .next/standalone,用 node server.js 即可 */ const fs = require('fs'); const path = require('path'); 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 目录(复制静态资源和索引)...'); if (!fs.existsSync(standaloneDir)) { console.error('❌ 错误:未找到 .next/standalone 目录,请先执行 pnpm build'); process.exit(1); } 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); } // 禁止把开发态 Turbopack 产物打进 standalone,否则线上会 500 / Failed to load chunk const chunkFiles = fs.readdirSync(chunksDir); const turbopackChunk = chunkFiles.find((f) => f.startsWith('turbopack-') && f.endsWith('.js')); if (turbopackChunk) { console.error('❌ 错误:检测到开发态产物 ' + turbopackChunk); console.error(' 当前 .next/static 来自 next dev,不能用于线上。请在本机执行:'); console.error(' pnpm run clean && pnpm build'); console.error(' 然后重新部署 .next/standalone 整个目录。'); process.exit(1); } console.log(' public → .next/standalone/public'); copyDir(publicSrc, publicDst); // 同步构建索引(与 devlop 打包一致) 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('✅ standalone 目录已就绪(可直接 node .next/standalone/server.js)');