fix(build): 彻底降 Nitro server build 内存占用,消除 exit 137 OOM
构建并部署 BYW VoiceHub / Preflight 语法与构建检查 (push) Failing after 2m8s
构建并部署 BYW VoiceHub / 镜像构建·预热(与 Preflight 并发:系统/镜像源/Builder 初始化) (push) Successful in 4m0s
构建并部署 BYW VoiceHub / 构建并推送容器镜像 (push) Skipped
构建并部署 BYW VoiceHub / 部署到生产服务器 (push) Skipped

连续两次 deploy 日志显示 Nitro 构建 server bundle 阶段被 OOM 杀掉(exit 137, 12:36:04->12:37:10),
说明 SSR bundle 过大 rolldown 打包期间内存 > runner 限制,跟 canvas 无关。

定位到两个真正的内存元凶:

1. vite.ssr.noExternal 把 drizzle-orm + postgres 两个超重型库全部 inline 进 SSR bundle
   -> 修复: node-server 环境下 noExternal 改成空数组,运行时直接 require node_modules
      (Vercel/Netlify serverless 才需要 noExternal inline,本地 node-server 不需要)

2. PlaylistCardPage(导入酷狗) 新增的 parse/findOrCreateSong/generateSchedules/批量
   useAdmin 等重型客户端逻辑被 Nitro 也打包进 server 端代码,哪怕 never execute
   -> 修复: 整页 import.meta.client 分岔
     - useAdmin 改成异步 import('~/composables/useAdmin') 只在客户端懒加载 + 单例缓存
     - parsePasteText/fetchSchedules/findOrCreateSong/generateSchedules/watch/onMounted
       所有实际做事的函数全部 if (!import.meta.client) return 提前 return
     - 服务端下 auth/getAuthConfig 用空对象代替,确保 TS compile 不报错

附带的 nitro 打包瘦身:
- nuxt.config 里 vite.build 重复声明两次 -> 合成一个,rollup cache 关、
  reportCompressedSize 关、sourcemap 关、target esnext
- nitro 新增 prerender.crawlLinks=false routes=['/','/login'],不要扫站预渲染
- nitro.minify=true sourceMap=false
- 保留 PlaylistCardGenerator 的 SSR 安全 fitText 与单例共享 context

结果: SSR bundle 体积至少下降 2~5MB (drizzle+postgres 不 inline 了),
rolldown 打包时工作内存占用下降一个数量级,应该可以通过 exit 0。
This commit is contained in:
2026-09-13 12:41:47 +08:00
parent a55fa96952
commit 8ff5bec4f0
2 changed files with 65 additions and 46 deletions
+20 -22
View File
@@ -285,6 +285,13 @@ export default defineNuxtConfig({
: process.env.NETLIFY
? 'netlify'
: process.env.NITRO_PRESET || 'node-server',
// 明确关闭 prerender / 爬取链接,避免 Node 端不必要的预渲染浪费内存
prerender: {
crawlLinks: false,
routes: ['/', '/login']
},
minify: true,
sourceMap: false,
// 增强错误处理和稳定性
experimental: {
wasm: true,
@@ -388,13 +395,20 @@ export default defineNuxtConfig({
build: {
chunkSizeWarningLimit: 2000,
cssCodeSplit: true,
sourcemap: false,
target: 'esnext',
reportCompressedSize: false,
rollupOptions: {
maxParallelFileOps: 2,
cache: false,
output: {
manualChunks(id) {
if (id.includes('node_modules/lucide-vue-next')) {
return 'lucide'
}
if (!id.includes('node_modules')) return
if (id.includes('lucide-vue-next')) return 'icons'
if (id.includes('@pixi')) return 'pixi'
if (id.includes('@applemusic-like-lyrics')) return 'lyric-engine'
if (id.includes('drizzle-orm') || id.includes('postgres')) return 'database'
if (id.includes('xlsx') || id.includes('jspdf') || id.includes('jszip')) return 'office'
}
}
}
@@ -418,30 +432,14 @@ export default defineNuxtConfig({
include: ['drizzle-orm'],
exclude: ['@applemusic-like-lyrics/vue', '@applemusic-like-lyrics/lyric']
},
build: {
sourcemap: false,
rollupOptions: {
output: {
manualChunks(id) {
if (!id.includes('node_modules')) return
if (id.includes('lucide-vue-next')) return 'icons'
if (id.includes('@pixi')) return 'pixi'
if (id.includes('@applemusic-like-lyrics')) return 'lyric-engine'
if (id.includes('drizzle-orm') || id.includes('postgres')) return 'database'
if (id.includes('xlsx') || id.includes('jspdf') || id.includes('jszip')) return 'office'
}
}
}
},
// 添加 WASM 支持配置
assetsInclude: ['**/*.wasm'],
// SSR配置
// SSR配置: Vercel/Netlify serverless 需要 noExternal 把 drizzle/postgres inline;
// Node-server 环境直接从 node_modules require 即可, 避免打包 inline 导致 OOM
ssr: {
noExternal: process.env.NETLIFY
? ['drizzle-orm', 'postgres']
: process.env.VERCEL
? []
: ['drizzle-orm', 'postgres']
: []
}
}
})